DirectX12

렌더링 파이프라인(1) - Input Assembly

띠애모 2024. 7. 16. 15:44

입력 어셈블러(IA) 단계의 목적은 사용자가 채운 버퍼에서 기본 데이터(점, 선 및 삼각형)를 읽고 다른 파이프라인 단계에서 사용할 기본 데이터로 데이터를 조립하고, 셰이더를 더 효율적으로 만드는 데 도움이 되는 시스템 생성 값을 첨부 하는 것입니다. 

 

하나의 메시가 픽셀이 되기까지 첫 번째 단계가 Input Assembly 단계이다.

메시의 각 VERTEX 정보와 INDEX 정보를 V.REM 에 저장하는 과정이다

 

  • 정점 버퍼는 위치, Normal, Color, UV등을 담고 있는데, 직렬화된 형태(배열)로 담고 있다.
  • GPU에서 전달 받은 정점 버퍼를, 미리 전달 받은 렌더 상태의 Vertex 명세를 통해
    정점 데이터(구조체)로 조립한다.
  • Direct 3D에서는 Primitive Topology라는 형태로 정점이 표현하는 기본 도형을 정의한다.
  • 정의해 준 기본 도형으로 정점을 모아 조립해준다. 그렇기 때문에 입력 조립 단계라고도 한다.

메모리에 사용자가 채워놓은 기본 데이터를
다른 파이프라인 단계에서 사용하기 위한 기본 형식으로 조립한다.

 

정점 버퍼와 같이 등장하는 용어로 인덱스 버퍼(Index Buffer)라는 것이 있다. 이 인덱스 버퍼는 쉽게 생각하면 정점들의 인덱스를 저장하고 있는 버퍼라 할 수 있는데 사각형을 예를 들어 생각 해보자. 사각형 하나를 그리기 위해서 이것을 최소 단위의 도형(삼각형)으로 쪼개면 삼각형 두 개가 생긴다. 그렇다면 이 삼각형 두 개를 그리기 위해서는 당연히 2*3 =6 개의 정점이 필요 할 것이다. 그런데 실제로 사각형은 몇 개의 점으로 구성되어 있는가?

 

 


4개의 점으로 구성되어 있다.
6개의 정점으로 사각형을 표현한다면 2개의 점이 중복되어 메모리 낭비가 된다고 할 수 있다.만약 사각형 하나가 아니라 사각형 10,000개로 구성된 모델이라고 가정하면 20000개의 정점이 낭비되는 것이고 이것은 엄청낭 손해이다! 또한 단순한 메모리의 낭비일 뿐만 아니라 같은 정점에 대한 작업을 GPU에서 여러번 처리해야 하기 때문에 불필요한 계산량까지 늘어난다.

 
이러한 중복되는 정점을 해결하기 위한 방법이 바로 Index Buffer이다. 위의 사각형의 예를 들면 정점 버퍼는 Vertex Bufer[] = { P0,P1,P2,P3} 이렇게 4개의 데이터만 갖고, 인덱스 버퍼를 추가로 생성하여 Index Buffer[] = {0, 2, 3, 1} 요런식으로 정점을 어떤 순서로 그려야 하는지 알려주는 목록을 제공하면 위에서 설명한 두 가지 문제를 해결할 수 있다. 인덱스 버퍼를 생성하는데에도 메모리가 드는데 이것은 낭비가 아니냐고 생각 할 수도 있겠지만

인덱스버퍼는 그냥 정수이기 때문에 위치,색깔,법선,UV등등의 많은 데이터를 갖는 정점 구조체보다 훨씬 메모리를 적게 차지하기 때문에 별 문제가 되지 않는데다가
중복되는 정점에 대해서 중복된 GPU 계산을 하지 않는다는 메리트가 굉장히 크기 때문에 인덱스 버퍼에 소요되는 메모리는 걱정하지 않아도 된다.

 

[정점 버퍼]

자 다시 이제 정점버퍼로 돌아와서 설명하겠습니다.
정점 버퍼는 그냥 정점들을 연속적인 메모리에 저장하는 자료구조에 불과하기 때문에 실제로 GPU에서는 이러한 정점들을 이용하여 어떤 도형을 만들어야 할 지 정보가 필요합니다. 예를 들면 이 정점을 두 개씩 엮어서 선분을 구성할 수도 있고, 세 개씩 엮어서 삼각형을 구성할 수도 있죠. 이러한 기본도형을 형성하는 방식을 알려주는데 쓰이는 수단으로 Direct3D에서는 Primitive Topology라는 형태를 이용합니다. 이 프리미티브 토폴로지는 해석하자면 “위상 구조”라 할 수 있는데 DX11 에서는 point list, line list, triangle strip, triangle list 등이 있습니다. 여기서 Triangle list를 선택하여 기본도형을 삼각형으로 선택했다고 가정 해 봅시다. 그러면 이제 모델 하나를 형성하기 위해서는 GPU에서 수많은 삼각형들을 이어 붙이기만 하면 되는 거죠.
입력 조립기 단계(Input Assemble) 에서는 이런 정점들을 읽어서 삼각형과 같은 기본 도형으로 조립하는 일을 합니다. 그래서 입력 조립 단계이다.

 

기본도형 위상구조(Primitive topology)

입력 조립기에서는 정점의 정보를 받아서 하나 이상의 도형 즉, 하나 이상의 삼각형을 만드는게 목적이라고 했다.

어떤 과정을 통해서 삼각형을 만들 수 있을까

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef enum D3D_PRIMITIVE_TOPOLOGY {
  D3D_PRIMITIVE_TOPOLOGY_UNDEFINED = 0,
  D3D_PRIMITIVE_TOPOLOGY_POINTLIST = 1,
  D3D_PRIMITIVE_TOPOLOGY_LINELIST = 2,
  D3D_PRIMITIVE_TOPOLOGY_LINESTRIP = 3,
  D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST = 4,
  D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5,
  D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = 10,
  D3D_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = 11,
  D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = 12,
  D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = 13,
  D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST = 33,
  ...
} ;
cs

위 구조체는 Direct3D에서 사용되는 Promitive 열거형의 일부이다.
우리는 이 열거형 중 하나를 사용해서 삼각형을 그릴 정보를 GPU에게 알려주게 된다.
그 세팅을 해 주는게 IASetPromitiveTopology()함수이다.
이 곳에 열거형 중 하나를 선택해 입력하면 GPU는 그 정보를 토대로 정점들을 이어나간다.

 

점 목록(Point List)

D3D_PRIMITIVE_TOPOLOGY_POINTLIST를 사용하면 개별적인 점의 형태로 정점을 그려낸다.

선 띠 (Line Strip)

D3D_PRIMITIVE_TOPOLOGY_LINESTRIP을 사용하면 선 띠 또는 선분 띠로 설정되며 정점들은 차례로 연결된 선분들을 형성한다.

선 목록 (Line List)

D3D_PRIMITIVE_TOPOLOGY_LINELIST를 선택하면 정점 두 개가 하나의 선분을 형성한다.
선 띠는 선분들이 자동으로 연결된다고 가정되는데 선 목록은 분리된 선분들을 만들어낸다.

삼각형 띠(Triangle Strip)

D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP을 사용하면 삼각형 띠가 설정되며 모든 삼각형들이 연결되어 표현되며 결과적으로 n개의 정점으로 n - 2개의 삼각형이 만들어진다.
여기서 중요한건 짝수 번째 삼각형과 홀수 번째 삼각형의 감기는 순서가 다르다는 것인데 이 문제는 나중에 후면 선별 시 문제가 발생할 수 있기 때문에 GPU내부적으로 짝수 번째 삼각형의 처음 두 정점의 순서를 바꿔 감기는 순서가 동일하게 맞춘다.

삼각형 목록 (Triangle List)

D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST는 정점 세 개가 하나의 삼각형을 형성하는 형태이다.

 

색인(Index)

입력 조립기에선 정점을 이용해 도형을 만들고, 그 도형을 만드는 방식을 설정한다고 말했다.
그러면 그 도형을 만드는 순서 즉, 어느 정점에서 시작해서 어느 정점이 마지막이라는 그 순서는 어떻게 정할까?

그 그리는 순서를 정해주는게 인덱스(Index)다.

스쳐가듯 말 했는데 이 삼각형이 그려지는 순서는 아주 중요하다. 이 감기는 순서에 따라서 이 삼각형이 뒷면인지, 앞면인지를 판별하는데 만약 뒷면이면 레스터라이저 단계에서 해당 부분은 렌더하지 않는다.

index를 설정하는 방법중 directX 는 반 시계 방향을 채택하고 있다.