Output-Merger Stage
이제 Rendering pipeline의 최종 단계이다. 바로 이전 단계인 pixel shader에서는 Texturing과 Lighting을 통해 vertex 내부에 생성되는 pixel들의 최종 color값을 결정하는 작업을 수행했다. 아래와 같은 그림에서 큐브와 원뿔의 vertex를 기준으로 pixel을 채우는 color들을 결정한 것이다! mesh(object)별로 결정된 이 color들을 다 모아서 우리가 최종적으로 보는 스크린에 출력할 color들을 결정하는 것이 output merger stage이다.

Output-Merger, 줄여서 OM stage에서는 이렇게 스크린 위에 올려줄 final rendered pixel color를 결정한다.
이때 최종 color값에 영향을 주는 다음의 4가지 옵션을 고려한다.

Pipeline State
Pipeline state라 함은 렌더링을 위해 GPU input data를 어떻게 해석하고 처리할지를 결정하는 hardware setting(또는 description)을 말한다. cpu에서 gpu로 데이터를 넘길 때, desc를 통해 옵션을 같이 넘김으로써 그 옵션에 따라 gpu가 어떻게 동작할지에 대한 명령 목록을 제시하는 것. 여기에는 rasterizer state, blend state, depth-stencil state뿐만 아니라 주어진 지오메트리의 primitive topology type과 shader state와 같은 일반적인 설정이 포함된다.
D3D12에서는, 대부분의 그래픽스 파이프라인에 해당하는 객체들을 pipeline state objects, 즉 PSO라는 구조체에 넣어서 전달한다. 일반적으로 초기화 시점에 여러개의 PSO가 생성되고 (여러개인 이유는 각 오브젝트의 처리마다 세팅을 다르게 할 수 있도록 하기 위함), 렌더링 타임에 빠르게 교체되어 다른 pipeline state를 사용할 수 있도록 한다.

Render Target
렌더 타겟이란 무엇인강. 렌더 타겟이란 drawing commands를 받을 수 있는 리소스 또는 객체이다. 렌더 타겟을 사용하면 장면을 화면에 렌더링할 back buffer가 아닌 임시 중간 버퍼에 렌더링할 수 있다! 렌더 타겟 drawing method는 사용자가 렌더 타겟에 콘텐츠를 렌더링할 수 있게 한다. Frame buffer라는 용어는 전통적으로 color data를 저장하는 random-access memory(RAM)의 일부를 나타내기에, 램 어딘가에 존재하는 color data가 저장된 공간! 정도로 이해할 수 있다. 그래서 현재 보여지고 있는 frame buffer를 front buffer(or primary buffer)라고 하며, 그림을 그리려는 frame buffer를 back buffer(or secondary buffer)라고 한다. 이에 따라 화면을 업데이트한다는 것은 frame buffer와 back buffer 사이의 swap을 의미하며, 이는 현재 accessed 받고 있는 메모리 공간 데이터, 즉 화면에 연결되어 보여지고 있는 메모리 공간에서 다음 프레임에 그려질 다른 메모리 공간으로 포인터를 바꿔주는 개념이다.
정리하면 도화지에 그림을 그리고 컴퓨터 스크린에 보여주는 개념인데, 도화지 위에 렌더링을 할거니까 도화지는 Render Target이라는 구조체가 된다. 여기서 새롭게 다음 프레임을 그리는 과정을 인지하게 되는 상황을 피하기 위해서 현재 보여주는 도화지 이외에 별도의 도화지를 하나 더 사용하고, 두 번째 도화지가 다 그려지면 두 개를 바꾸는 방법을 사용한다. 이때 각각이 front buffer, back buffer이다. 그런데 이렇게 두 개뿐만 아니라 렌더 타겟은 임시 버퍼, 데이터 저장 등의 다른 용도로 사용할 수 있다. 예를 들어 scene을 그릴 때 RGB 값을 주로 사용하지만 depth 값으로 빛 계산, shadow 계산과 같은 추가적인 operation들을 수행하고 싶다면? rgb를 리턴하는 render target 외에도 새로운 render target을 하나 더 만들어 depth를 리턴해 저장하는 용도로 별도의 렌더링을 할 수 있다. 즉 도화지를 여러개 가져다가 다양한 데이터를 담아두는 데에 쓸 수 있다.
Depth-Stencil Testing
depth-stencil buffer(texture resource)는 depth data와 stencil data를 모두 포함할 수 있다. depth data는 카메라에 가장 가까이 있는 픽셀을 결정하는 데에 사용되고, stencil data는 어떤 픽셀들이 업데이트 될 수 있는지 마스킹하는 데에 사용된다. 두 value data들은 output-merger 단계에서 픽셀을 그릴지 여부를 결정하는 데에 사용된다.

Dx12에서 depth-stencil 리소스의 생성은 texture resource를 이용함.

Depth Test
depth buffer를 사용해서 어떤 픽셀을 그릴지 결정하는 과정을 depth buffering(or z-buffering)이라고 한다. depth value가 output-merger stage에 도달하면, (interpolation에서 오거나 pixel shader에서 오는 경우 모두) 항상 다음과 같이 클램핑 되고, 클램핑 이후에 depth value는 기존의 depth-buffer value에 비교된다.
$$ z = min(Viewport.MaxDepth, max(Viewport.MinDepth, z)) $$

depth test는 per-sample operation으로, 어떤 픽셀이 보이고 숨겨질지를 결정한다. 같은 view line에 있는 픽셀들에 대해 순차적으로 현재의 depth of value와 비교하는 과정을 통해 가까이 있는지 멀리 있는지를 테스트하는 것이다. 이때 픽셀을 스크린에 투영한 거리가 저장된 depth value보다 작다면 픽셀은 그 픽셀 앞쪽에 있다고 판단하고, depth value와 pixel의 color는 depth buffer와 color buffer를 각각 업데이트 한다. 반면에 픽셀 뒤에 있다고 판단되면 보이지 않는 것으로 간주된다.
다음 예시를 통해 이해해보자. min_depth(min_z)가 0.0, max_depth(max_z)가 1.0, red triangle의 depth는 0.8, blue triangle의 depth는 0.5라고 가정한다.

먼저 빨간색 삼각형을 처리하고 다음으로 파란색 삼각형이 처리되는 경우이다.
initialized 단계에서 볼 수 있듯이, max_depth가 1이기 때문에 처음에는 모두 1로 채워질 것. (max_depth로 클램프됨!!) 그리고 빨간색 삼각형이 먼저 처리되면서 차지하는 부분의 pixel들이 그 depth 값에 따라 0.8로 업데이트 된다. 다음으로 파란색 삼각형이 처리되면서 더 작은 값인 0.5로 업데이트되고, 칼라값 또한 업데이트 된다.

다음은 첫 번째 예시와 같은 조건에서 파란색 삼각형이 먼저 처리되는 경우이다. 모든 픽셀에 대해 동일하게 진행하기에 먼저 파란색이 차지하는 부분에 0.5라는 depth value를 z-buffer에, 그에 따른 color는 color buffer에 저장함. 이후 빨간색 삼각형이 처리되지만 파란색과 겹치는 부분에 대해서는 더 큰 value로 인해 업데이트하지 못하고.. 최종적으로 렌더링되는 scene은 어떤걸 먼저하든 똑같다는 결론...!

Stencil Test
Transition effect나 UI area를 무시하는 용도로 많이 사용.

Color Blending
depth test에서 픽셀은 픽셀을 대체하거나 discard 되거나 둘 중 하나. 그치만 몇몇 표면들은 반투명할 수 있고 (부분적으로 투명), 이 경우 물체 뒤에 있는 물체는 보여야 함! 블렌딩은 하나 이상의 pixel value를 결합해 최종 pixel color를 생성한다. 주목할 점은 블렌딩 작업이 output value가 render target에 기록되기 전에 모든 pixel shader output에 대해 수행된다는 것이다.
블렌딩 프로세스는 fragment의 alpha value를 사용한다. alpha value는 256 단계의 불투명도를 나타내는데, 0은 "fully transparent", 255는 "fully opaque"를 나타냄. 불투명도 표현을 위해 정규화된 범위 [0,1]이 정수 범위 [0,255]보다 선호된다.
$c$를 블렌딩된 색상, $\alpha$를 픽셀의 불투명도, $c_{src}$를 pixel shader로부터의 color output, $c_{dst}$를 back buffer의 color라고 정의해보자. 블렌딩이 없다면 $c_{src}$가 $c_{dst}$를 덮어쓰고 back buffer의 새로운 color가 될 것. 블렌딩을 사용한다면 $c_{src}$와 $c_{dst}$가 혼합되어 결합된 색상 $c$가 된다. 이때 blending equation은 소스 blend factor, $f_{src}$와 대상 blend factor $f_{dst}$로 정의된다.
$$ c = f_{src}c_{src} ▣ f_{dst}c_{dst} $$
이때 operator ▣는 아래의 blend operation setting에 따라 달라진다. blend를 어떻게 수행할 것인지에 대해 cpu에서 description으로 넘겨주는 형태로, ▣ 연산자는 D3D12_BLEND_OP에 의해 정의된다.
그리고 blend factor는 D3D12_BLEND에 의해 정의된다.
Dx12에서는 최종적으로 PSO의 BlendState에 넣어서 blend state를 정의해주는데,..
blending은 초기에 disable한 상태임. 블렌딩을 활성화하려면, D3D12_BLEND_DESC을 잘 설정해서 넣어주면 됨.

Keeping Destination Pixel Example
우리가 대상 픽셀을 유지하고 덮어쓰지 않고 싶다고 가정해보자. 그러면 소스 픽셀의 blend factor를 D3D12_BLEND_ZERO, 대상 blend factor는 D3D12_BLEND_ONE로, blend operator를 D3D12_BLEND_OP_ADD로 설정할 수 있는데, 이 경우 blending equation은 다음과 같다.
$$ c = f_{src}c_{src}+f_{dst}c_{dst} $$
$$ c = 0*c_{src}+1*c_{dst} $$
$$ c = c_{dst} $$
Alpha Blending (Transparency)
알파 블렌딩을 위해서는 source alpha component가 source pixel의 불투명도를 조절하는 percentage로 간주된다.
알파 블렌딩을 위한 가장 널리 사용되는 설정은 다음과 같다.
이 세팅에 대해, blending equation은 다음과 같다.
$$ c = f_{src}c_{src}+f_{dst}c_{dst} $$
$$ c = \alpha_s*c_{src}+(1-\alpha_s)*c_{dst} $$
alpha bleding(or color blending)을 위해서는 primitive들이 무작위 순서로 렌더되어서는 안된다. all opaque primitive들을 먼저 그린 후에, back-to-front 순서로 진행해야 함. 그러므로 partially transparent object들은 정렬되어야 한다.
Example
Consider four pixels competing for a pixel location. Their RGBA colors and z-coordinates are given as follows:
Q1. What is the correct order of processing the pixels?
Q2. Compute the final color of the pixel using the equation $c= \alpha_{s}*c_{src}+(1-\alpha_s)*c_{dst}$
'☃️ Study > Graphics' 카테고리의 다른 글
| Rasterization (0) | 2023.10.20 |
|---|---|
| Input Assembler & Vertex Processing (0) | 2023.10.16 |
| [CG] Meshing and Geometry (0) | 2023.05.02 |
| [CG] Optics & Lighting (0) | 2023.04.25 |
| [CG] Viewings (0) | 2023.04.25 |
댓글