DirectX 2D/DirectX 2D 정리

렌더링 파이프라인 ( Rendering Pipeline )

무면허 개발자 2022. 10. 18. 11:10

( 본 게시글은 작성자가 메모용으로 사용하는 용도임을 밝힙니다. 정보가 불확실할 수 있으니 참고 부탁드립니다 )

 

렌더링 파이프라인 ( Rendering Pipeline ) 혹은 그래픽스 렌더링 파이프라인 ( Graphics Rendering Pipeline )은 리소스를 2차원에 투영하는 렌더링 프로세스를 표현한 것이다. 데이터로 존재하는 리소스가 모니터에 출력하는 과정까지 렌더링 파이프라인을 따르게 된다.

렌더링 파이프라인 구조는 크게 5단계로 나눌 수 있다.

 

1. 입력 조립 ( Input Assembler )

2. 정점 쉐이더 ( Vertex Shader )

3. 래스터라이저 ( Rasterizer )

4. 픽셀 ( 프래그먼트 ) 쉐이더 ( Pixel Shader )

5. 출력 병합 ( Output Merger )

 

============================================================================================

 

입력 조립 ( Input Assembler )

Vertex Specification라고도 불리는 Input Assembler 단계는 CPU ( 중앙 처리 장치 )로부터 렌더링을 수행할 도형의 정점 정보를 정점 버퍼에 담아 전달 받는다. GPU ( 그래픽 처리 장치 )는 CPU의 자원에 직접 접근할 수 없기 때문에, CPU에서는 정점 버퍼라고 부르는 메모리에 필요한 정보를 담아 GPU로 전달해준다. 정점 버퍼는 정점 데이터를 CPU에서 GPU로 운반하기 위한 자료구조이다. 여기서 정점 버퍼는 위치, 노말, 색상, UV를 담고 있는데, 직렬화된 형태 ( 배열 )로 담고 있다. 그리고 GPU에서는 이렇게 전달 받은 렌더 상태의 버텍스 명세를 통해 정점 데이터 ( 구조체 )로 조립한다. 그런데 이렇게 정점 버퍼를 정점 데이터로 조립한다고 입력 조립이 아니고, 정점들을 모아서 삼각형과 같은 기본 도형 ( Primitive )로 조립해주기 때문에 입력 조립 단계라고 한다. 이렇게 조립된 프리미티브가 다음단계인 정점 쉐이더의 입력이 된다.

 

정점 쉐이더 ( Vertex Shader )

정점 쉐이더 ( Vertex Shader ) 단계에서는 Input Assembler 단계에서 입력받은 정점 데이터로 공간 변환을 수행한다. (전단계인 Input Assembler 단계에서 받은 정점들의 정보로 프리미티브는 생성되었지만, 공간 좌표계를 변환할 필요가 있다. 기본적으로 도형들은 자신만의 좌표계인 Local Space의 좌표를 가지는데, 모든 물체들이 하나의 월드에 위치하도록 Local Space에서 World Space로 변환하고 실제 플레이어가 바라보는 카메라가 중심이 되는 공간인 View Space로 변환해준다.

 

공간 변환 과정

공간 변환은 각각의 행렬 연산을 통해 이루어진다. 변환 행렬은 총 3가지로, Model, View, Projection이다. 각각 변환 행렬 이름이면서, 변환 자체를 지칭한다.

 

M ( Model )

오브젝트 공간 ( Model Space, Object Space ) -> 월드 공간 ( World Space )

각 오브젝트마다 자신의 피벗 위치를 원점으로 하는 좌표 공간 ( Local Space )을 가지고 있다. 게임 내의 월드는 단하나의 위치를 원점으로 하는 좌표 공간 ( World Space )를 갖고 있다. 여기서 각 오브젝트의 좌표 공간을 변환하여 하나의 월드 공간으로 통합하게 된다. 이 과정에서 이동 ( Translate ), 회전 ( Rotate ), 크기 ( Scale ) 변환이 이루어진다. 3가지 변환은 각각 행렬을 통해 수행되고, 이를 하나의 행렬로 만든 것을 TRS 행렬이라고 한다.

 

V ( View )

월드 공간 ( World Space ) -> 카메라 공간 ( View Space )

카메라 공간은 카메라의 위치가 원점이고, 카메라가 바라보는 방향이 +Z축인 공간을 의미한다. View 변환은 모든 오브젝트를 화면에 그려내기 쉽도록 카메라를 기준으로 공간을 변환하는 과정이다.

 

P ( Projection )

카메라 공간 ( View Space ) -> 클립 공간 ( Clip Space )

카메라 기준의 정점 위치를 화면에 보이기 위한 정점 위치로 변환한다. 화면에 렌더링될 수 있는 영역을 나타내는 절두체 ( Frustum )가 정의된다. 절두체를 완전히 벗어나는 폴리곤들은 모두 버려지고, 절두체의 경계에 걸쳐 있는 폴리곤들은 일단 유지한다. 원근감이 없는 직교 투영(Orthographic Projection) 또는 원근감이 있는 원근 투영(Perspective Projection)이 진행된다. 클립 공간의 좌표계는 4D이며, 클립 공간의 X,Y좌표는 -1 ~ 1 범위에 존재하며, Z 좌표는 0 ~ 1 범위에 존재한다. W값은 카메라에서 멀수록 커지며, 추후 NDC 변환에 사용된다. Vertex Shader의 최종 출력은 클립 공간의 정점 데이터이다.

 

래스터라이저 ( Rasterizer )

래스터라이저 ( Rasterizer ) 단계는 정점 정보를 완전히 결정한 도형을 실제 픽셀 데이터로 변환하는 단계이며, 프로그래밍이 불가능한 고정 파이프라인 단계이다. 클립 공간의 정점 데이터를 전달받아 프래그먼트를 구성하고, 화면에 출력할 픽셀들을 찾아낸다. 픽셀 색상등의 데이터는 정점 데이터를 기준으로 보간된다. 프래그먼트 ( Fragment )란 픽셀 하나의 색상을 화면에 그려내기 위한 정보를 담고 있는 데이터이다. 래스터라이저의 역할로는 총 5개가 있다.

 

1. 클리핑 ( Clipping )

2. 원근 분할 ( Perspective Division )

3. 후면 컬링 ( Back-face Culling )

4. 뷰포트 변환 ( Viewport Transformation )

5. 스캔 변환 ( Scan Transformation )

 

클리핑 ( Clipping )

Vertex Shader의 마지막 단계에서 절두체를 완전히 벗어나는 폴리곤은 버려졌지만, 경계에 걸친 폴리곤들은 그대로 있기 때문에, 해당 폴리곤들을 잘라내어, 절두체 외부 영역은 버리는 단계이다.

 

원근 분할 ( Perspective Division )

클립 공간 좌표의 모든 요소를 W값으로 나누게 되는데, 이를 통해 모든 원근법 구현이 완료되며, 이를 원근 분할이라고 한다. 여기서 원근 분할을 마친 좌표계를 NDC ( Normalized Device Coordinates )라고 한다. NDC ( Normalized Device Coordinates )는 X, Y 좌표가 모두 -1 ~ 1, Z 좌표는 0 ~ 1에 위치하는 좌표계이고, 스크린 좌표로 쉽게 변환할 수 있도록 하기 위한 변환상의 마지막 좌표계이다. 클립 공간의 ( x,y,z )를 w로 나눈 결과이다.

 

후면 컬링 ( Back-face Culling )

View 벡터와 Normal 벡터의 관계를 통해 후면을 찾아내어, 렌더링되지 않도록 면을 제거한다. 

 

뷰포트 변환 ( Viewport Transformation )

NDC 공간 상의 좌표를 2D 스크린 좌표로 변환한다. -1 ~ 1 범위인 X,Y 좌표를 화면 해상도 범위로 변환한다. 2D공간으로 변환한다고 하지만, 실제로는 Z값을 깊이 값으로 사용하기 위해 그대로 유지한다.

 

스캔 변환 ( Scan Transformation )

프리미티브를 통해 프래그먼트를 생성하고, 프래그먼트를 채우는 픽셀들을 찾아낸다. 각 픽셀마다 정점 데이터들을 보간하여 할당한다.

 

픽셀 ( 프래그먼트 ) 쉐이더 ( Pixel Shader )

DirectX에서는 픽셀 쉐이더, OpenGL, 유니티 엔진에서는 프래그먼트 쉐이더 ( Fragment Shader ) 라고 부른다. 픽셀 쉐이더는 모델이 화면에서 차지하는 픽셀의 갯수만큼 실행된다. 쉐이더를 통해 색상을 변화시키는 것은 모두 픽셀 쉐이더의 역할이라고 볼 수 있다. 투명도를 결정하고, 라이팅과 그림자 적용, 텍스쳐 색상을 메시에 입히는 것도 모두 픽셀 쉐이더의 역할이다. 픽셀 쉐이더는 각 픽셀들의 색상과 깊이 값을 출력으로 전달한다. 깊이 값은 Z-Buffer, 색상 값은 Color Buffer에 저장된다. 이런 버퍼들을 스크린 버퍼라고 한다.

 

출력 병합 ( Output Merger )

픽셀 쉐이더가 생성한 픽셀 단편들을 출력하는 단계이다. 픽셀들을 화면에 출력하기 위한 마지막 연산 ( Z-Test, Stencil Test, Alpha Blending )들을 수행한다. 각각 픽셀 위치마다 여러 오브젝트의 픽셀이 겹쳐 있을 수 있다. 해당 Output Merger단게에서는 이렇게 겹치는 픽셀들을 연산 및 판단하여 픽셀의 최종적인 색상을 결정한다.

 

자료참조 : https://rito15.github.io/posts/rendering-pipeline/

 

렌더링 파이프라인 간단 정리

렌더링 파이프라인 구조 요약(DirectX 9 기준)

rito15.github.io