[OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문] 10. 출력 병합기
GPU 파이프라인의 마지막 단계인 출력 병합기에서, 픽셀의 앞뒤를 가리는 z-버퍼링과 반투명 물체를 섞는 알파 블렌딩을 정리했습니다.
개요
우리는 이제 GPU 파이프라인의 마지막 단계인 출력 병합기(output merger)에 이르렀다. (GL은 출력 병합기라는 이름 대신 ‘프래그먼트별 연산’이라는 용어를 사용한다. 하지만, 프래그먼트 쉐이더 역시 프래그먼트별 연산을 수행하므로, GL의 이러한 명명법은 혼란을 초래할 수 있다.) 래스터라이저와 마찬가지로, 출력 병합기도 하드웨어로 고정되어 있는데, GL 함수를 사용하여 그 작동 방식을 일부 조정할 수 있다. 이 장은 출력 병합기가 수행하는 두 가지 주된 작업인 z-버퍼링과 알파 블렌딩을 다룬다.
10.1 Z-버퍼링
그림 10.1. 뷰포트 내의 두 삼각형이 (x, y) 좌표의 픽셀을 놓고 경쟁하는데, 이 픽셀은 결국 파란색으로 칠해진다.
그림 10.1은 뷰포트 안의 두 삼각형, 그리고 스크린 공간 좌표가 (x,y)인 픽셀을 보여준다. 파란색 삼각형이 빨간색 삼각형 앞에 있으므로 픽셀은 파란색으로 그려질 것이다. 이러한 결정은 (x,y)에서 두 삼각형의 z값 비교를 통해 이뤄진다.
GL은 컬러 버퍼, 깊이 버퍼, 스텐실 버퍼(stencil buffer)를 제공하는데, 이들을 합해서 프레임 버퍼라고 한다. 컬러 버퍼는 스크린의 2차원 뷰포트에 나타날 픽셀 전체를 저장하는 메모리 공간이다. 깊이 버퍼는 z-버퍼라고도 불리는데, 컬러 버퍼와 동일한 해상도를 가지며 현재 컬러 버퍼에 저장되어 있는 픽셀의 z값을 저장한다. 이 z값은 스크린 공간에서 정의된 3차원 뷰포트의 z범위에 속한다. 이 책은 스텐실 버퍼를 다루지 않는다.
그림 10.2.a. Z-버퍼링. 빨간색, 파란색 삼각형 순서로 렌더링이 이루어졌다.
그림 10.2.b. Z-버퍼링. 파란색, 빨간색 삼각형 순서로 렌더링이 이루어졌다.
예제 코드 10.1. 컬러 버퍼와 깊이 버퍼를 초기화하는 GL 프로그램.
그림 10.1에서 빨간색 삼각형을 먼저, 파란색 삼각형을 나중에 처리할 때 z-버퍼와 컬러 버퍼가 갱신되는 과정이 그림 10.2.a에 나와 있다. 7.4절에서 소개한 것처럼, glDepthRange로 정의된 스크린 공간 3차원 뷰포트의 z범위가 [0.0, 1.0]이라고 가정하자. 그러면 z-버퍼는 배경의 z값에 해당하는 1.0으로 초기화된다. 한편, 컬러 버퍼는 배경색으로 초기화된다. 그림 10.2에 배경은 흰색이라고 가정했다. 예제 코드 10-1에서, glClearDepth와 glClearColor는 각각 초기 z값과 컬러를 설정하고, glClear는 이를 이용해 z-버퍼와 컬러 버퍼를 초기화한다.
프래금너트 쉐이더가 한 프래그먼트의 RGBA 색상을 출력할 때, 그 프래그먼트의 스크린 공간 좌표 (x,y,z)는 자동으로 출력 병합기에 전달된다. 그러면 출력 병합기는 그 z좌표를 현재 z-버퍼의 (x,y)에 기록되어 있는 z값과 비교한다. 만약 프래그먼트의 z좌표가 작다면, 이는 현재 컬러 버퍼에 저장되어있는 픽셀보다 앞에 있다는 뜻이므로, 컬러 버퍼의 픽셀을 프래그먼트 색상으로 대체한다. 동시에 z-버퍼의 (x,y)에 저장된 z값도 프래그먼트의 z좌표로 대체한다. 반대로, 프래그먼트의 z좌표가 크다면, 이는 현재 컬러 버퍼에 저장되어 있는 픽셀보다 뒤에 있어 보이지 않게 되므로 해당 프래그먼트를 버린다.
간단한 논의를 위해 그림 10.1의 두 삼각형은 스크린 공간의 xy평면에 평행하고, 파란색과 빨간색 삼각형은 각각 0.5와 0.8의 z값을 가진다고 가정하자. 그림 10.2.a의 가운데 열은 빨간색 삼각형만을 렌더링한 결과를 보여준다. 그 다음에 파란색 삼각형을 렌더링한 결과는 그림 10.2.a의 오른쪽 열에 있다. (38.56), (38.57), (39.56) 좌표에 위치한 픽셀들이 빨간색에서 파란색으로 바뀐 것에 주목하라. 동시에 z-버퍼의 같은 곳에 z값 역시 0.8에서 0.5로 갱신되었다. 이처럼 z-버퍼를 이용하여 픽셀과 프래그먼트의 앞뒤 순서를 가리는 알고리즘을 z-버퍼링(z-buffering) 혹은 깊이 버퍼링(depth buffering)이라 부른다.
그림 10.2.b는 삼각형을 반대의 순서로 처리한 경우를 보여준다. 즉, 파란색 삼각형이 먼저, 빨간색 삼각형이 나중에 그려졌다. 렌더링이 끝났을 때 z-버퍼와 컬러 버퍼는 그림 10.2.a와 동일한 내용을 담고 있게 된다. 원칙적으로 z-버퍼링 알고리즘은 삼각형들을 임의의 순서로 처리하는 것을 허용한다. 이러한 특징 덕분에 z-버퍼링 알고리즘이 널리 채택될 수 있었다. 하지만 삼각형 처리 순서가 중요할 때가 있는데, 이에 대해서는 다음 절에서 논의할 것이다.
z-버퍼링을 활성화하기 위해서는 glEnable(GL_DEPTH_TEST)를 호출해야 한다. 그 다음, glDepthFunc를 사용하여 z값 비교에 사용할 방법을 지정해야 한다. 특별한 경우가 아니라면 프래그먼트 z좌표가 픽셀의 z값보다 작을 때, z-버퍼와 컬러 버퍼가 갱신되므로, glDepthFunc의 기본값은 GL_LESS이다.
10.2 알파 블렌딩
앞 절에서 우리는 ‘프래그먼트 쉐이더가 출력한 프래그먼트’와 ‘컬러 버퍼에 저장된 픽셀’이 어떻게 경쟁하는지 배웠다. 프래그먼트는 픽셀을 대체하거나 혹은 폐기되었다. 이 절은 프래그먼트와 픽셀이 혼합되어야 하는 경우를 설명할 것이다.
이제까지 우리는 암묵적으로 모든 삼각형들은 불투명하다고 가정했다. 따라서, 한 픽셀을 놓고 경쟁하는 두 삼각형 중 하나는 다른 하나를 완전히 가리게 된다. 하지만 반투명(translucent) 물체를 렌더링할 때는 이야기가 달라진다. 반투명한 프래그먼트의 z좌표가 컬러 버퍼에 있는 픽셀의 z값보다 작을 경우, 픽셀의 색상은 프래그먼트를 통해 비쳐 보여야 한다. 이는 픽셀과 프래그먼트 색상의 혼합을 필요로 한다.
프래그먼트 쉐이더가 출력하는 프래그먼트의 RGBA 색상 중 A를 알파 채널(alpha channel)이라고도 하는데, 픽셀과의 혼합 과정은 프래그먼트의 알파 채널을 이용하므로 알파 블렌딩(alpha blending)이라 불린다. A는 대개 RGB 각각의 채널과 같은 크기의 비트 수를 가진다. 예를 들어, R에 8비트가 할당된다면 A에도 8비트가 할당되어 총 32비트의 RGBA 색상을 이룬다. 이렇게 A에 8비트를 할당할 경우, 256 단계의 불투명도를 표현할 수 있는데, [0,255]의 정수 범위보다는 정규화된 범위인 [0,1]이 선호된다. 최소값 0은 ‘완전한 투명’을 나타내고 최대값 1은 ‘완전한 불투명’을 나타낸다. 전현적인 알파 블렌딩 식은 다음과 같다.
\[c = \alpha c_f + (1 - \alpha) c_p \tag{10.1}\]여기서 c는 블렌딩된 색상을 나타내고, 알파는 프래그먼트의 불투명도(알파 채널), $c_f$는 프래그먼트 색상, $c_p$는 픽셀 색상을 나타낸다. 만약 알파가 0이면 투명한 프래그먼트는 보이지 않게 되고, 알파가 1이면 프래그먼트는 픽셀을 완전히 가리게 된다. 만약 알파가 0.5라면 c는 프래그먼트와 픽셀 색상이 평균값이 될 것이다.
그림 10.3 알파 블렌딩. (a) 파란색 삼각형은 반투명하고, 빨간색 삼각형은 불투명하다. (b) 컬러 버퍼의 세 픽셀이 블렌딩된 색상을 가진다.
그림 10.3.a에서 파란색 삼각형의 RGBA 색상은 (0, 0, 1, 0.5)이고, 빨간색 삼각형의 RGBA 색상은 (1, 0, 0, 1)이다. 즉, 파란색 삼각형은 반투명하고, 빨간색 삼각형은 불투명하다. 빨간색 삼각형이 먼저 처리되어 컬러 버퍼가 채워졌고, 이제 프래그먼트 쉐이더가 (38, 56) 좌표를 가진 파란색 프래그먼트를 출력했다고 하자. 그러면 파란색 프래그먼트는 식 (10.1)에 의해 빨간색 픽셀과 블렌딩된다. 블렌딩된 색상 c는 (0.5, 0, 0.5)가 되고, 이는 컬러 버퍼의 (38,56) 좌표에 쓰여진다. 또한, z-버퍼의 (38,56) 좌표는 파란색 프래그먼트의 깊이 0.5로 변경된다. 마찬가지로, (38,57)과 (39,56) 좌표도 처리된다.
그런데, 렌더링 순서가 바뀌어, 파란색 삼각형을 먼저, 빨간색 삼각형을 나중에 그리면 어떻게 될까? (38,56) 좌표를 예로 들면, 빨간색 프래그먼트의 z좌표 0.8은 파란색 픽셀의 z값 0.5보다 크므로 폐기될 것이다. 블렌딩이 이뤄지지 않는다.
우리는 앞 절에서, z-버퍼링 알고리즘은 삼각형 처리 순서에 독립적임을 보였다. 하지만 이는 불투명한 삼각형들에만 적용된다. 반투명한 삼각형들은 이렇게 임의의 순서로 렌더링될 수 없다. 대신, 모든 불투명한 삼각형이 처리된 뒤, 반투명한 삼각형들은 뒤에서부터 앞으로(back-to-front) 차례차례 처리되어야 한다. 이를 위해 반투명한 삼각형들은 정렬되어야 한다. 하지만 삼각형들을 정렬하는 것은 간단한 문제가 아니다. 또한 매 프레임마다 카메라 시선이 바뀐다면, 새로운 시선에 대해 실시간으로 정렬이 이루어져야 한다. 뷰 프러스텀 안에 반투명한 삼각형이 많을수록 계산 부하는 커진다.
예제 코드 10-2. 알파 블렌딩을 위한 GL 프로그램.
예제 코드 10-2는 알파 블렌딩을 위한 GL 프로그램인데, z-버퍼링에서와 마찬가지로, 우선 glEnable(GL_BLEND)를 호출해서 알파 블렌딩을 활성화한 후, glBlendFunc와 glBlendEquation을 사용하여 프래그먼트와 픽셀이 혼합되는 방법을 지정한다. glBlendFunc의 첫 번째와 두 번째 파라미터는 각각 프래그먼트와 픽셀의 가중치를 설정한다. 예제 코드 10-2에서는 glBlendFunc의 첫 번째 파라미터로 GL_SRC_ALPHA를 썼는데, 여기에서 SRC(source)는 프래그먼트를 의미하므로, GL_SRC_ALPHA는 프래그먼트의 알파가 된다. 반면, glBlendFunc의 두 번째 파라미터 GL_ONE_MINUS_SRC_ALPHA는 이름 그대로 1-알파를 뜻한다. 이러한 가중치를 가진 프래그먼트 색상과 픽셀 생상이 어떻게 혼합될 것인지를 glBlendEquation이 지정하는데, 그 기본값은 GL_FUNC_ADD이다. 즉 둘을 더하라는 것이다. 예제 코드 10-2는 식 (10.1)을 그대로 구현하고 있다.
참고