코가손의 블로그

[DirectX12] 렌더링 파이프라인 본문

GameDev/DirectX12

[DirectX12] 렌더링 파이프라인

Cogason 2021. 11. 23. 16:16

렌더링 파이프라인은 3차원 장면의 기하학적 서술과 가상 카메라의 위치 및 방향이 주어졌을 때 현재 가상 카메라에 비친 3차원 장면의 모습에 근거해서 2차원 이미지를 생성하는 데 필요한 일련의 모든 과정이다.

 

 

입력 조립기 단계(Input assembler)

입력 조립기 단계는 메모리에서 기하 자료(정점, 색인)를 읽어서 기하학적 기본도형(삼각형, 선분, 점 등)을 조립한다.

 

 Direct3D의 정점은 공간적 위치 의외의 정보도 담을 수 있다.

예를 들어, 조명을 구현하기 위한 법선 벡터나 텍스처 적용을 위해 텍스처 좌표를 추가할 수 있다.

 

정점

 정점들은 정점 버퍼(vertex buffer)라고 하는 특별한 자료구조 안에 담겨서 렌더링 파이프라인에 묶인다(bind).

정점 버퍼에는 정점들이 연속적인 메모리에 저장되어 있으며 정점이 어떻게 사용되야 하는지의 정보(두 개, 세 개씩 사용해서 삼각형을 형성해야 하는지)는 들어있지 않다.

 

기본도형 위상구조

 정점 자료를 이용해서 기하학적 기본도형을 형성하는 방법을 Direct3D에 알려주려면 기본도형 위상구조(primitive topology)라는 것을 설정해야 한다.

void ID3D12GraphicsCommandList::IASetPrimitiveTopology(
    D3D_PRIMITIVE_TOPOLOGY Topology;
    
typedef enum D3D11_PRIMITIVE_TOPOLOGY { 
  D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED                   = 0,
  D3D11_PRIMITIVE_TOPOLOGY_POINTLIST                   = 1,
  D3D11_PRIMITIVE_TOPOLOGY_LINELIST                    = 2,
  D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP                   = 3,
  D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST                = 4,
  D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP               = 5,
  D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ                = 10,
  D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ               = 11,
  D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ            = 12,
  D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ           = 13,
  D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST   = 33,
  D3D11_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST   = 34,
  D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST   = 35,
  D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST   = 36,
  D3D11_PRIMITIVE_TOPOLOGY_5_CONTROL_POINT_PATCHLIST   = 37,
  D3D11_PRIMITIVE_TOPOLOGY_6_CONTROL_POINT_PATCHLIST   = 38,
  D3D11_PRIMITIVE_TOPOLOGY_7_CONTROL_POINT_PATCHLIST   = 39,
  D3D11_PRIMITIVE_TOPOLOGY_8_CONTROL_POINT_PATCHLIST   = 40,
  D3D11_PRIMITIVE_TOPOLOGY_9_CONTROL_POINT_PATCHLIST   = 41,
  D3D11_PRIMITIVE_TOPOLOGY_10_CONTROL_POINT_PATCHLIST  = 42,
  D3D11_PRIMITIVE_TOPOLOGY_11_CONTROL_POINT_PATCHLIST  = 43,
  D3D11_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST  = 44,
  D3D11_PRIMITIVE_TOPOLOGY_13_CONTROL_POINT_PATCHLIST  = 45,
  D3D11_PRIMITIVE_TOPOLOGY_14_CONTROL_POINT_PATCHLIST  = 46,
  D3D11_PRIMITIVE_TOPOLOGY_15_CONTROL_POINT_PATCHLIST  = 47,
  D3D11_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST  = 48,
  D3D11_PRIMITIVE_TOPOLOGY_17_CONTROL_POINT_PATCHLIST  = 49,
  D3D11_PRIMITIVE_TOPOLOGY_18_CONTROL_POINT_PATCHLIST  = 50,
  D3D11_PRIMITIVE_TOPOLOGY_19_CONTROL_POINT_PATCHLIST  = 51,
  D3D11_PRIMITIVE_TOPOLOGY_20_CONTROL_POINT_PATCHLIST  = 52,
  D3D11_PRIMITIVE_TOPOLOGY_21_CONTROL_POINT_PATCHLIST  = 53,
  D3D11_PRIMITIVE_TOPOLOGY_22_CONTROL_POINT_PATCHLIST  = 54,
  D3D11_PRIMITIVE_TOPOLOGY_23_CONTROL_POINT_PATCHLIST  = 55,
  D3D11_PRIMITIVE_TOPOLOGY_24_CONTROL_POINT_PATCHLIST  = 56,
  D3D11_PRIMITIVE_TOPOLOGY_25_CONTROL_POINT_PATCHLIST  = 57,
  D3D11_PRIMITIVE_TOPOLOGY_26_CONTROL_POINT_PATCHLIST  = 58,
  D3D11_PRIMITIVE_TOPOLOGY_27_CONTROL_POINT_PATCHLIST  = 59,
  D3D11_PRIMITIVE_TOPOLOGY_28_CONTROL_POINT_PATCHLIST  = 60,
  D3D11_PRIMITIVE_TOPOLOGY_29_CONTROL_POINT_PATCHLIST  = 61,
  D3D11_PRIMITIVE_TOPOLOGY_30_CONTROL_POINT_PATCHLIST  = 62,
  D3D11_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST  = 63,
  D3D11_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST  = 64
} D3D11_PRIMITIVE_TOPOLOGY;

 

모든 그리기 호출은 현재 설정된 기하구조 위상구조를 사용한다(명령 목록을 통해 다른 위상구조가 설정될 때까지는)

mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST);
/*...선 목록을 적용해서 물체들을 그린다...*/

mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST);
/*...삼각형 목록을 적용해서 물체들을 그린다...*/

mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST);
/*...삼각형 띠를 적용해서 물체들을 그린다...*/

몇몇 예외는 있지만 거의 항상 삼각형 목록(triangle list)을 사용한다.

기본 형식에 대한 꼭짓점 순서

 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST를 지정하면 삼각형 목록이 설정된다.

삼각형 목록이 설정된 상태에서 그리기 호출의 매 정점 세 개가 하나의 삼각형을 형성한다.

따라서 3n개의 정점으로 n개의 삼각형이 만들어진다.

삼각형이 모두 연결되는 삼각형 띠와는 달리, 삼각형 목록으로는 따로 떨어진 삼각형들을 형성할 수 있다.

 

색인(Index)

 3차원 물체의 기본 구축 요소는 삼각형이다. 삼각형의 정점들을 지정하는 순서가 중요한데, 이를 감기 순서(winding order)라고 부른다. 

 

왼쪽은 인덱싱을 하지 않음, 오른쪽은 인덱싱을 사용

 평행사변형을 그리기 위해서는 삼각형 2개 [v0, v1, v2], [v3, v4, v5] 가 필요하다.

왼쪽의 평행사변형을 보면 정점 v2와 v3 그리고 v1과 v4가 겹친다. 이 정점들은 정점 버퍼에 들어 있다.

삼각형들은 모형이 세밀하고 복잡해질 수록 더욱 많은 정점들을 공유하게 된다.

정점들의 중복이 비효율적인 이유는 크게 두 가지 이다.

 

  1. 메모리 요구량이 증가한다. 같은 정점 자료를 여러 번 저장 할 필요가 없다
  2. 그래픽 하드웨어의 처리량이 증가한다. 같은 정점 자료를 여러 번 처리할 필요가 없다

 

해결책은 색인(index)을 이용하는 것이다. 색인을 이용하는 색인 목록을 만든다.

색인 목록은 정점이 어떤 순서로 사용해서 삼각형을 형성해야 하는지를 나타낸다.

오른쪽의 평행사변형 처럼 색인을 이용하면 4개의 정점으로 사각형을 만들 수 있다.

 

색인목록을 이용하여 삼각형0 과 삼각형1을 만드는 방법은 다음과 같다

UINT indexList[6] = {0, 1, 2,    // 삼각형 0
                     0, 2, 3};   // 삼각형 1

 

그래픽 카드는 정점 목록의 고유한 정점들을 처리한 후, 색인 목록을 이용해서 정점들을 조합해 삼각형을 형성한다.

정점 목록의 '중복'이 색인 목록으로 옮겨간 셈인데, 별로 문제가 되지 않는다. 이유는 다음 두 가지 이다.

  1. 색인은 그냥 정수이므로 완전한 정점 구조체보다 적은 양의 메모리를 차지한다.
  2. 정점들이 적절한 순서로 캐시에 저장된다면, 그래픽 하드웨어는 중복된 정점들을 너무 자주? 처리할 필요가 없다.

 

정점 셰이더 단계(Vertex Shader)

 입력 조립기 단계는 기본도형들을 조립한 후 해당 정점들을 정점 셰이더 단계로 넘겨준다.

정점 셰이더는 정점 하나를 받아서 정점 하나를 출력하는 과정이다. 화면에 그려질 모든 정점은 이 정점 셰이더를 거쳐 간다. 개념적으로, 하드웨어 안에서 다음과 같은 일이 일어난다고 할 수 있다.

for(UINT i = 0; i < numVertices; ++i)
    outputVertex[i] = VertexShader( inputvertex[i] );

 

 정점 셰이더 함수의 구체적인 내용은 프로그래머가 구현해서 GPU에 제출한다. 그 함수는 각 정점에 대해 GPU에서 실행되기 때문에 아주 빠르다.

 

변환, 조명, 변위 매핑 등 수많은 특수 효과를 정점 셰이더에서 수행할 수 있다. 정점 셰이더에서 입력 정점 자료에 접근하는 것은 물론이고, 텍스처라던가 변환 행렬, 장면 광원 정보 등 GPU메모리에 담긴 다른 자료에도 접근할 수 있다.

 

 

테셀레이션 단계(Tessellation)

테셀레이션은 주어진 메시의 삼각형들을 더 잘게 쪼개서 새로운 삼각형들을 만드는 과정을 말한다. 새 삼각형들을 새로운 위치로 이동함으로써 원래 메시에는 없는 세부적인 특징을 만들어 낼 수 있다.

테셀레이션에는 여러 가지 장점이 있다.

  1.  카메라에 가까운 삼각형들에는 테셀레이션을 적용해서 세부도를 높이고, 먼 삼각형들에는 테셀레이션을 적용하지 않는 방식의 세부수준 메커니즘을 구현할 수 있다. 이렇게 하면 세부 특징을 관찰자가 실제로 볼 수 있는 부분에만 더 많은 삼각형을 사용하게 되므로 효율적이다.
  2.  메모리에는 저다각형(low-poly)메시, 즉 적은 수의 삼각형들로 이루어진 메시를 담아두고 즉석에서 삼각형들을 추가함으로써 메모리를 절약할 수 있다.
  3. 애니메이션이나 물리 처리 같은 연산들을 단순한 저다각형 메시에 대해 수행하고, 테셀레이션된 고다각형 메시는 렌더링에만 사용함으로써 계산량을 줄일 수 있다.

 

기하 셰이더 단계(Geometry shader)

 기하 셰이더 단계는 선택적이다. 기하 셰이더는 하나의 온전한 기본도형을 입력받아서 그것을 임의로 변형한다.

예를 들어 삼각형 목록을 그리는 경우 기하 셰이더에는 삼각형을 정의하는 정점 세 개가 입력된다(이미 정점 셰이더 처리를 거침). 기하 셰이더의 주된 장점은 기하구조를 GPU에서 생성하거나 파괴할 수 있다는 것이다. 예를 들어

입력 기하구조를 여러 개의 기하구조들로 확장할 수도 있고, 조건에 따라 입력 기하구조를 폐기할 즉 다음 단계로 출력하지 않을 수도 있다. 이와는 달리 정점 셰이더는 정점을 생성하지 못한다. 정점 셰이더는 항상 정점 하나를 받아서 정점 하나를 출력한다. 기하 셰이더의 흔한 용도는 점이나 선분을 사각형으로 확장하는 것이다.

 

기하 셰이더의 출력을 스트림 출력 단계를 통해 메모리의 버퍼에 저장해 두고 나중에 활용하는 것이 가능하다.

 

절단

시야 절두체 바깥에 있는 기하구조를 폐기한다. 절단은 Clipping연산이라고 하며 하드웨어가 수행해준다.

 

레스터화 단계(Rasterizer)

레스터화 단계의 주 임무는 투영된3차원 삼각형으로부터 픽셀 색상들을 계산하는 것이다.

뷰포트 변환 > 후면 선별 > 정점 특성의 보간 단계가 있다.

 

뷰포트 변환

절단을 마치고 하드웨어는 원근 나누기를 수행해서 결과를 정규화된 장치 좌표(NDC, 가로2 세로2)로 변환한다. 정점들인 NDC공간으로 들어오면 2차원 x, y좌표성분들이 후면 버퍼의 한 직사각형 영역(뷰포트)으로 변환된다.

뷰포트 변환을 마치게 되면 x, y성분은 픽셀 단위의 값이 된다(z 성분은 깊이 버퍼링에 사용해야 하므로 변경하지 않는다).

 

후면 선별

하나의 삼각형에는 면이 2개 있다. 관찰자에게 보여지는 면을 전면, 그 뒤를 후면이라고 한다. 삼각형의 정점 v0, v1, v2 가 있을 때, 정점이 시계방향으로 감기면 전면이고 반시계방향으로 감기면 후면이다. 후면 삼각형들은 그려질 필요가 없기 때문에 후면 삼각형들을 골라서 폐기하는 공정을 후면 선별이라고 한다.

 

정점 특성의 보간

삼각형은 정점들로 정의된다. 정점 자료에는 위치 정보뿐만 아니라 법선 벡터, 텍스처 좌표 같은 추가적인 특성을 붙일 수 있다. 뷰포트 변환을 거친 후에는 그러한 특성들을 삼각형을 덮는 각 픽셀에 대해 보간해야 한다. 또한 정점의 깊이 값도 그런 식으로 보간해서, 각 픽셀에 깊이 버퍼링 알고리즘을 위한 깊이 값을 부여해야 한다. 정점 특성 보간 덕분에 삼각형 내부 픽셀들을 위한 값들을 정점에 부착된 값들로부터 계산할 수 있게 된다.

 

픽셀 셰이더 단계(Pixel shader)

픽셀 셰이더는 프로그래머가 작성하고 GPU가 실행하는 프로그램이다.

픽셀 셰이더는 각각의 픽셀 단편(pixel fragment)에 대해 실행된다. 기본적으로 보간된 정점 특성들을 입력받아서 하나의 색상을 출력한다. 픽셀 셰이더는 고정된 상수 색상을 돌려주는 아주 간단한 것에서부터 픽셀당 조명, 반사, 그림자 효과 등 좀 더 복잡한 작업을 수행하는 것에 이르기까지 다양하다.

 

출력 병합기 단계(Output merger)

픽셀 셰이더가 생성한 픽셀 단편들은 렌더링 파이프라인의 출력 병합기 단계로 입력된다. 이 단계에서 일부 픽셀 단편들이 기각될 수 있다(깊이 판정, 스텐실 판정에 의해). 기각되지 않은 픽셀 단편들은 후면 버퍼에 기록된다. 혼합(blending)도 이 단계에서 일어난다. 혼합이란, 새 픽셀이 후면 버퍼의 기존 픽셀을 완전히 덮어쓰는 것이 아니라 두 픽셀을 일정한 공식에 따라 섞은 결과를 기록하는 것을 말한다. 혼합은 반투명 같은 특수 효과를 내는 데 쓰인다.

'GameDev > DirectX12' 카테고리의 다른 글

[DirectX12] 텍스처 형식  (0) 2021.11.16
[DirectX12] COM(Component Object Model)  (0) 2021.11.15
Comments