본문 바로가기
게임 공부/Computer graphics

5 - 출력 병합(Output merging)

by woohyeon 2020. 8. 13.
반응형

책을 보며 개인적으로 공부하는 내용이므로 틀린 부분이 있을 수 있습니다.

자료 및 내용 출처
<게임 프로그래밍을 위한 3차원 그래픽스> 

 

 

출력 병합(Output merging)

출력 병합 단계는 렌더링 파이프라인의 마지막 단계이다. 이전 단계인 프래그먼트 처리에서는 프래그먼트의 색상을 결정하여 RGB 값을 결과로 출력했다. 여기엔 사실 불투명도를 나타내는 알파(A), 깊이를 나타내는 Z 값도 포함되어 있다. 그래서 RGBAZ 프래그먼트로 부르기도 한다. 해당 단계에서는 프래그먼트별로 불투명도와 깊이를 추가로 비교하여 최종적으로 화면에 보일 프래그먼트를 생성한다.

 

Z-버퍼링(Z-Buffering)

래스터화 단계에서 수행되는 Z-컬링을 이해하려면 Z-버퍼링을 알아야 한다고 했다. Z-버퍼링은 Z-버퍼(깊이 버퍼)를 이용한 일종의 알고리즘이다. 3차원 공간의 물체들을 2차원 평면에 표현할 때 고려해야할 것이 있다. 바로 Z(깊이) 인데, 아래와 같이 두 삼각형의 일부분이 겹쳐있다고 생각해보자. 아래 3차원 공간의 두 삼각형을 2차원 평면에 그릴 때, 겹치는 부분은 카메라로부터 더 가까이 있는 물체의 색상으로 그려질 것이다.

image

위 그림에선 파란색 삼각형의 z좌표가 더 작기 때문에, 즉 더 앞에 있기 때문에 겹치는 부분이 파란색으로 그려졌다. 실제로 우리가 정면에서 두 삼각형을 볼 때 겹치는 부분은 더 앞에 있는 삼각형의 일부분일 것이다. 이처럼 2차원에서 프래그먼트의 색상을 결정하려면 z좌표를 이용하여야 한다. 이처럼 프래그먼트의 z좌표만 따로 저장해놓은 버퍼를 z-버퍼 또는 깊이 버퍼라 한다. 그리고 z-버퍼를 이용하여 이러한 것을 결정하는 것을 z-버퍼링이라 한다. z-버퍼를 시각화하면 대략 다음과 같다.

image

z값은 [0, 1] 범위를 가지며 초기엔 모두 1.0으로 초기화된다. 버퍼의 크기(해상도)는 뷰포트 해상도와 동일할 것이다.

 

z-버퍼와 별개로 컬러 버퍼(color buffer)라는 것이 존재하는데, 지금까지 결정된 프래그먼트의 색상이 임시로 저장되는 버퍼이며, z-버퍼와 크기는 동일하다. 또한 스텐실 버퍼(stencil buffer)라는 것이 있는데 이 3개의 버퍼를 통틀어 프레임 버퍼(frame buffer)라고 한다. 여기서는 우선 z-버퍼와 컬러 버퍼에 대해서만 살펴본다.

컬러 버퍼는 초기엔 다음과 같이 흰색으로 채워졌다고 가정한다.

image

이러한 두 버퍼를 가지고 스크린 상의 픽셀을 결정하는 과정을 살펴보자.

image

우선 첫 단계는 초기화된 상태로 위에서 살펴본 것들과 같은 모습이다. 이 두 버퍼를 초기화한 상태로 가지고 있다가, 입력으로 각 프래그먼트가 들어올 것이다. 프래그먼트는 RGB값은 물론 Z값도 가지고 있을 것이다. 만약 빨간색 삼각형이 먼저 들어왔다고 생각해보자. 빨간색 삼각형을 구성하는 모든 프래그먼트의 z값은 0.8이라고 가정하자.

우선 빨간색 삼각형에 해당하는 영역의 프래그먼트들의 z값과 z-버퍼에서 이에 대응하는 영역의 z값을 서로 비교한다. 현재 z-버퍼의 모든 z값은 1.0이며, 빨간색 삼각형의 프래그먼트들은 0.8이다. 비교한 결과 프래그먼트가 더 앞에 존재하기 때문에 해당 영역은 빨간색으로 채워질 것이다. 따라서 z-버퍼에서 해당 영역의 값을 빨간 삼각형의 프래그먼트의 z값으로 갱신해야 한다. 또한 컬러 버퍼는 해당 영역(픽셀)을 빨간색으로 갱신한다. 그 결과가 두 번째 단계에 나타낸 모습이다.

다음으로 파란색 삼각형이 그려질 차례이다. 파란색 삼각형의 z값은 0.5라고 가정하자. 파란색 삼각형의 프래그먼트들이 입력으로 들어오면 해당 z값들과 이에 대응하는 z-버퍼의 z값을 서로 비교한다. 그 결과 빨간색 삼각형과 겹치지 않는 부분은 모두 0.5로 갱신될 것이다. 그리고 겹치는 부분 또한 파란색 삼각형의 z값이 더 작으므로 파란색 삼각형의 z값으로 갱신될 것이다. 추가로 컬러 버퍼의 색상도 갱신된다.

 

위에선 빨간색 삼각형을 먼저 그렸는데, 순서의 독립성을 확인하기 위해 역순을 확인해보자.

image

초기엔 동일하게 z-버퍼는 1.0, 컬러 버퍼는 하얀색으로 초기화된다. 이후 파란색 삼각형의 프래그먼트와 z-버퍼를 비교하여 z값을 갱신한다. 그리고 컬러 버퍼 또한 갱신한다. 그 다음 빨간색 삼각형이 그려지는데, 파란색 삼각형과 겹치지 않는 부분은 그대로 빨간색 삼각형의 z값과 색상이 입혀지고, 겹치는 부분은 파란색 삼각형의 z값보다 크기 때문에, 즉 더 뒤에 있기 때문에 갱신되지 않는다.

그리고 결과는 이전과 동일하다는 것을 보여준다. 이처럼 z-버퍼링을 이용한 처리는 렌더링되는 순서와 관계가 없어서 유연하지만, 성능상 임의의 순서로 처리하는 것은 성능 저하를 일으킬 수 있다.



알파 블렌딩(Alpha blending)

위에서 살펴본 삼각형들은 모두 불투명하다는 가정하에 진행했다. 하지만 그리는 물체들이 항상 불투명하진 않다. 만약 파란색 삼각형이 반투명했다면, 빨간색 삼각형과 겹치는 부분은 파란색이 아니었을 것이다. 이 경우 파란색 삼각형을 통해 뒤에 존재하는 빨간색 삼각형의 색상이 비쳤어야 한다. 때문에 위에서 살펴본 단순한 처리가 아닌, 색상을 혼합하여 불투명도(알파)에 맞게 알맞은 색상을 출력해야 한다. 예를 들어 파란색 삼각형의 불투명도가 40% 였다면 겹치는 부분의 색상을 처리할 때, 파란색은 40%, 빨간색은 60% 만큼 색상을 차지하도록 해야 한다. 이처럼 색상을 혼합하여 불투명도를 표현하는 방식을 알파 블렌딩(Alpha blending)이라 한다.


RGB와 비슷하게 알파 또한 값의 범위를 0~255(8비트)로 설정할 수 있지만, 정규화된 범위의 0~1이 더 선호된다. 알파가 0이라면 완전히 투명하며, 알파가 1이라면 불투명한 상태를 의미한다. 알파는 보통 RGB값과 함께 나타내며, RGBA라고 한다. 반투명한 빨간색일 경우 RGBA는 (1,0,0,0.5) 가 된다. 알파 블렌딩을 통해 혼합된 색상을 구하는 공식은 다음과 같다.

image

c는 혼합된 컬러를 의미하며, α는 프래그먼트의 불투명도, cf는 프래그먼트 색상, cp는 픽셀 색상을 의미한다. 즉 파란색 삼각형의 불투명도가 0.5라면 파란색 삼각형과 빨간색 삼각형이 겹치는 영역의 색상은 다음과 같다.

c = 0.5 * (0,0,1) + 0.5 * (1,0,0)
  = (0, 0, 0.5) + (0.5, 0, 0)
  = (0.5, 0, 0.5)

즉 R값이 50%, B값이 50% 혼합된 색상이다.

 

이를 그림으로 표현하면 다음과 같다. 빨간색 삼각형의 z값은 0.8이고, 파란색 삼각형의 z값은 0.5라고 가정하자. 겹친 부분에 해당하는 색상은 (0.5, 0, 0.5) 일 것이다.

image

그런데 한 가지 문제점이 있다. 위 그림은 빨간색 삼각형 먼저 그려졌다고 가정했다. 두 삼각형을 그리는 과정을 살펴보면, 우선 빨간색 삼각형이 그려지면서 컬러버퍼와 z-버퍼의 값이 빨간색, 0.8로 채워진다. 그다음 파란색 삼각형이 그려지면서 겹치지 않는 부분은 파란색과 0.5로 채우고 겹치는 부분은 알파 블렌딩을 통해 (0.5, 0, 0.5)로 갱신할 것이다. 이 경우는 문제가 전혀없다.

만약 빨간색 삼각형이 아닌 파란색 삼각형부터 그린다고 생각해보자. 파란색 삼각형이 차지하는 영역의 컬러 버퍼와 z-버퍼를 파란색과 0.5로 채울 것이다. 그 다음 빨간색 삼각형이 그려질 때 겹치지 않는 부분은 빨간색과 0.8로 채워질 것이다. 그런데 겹쳐지는 부분을 그리려고 보니 현재 z-버퍼의 값이 0.5로 빨간색 삼각형의 z값인 0.8보다 작다. 따라서 아예 그리지 않고 종료가 되며, 겹치는 부분은 파란색이 그대로 유지된다.

이처럼 반투명한 물체에 대한 z-버퍼링은 불투명한 물체를 그릴 때와 달리 그 순서가 매우 중요하다. 따라서 불투명한 물체들이 먼저 그려진 뒤, 반투명한 물체들이 뒤에서부터 앞으로 그려져야 한다. 즉 z값이 큰 물체부터 순서대로 그려져야 한다. 이를 위해선 반투명한 삼각형들은 z값을 기준으로 정렬되어 있어야 한다.
이러한 조건은 프래그먼트 셰이더에서 사용자가 직접 구현해주어야 한다.

 

Z-컬링(Z-Culling)

지금까지 보았던 Z-버퍼링(z-버퍼를 이용한 깊이 검사)은 출력 병합 단계에서 수행된다. 그런데 살짝 아쉬운 점이 있다. 만약 입력으로 들어온 프래그먼트의 z값이 현재 z-버퍼에 존재하는 값보다 크다면, 즉 기존 물체에 의해 가려진다면 해당 프래그먼트는 버려질 것이다. 그런데 프래그먼트 처리에서 이러한 버려질 프래그먼트에 텍스쳐링, 조명 등의 연산을 이미 수행했다. 이러한 경우 연산에 투자한 시간이 헛수고가 된다.

만약 프래그먼트 처리 단계에 진입하기 전에 미리 z-버퍼와 비교할 수 있다면 위와 같은 시간 낭비를 하지 않아도 된다. 이처럼 프래그먼트 처리 진입 전에 z값을 통해 버려질 프래그먼트를 미리 선별하는 것을 Z-컬링(Z-Culling)이라 한다. Z-컬링은 프래그먼트 처리의 전 단계인 래스터화 단계에서 수행된다. 다만 하드웨어(GPU)에서 Z-컬링에 대한 기능을 지원해주어야 한다. Z-컬링은 환경에 따라 ZCULL, Early Z, Hierarchical Z 라고 불리기도 한다.

Z-컬링을 수행하는 알고리즘 중 프리-z 패스(pre-z pass) 라는 알고리즘을 간단하게 알아보자. 프리-z 패스는 투패스(two-pass) 알고리즘의 일종이다. 투패스란 2번의 렌더링을 거친다는 의미로 그 과정이 첫 번째 pass, 두 번째 pass로 나뉘어져 있다.

우선 프리-z 패스의 첫 번째 pass에선 z-버퍼를 얻는 것을 목표로 한다. 즉 픽셀 셰이더(프래그먼트 프로그램)의 텍스쳐링, 조명 등의 연산은 수행하지 않으며 컬러 버퍼를 생성하지 않는다. 오로지 z-버퍼만 얻고 첫 pass를 종료한다. 그리고 이러한 z-버퍼를 가지고 두 번째 pass에서 정상적인 렌더링을 수행한다. 프래그먼트 처리 단계에 진입하기 전에 z-버퍼를 가지고 z-컬링을 수행하여 가려질 픽셀들을 미리 걸러낸다. 그리고 나머지 단계를 그대로 수행한다.

이렇게 보면 Z-컬링을 무조건 수행하는 것이 좋은 것처럼 보이지만, 사실 그렇지 않을 수도 있다. 예를 들어 픽셀 셰이더의 연산에 의해 z값이 수정되는 경우 당연히 Z-컬링이 수행되어서는 안된다. 따라서 픽셀 셰이더에서 z 값을 건드리지 않도록 하는 것이 좋다. 두 번째론, 픽셀 셰이더의 연산들이 가벼운 경우이다. Z-컬링을 하는 이유는 버려질 픽셀들에 대해 픽셀 셰이더의 무거운 연산을 수행하지 않음으로써 성능을 이득보려는 것이다. 하지만 픽셀 셰이더의 연산보다 두 번 렌더링하는 연산이 더 크다면 이는 오히려 성능 저하를 일으킨다. 따라서 상황에 따라 적절하게 사용해야 한다.




댓글