쉐이더 심화 3-투명도와 깊이(~Depth Testing)
컴퓨터 그래픽에서의 색상은 R, G, B, A 채널로 이루어져 있다. 여기서는 Alpha Channel을 이용해 다양한 형태의 오브젝트가 들어있는 씬을 그리고, 모든 대상을 렌더링하고, 캐릭터에 애니메이션 작업을 하는 방법을 배우겠다.
(=이와 관련된 연산은 fragment 처리 단계에서 이루어진다.)
1. Quad Mesh에 대상 그리기
2. Alpha Testing
3. Depth Testing
1. Quad Mesh에 대상 그리기
= 사각형이 아닌 형태를 그려보자!
= 2D 게임은 사용하지 않는 fragment를 감춰버린다.(모든 것을 Quad에 그리기 때문에 Alpha Testing으로 투명하게 만듦.)
(3D 게임 : 복잡한 mesh로 다양한 형태 렌더링.)
1.1. Mesh
= 원하는 좌표, 원하는 크기로 Mesh를 배치.
= Mesh 움직이는 방법을 배우지 않아서 원하는 곳에 vertex 생성.
opApp.cpp에서 quad 생성
void buildMesh(ofMesh& mesh, float w, float h, glm::vec3 pos)
{
float verts[] = { -w+pos.x, -h+pos.y, pos.z,
-w+pos.x, h+pos.y, pos.z,
w+pos.x, h+pos.y, pos.z,
w+pos.x, -h+pos.y, pos.z
};
float uvs[]={0,0, 0,1, 1,1, 1,0};
for(int i=0; i<4; i++)
{
int idx=i*3;
int uvIdx=i*2;
mesh.addVertex(glm::vec3(verts[idx], verts[idx+1], verts[idx+2]));
mesh.addTexCoord(glm::vec2(uvs[uvIdx], uvs[uvIdx+1]));
}
ofIndexType indices[6]={0,1,2,2,3,0};
mesh.addIndices(indices, 6);
}
ofApp.cpp에서 원하는 크기 갖게 Mesh를 설정한다.
(예시는 수평 방향으로 화면의 1/4, 수직 방향은 절반 크기.)
draw()에서 uniform texture로 설정 후 작동되도록 코드도 작성한다.
void ofApp::setup(){
ofDisableArbTex();
buildMesh(charMesh, 0.25, 0.5, glm::vec3(0.0, 0.15, 0.0));
alienImg.load("alien.png");
charShader.load("passthrough.vert", "alphaTest.frag");
}
-------(생략)---------
void ofApp::draw(){
charShader.begin();
charShader.setUniformTexture("greenMan", alienImg, 0);
charMesh.draw();
charShader.end();
}
++ 표준에 가까운 텍스쳐를 가까우기 위해 스크린 픽셀 좌표를 사용하는 텍스쳐를 비활성한다.
(ofDisableArbTex();)
++ ofApp.h에서 charMesh, charShader, alienImg를 각각 ofMesh, ofShader, ofImage로 선언.
1.2. shader
1.2.1. vertex shader
vertex 정보만 다음 파이프라인에 전달하는 기본적인 기능만 수행하면 되므로, 이전에 사용한 shader와 유사하게 작성한다.
passthrough.vert
#version 410
layout (location=0) in vec3 pos;
layout (location=3) in vec2 uv;
out vec2 fragUV;
void main()
{
gl_Position=vec4(pos, 1.0);
fragUV=vec2(uv.x, 1.0-uv.y);
}
1.2.2. fragment shader
외계인 텍스쳐에서 alpha가 1.0보다 작은 영역에 해당하는 fragment를 quad에서 감춰야 하므로 alpha testing이 적용된 discard 명령어를 사용한다.
alphaTest.frag
#version 410
uniform sampler2D greenMan;
in vec2 fragUV;
out vec4 outCol;
void main()
{
outCol = texture(greenMan, fragUV);
if (outCol.a < 1.0) discard;
}
해당 코드의 결과는 아래와 같다.
2. Alpha Testing, Discard
우선 Alpha Testing 기법에 대해 알아보자.
2.1. Alpha Testing?
= alpha의 cutoff threshold를 정의한다.
= 각 fragment의 alpha를 threshold값과 비교한다.
= fragment alpha가 1보다 크면 렌더링하고, 작으면 처리를 중단시킨다. (버리는 명령어가 discard)
(++1.0인 경우, 100% 불투명하지 않은 fragment는 버린다.)
2.2. Alpha Blending?
= ofDisableAlphaBlending()으로 alpha blending을 비활성해도 1의 코드는 잘 작동된다.
= 기준값에 따라 값을 처리하는 과정에 GPU의 alpha blending이 필요하지 않기 때문.
= 2.1과 2.2는 분명히 다르지만, 둘 다 fragment의 alpha값에 영향을 미치므로 alpha blending 비활성화가 되어있을 시, 코드 작동이 원활하지 않을 수 있으므로 삭제하고 진행하는 것을 장려한다.
3. Depth Testing
3.1. 배경 texture 적용
= Z축 중요. (NDC에서 Z축은 우리가 컴퓨터 화면을 바라보는 방향을 향하개 해서 외계인보다 뒤에 그려져야 하기 때문.)
=> Z값이 작은 vertex가 큰 vertex보다 앞에 위치하므로 배경 mesh는 0보다 큰 Z값이어야 한다.
ofApp.cpp
buildMesh(backgroundMesh, 1.0, 1.0, glm::vec3(0.0, 0.0, 0.5));
스크린에 꽉 차도록 배경 mesh를 설정한다.
ofApp.cpp
void ofApp::draw(){
alphaTestShader.begin();
alphaTestShader.setUniformTexture("tex", alienImg, 0);
charMesh.draw();
alphaTestShader.setUniformTexture("tex", backgroundImg, 0);
backgroundMesh.draw();
alphaTestShader.end();
}
두 mesh를 한 스크린에 렌더링해야하기 때문에 같은 두 mesh를 같은 texture에 배치하도록 설정한다.
++ shader를 alphaTestShader로 바꾸고, 배경 Mesh와 이미지를 설정하기 위해 ofMesh, ofImage를 ofApp.h에 추가한다.
++ alphaTest fragment shader의 uniform sampler2D를 tex로 바꾼 뒤 진행한다.
위 코드의 결과는 아래와 같다.
3.2. Vertex Z값 비교
= 그래픽 파이프라인에서 Mesh처리시, GPU에서 현재 프레임 그려질 대상에 대한 정보 x.
-> 깊이 버퍼(Depth Buffer, 특정 프레임 렌더링시 깊이 정보를 저장하는 texture) 추가
=> 깊이 버퍼를 사용하는 모든 계산은 fragment 처리 단계에서 이루어진다.
= setup()에서 ofEnableDepthTest()를 추가해서 다시 빌드하면 GPU가 깊이 버퍼를 생성해서 빌드하면 아래와 같다.
외계인의 크기를 배경에 맞게 조절해준다. (아래의 예시와 같은 사이즈와 위치를 원할 경우 아래의 코드를 작성한다.)
buildMesh(charMesh, 0.1, 0.2, glm::vec3(0.0, -0.2, 0.0));
다음은 구름을 만들면서 alpha blending, alpha texture의 차이를 알아보고 태양을 통해 addictive blending, 스프라이트 애니메이션 생성 과정을 알아볼 것이다.