Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

codingfarm

6. Direct3D의 그리기 연산 - 색인과 색인 버퍼 본문

computer graphics/DX12 book

6. Direct3D의 그리기 연산 - 색인과 색인 버퍼

scarecrow1992 2021. 6. 6. 01:37

앞서 버퍼는 GPU가 접근 가능한 GPU 자원(ID3D12Resource) 공간으로 저장했다.

이 버퍼를 통해 GPU는 vertex 정보 접근할 수 있으며, GPU가 색인(index)들의 배열에 접근할 수 있으려면 색인들의 버퍼 GPU자원(ID3D12Resource)에 넣어 두어야 한다.

index를 담는 버퍼를 index buffer라 부른다.

 

Index Buffer

앞서 우리는 vertex buffer를 이용하여 화면에 삼각형 mesh를 출력하는 방법을 배웠다.

하지만 이전 코드를 보면 동일한 vertex가 여러번 선언되는것을 볼 수 있으며, 이는 프로그램의 효율을 떨어뜨린다.

그러므로 필요한 vertex의 목록을 만든뒤, 필요할때마다 해당 vertex의 index를 이용해 간접 참조하는 방식으로 효율성을 높일 수 있다.

이것이 index buffer의 주 목적이다.

 

vertex buffer와 마찬가지로 예제 프레임워크의 d3dUtil::CreateDefaultBffer 함수로 index buffer의 생성이 가능하다.

index buffer를 pipeline에 binding 하는 절차는 vertex buffer를 binding 하는것과 큰차이가 없다

  1. index buffer를 생성한다.
  2. index buffer view를 생성한다.
  3. pipeline에 binding 한다.

 

1. Index Buffer

index buffer 또한 ID3D12Device::CreateCommittedResource 메서드로 생성 가능하며, D3DUtil::CreateDefaultBuffer 메서드로 생략과정을 간략화 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
std::array<std::uint16_t, 36> indices =
{
    // front face
    012,
    023,
 
    // back face
    465,
    476,
 
    // left face
    451,
    410,
 
    // right face
    326,
    367,
 
    // top face
    156,
    162,
 
    // bottom face
    403,
    437
};
 
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
 
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
 
IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
        mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);
cs

 

 

2. Index Buffer View

  • D3D12_INDEX_BUFFER_VIEW로 대표됨
1
2
3
4
5
typedef struct D3D12_INDEX_BUFFER_VIEW {
  D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
  UINT                      SizeInBytes;
  DXGI_FORMAT               Format;
} D3D12_INDEX_BUFFER_VIEW;
cs

1. D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;

  • 생설할 view의 대상이 되는 index buffer 자원의 가상 주소
  • ID3D12Resource::GetGPUVirtualAddress 메서드로 얻을 수 있다.

 

2. UINT SizeInBytes;

  • BufferLocation에서 시작하는 index buffer의 크기(byte 갯수)

 

3. DXGI_FORMAT Format;

  • 색인의 형식
  • 16비트 index면 DXGI_FORMAT_R16_UINT를, 32비트 index면 DXGI_FORMAT_R32_UINT를 지정해야 함

 

 

3. Binding

  • index buffer는 ID3D12CommandList::SetIndexBuffer 메서드를 통해서 입력 조립기 단계(IA)에 binding 됨
    • 여러개의 vertex buffer view 들을 binding 하는 D3D12GraphicsCommandList::IASetVertexBuffers 와는 달리 하나의 descriptor만 binding 가능하다.

다음 코드는 

  1. 한 입방체의 삼각형들을 정의하는 index buffer를 하나 생성함
  2. 그에 대한 view를 생성함
  3. view를 pipeline에 binding 함

위의 절차를 코드로 표현하면 아래와 같다

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
std::array<std::uint16_t, 36> indices =
{
    // front face
    012,
    023,
 
    // back face
    465,
    476,
 
    // left face
    451,
    410,
 
    // right face
    326,
    367,
 
    // top face
    156,
    162,
 
    // bottom face
    403,
    437
};
 
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
 
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
 
// 1. index buffer 생성
IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
    mCommandList.Get(), indices.data(), ibByteSize, IndexBufferUploader);
 
// 2. index buffer view 생성
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = DXGI_FORMAT_R16_UINT;
ibv.SizeInBytes = ibByteSize;
 
// 3. binding
mCommandList->IASetIndexBuffer(&ibv);
 
// 4. indexed 그리기 명령 
mCommandList->DrawIndexedInstanced(
    indices.size(), 
    1000);
cs

명령제출은 바로 밑에서 배우겠다.

 

명령 제출

index buffer를 이용해 기본도형을 그리려면 DrawIndexedInstanced 메서드를 호출해야한다.

1
2
3
4
5
6
7
void DrawIndexedInstanced(
 UINT IndexCountPerInstance,
  UINT InstanceCount,
  UINT StartIndexLocation,
  INT  BaseVertexLocation,
  UINT StartInstanceLocation
);
cs

1. UINT IndexCountPerInstance;

  • 그리기에 사용할 색인들의 갯수(인스턴스당)

 

2. UINT InstanceCount;

  • 그릴 인스턴스 갯수
  • 인스턴싱이라 불리는 고급 기법에서 사용 (지금은 1로 설정)

 

3. UINT StartIndexLocation;

  • 색인 버퍼의 색인들 중 이 그리기 호출에서 사용할 첫 index의 index(0 기반)

 

4. INT BaseVertexLocation;

  • 이 그리기 호출에 쓰이는 index들에 더할 정수 값, 더한 결과를 최종 색인으로 사용해서 정점 버퍼에서 정점을 가져온다.

 

5. UINT StartInstanceLocation;

  • 고급 기법인 인스턴싱에 쓰임(지금은 0으로 설정)

 

이해를 돕기위해 다음과 같은 상황을 예로 들겠다.

구, 직육면체, 원기둥으로 이루어진 장면을 그려야한다. 처음에는 세 물체에 각자 개별적인 정점 버퍼와 색인 버퍼가 있다. 각 local index buff의 index들은 그에 해당하는 local vertex buffer를 기준으로 한다. 그러나 실제로 장면을 그릴 때에는 아래 그림처럼 구, 상자, 원기둥의 정점들과 색인들을 하나의 global vertex buffer와 하나의 index buffer로 합치게된다.

위처럼 버퍼의 병합이 이루어지면 본래 개별 vertex buffer 기준으로 작성되었던 index buffer의 index들은 잘못된 지점을 가리키게된다.

가령 상자 index들은 상자 vertex들의 색인이 다음과 같다는 가정하에 계산된것이다.

$0, 1, \cdots, numBoxVertices - 1$

하지만 위 그림처럼 병합된 이후에는 상자 정점들의 실제 index가 아래처럼 된다.

$firstBoxVertexPos, firstBoxVertexPos + 1, \cdots, firstBoxVertexPos + numBoxVertices-1$

 

따라서 global vertex buffer에 맞게 index들을 다시 계산해야한다.

그저 모든 상자 색인에 firstBoxVertexPos를 그리고 모든 원기둥 색인에는 firstCylVertexPos를 더해야 한다.

기준 정점 위치(base vertex location) : global vertex buffer에서 한 물체의 첫 번째 vertex의 위치

한 물체의 새 index들은 각 index들은 각 index에 해당 기준 정점 위치를 더한것이다.

하지만 이러한 색인 갱신 작업을 CPU에서 직접 수행할 필요는 없다. DrawIndexedInstance의 넷째 매개변수로 기준 정점 위치를 지정하면 Direct3D가 처리해준다.

아래는 구와 상자, 원기둥을 그리기 위해 command list에 draw index 명령을 연달아 제출하는 사용법이다.

1
2
3
4
5
6
mCmdList->DrawIndexedInstanced(
    numSphereIndices, 1000);
mCmdList->DrawIndexedInstanced(
    numBoxIndices, 1, firstBoxIndex, firstBoxVertexPos, 0);
mCmdList->DrawIndexedInstanced(
    numCylIndices, 1, firstCylIndex, firstCylVertexPos, 0);
cs

3, 4번째 매개변수에 들어갈 값을 구하는법은 추후 알아보겠다.

 


vertex buffer와 index buffer를 함께 사용하는 방법은 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
struct Vertex{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
 
typedef struct D3D12_VERTEX_BUFFER_VIEW {
  D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
  UINT                      SizeInBytes;
  UINT                      StrideInBytes;
} D3D12_VERTEX_BUFFER_VIEW;
 
std::array<Vertex, 8> org_vertices = {
    Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) }),
    Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) }),
    Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) }),
    Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) }),
    Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) }),
    Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) }),
    Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) }),
    Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) })
};
 
std::array<std::uint16_t, 36> indices =
{
    // front face
    012,
    023,
 
    // back face
    465,
    476,
 
    // left face
    451,
    410,
 
    // right face
    326,
    367,
 
    // top face
    156,
    162,
 
    // bottom face
    403,
    437
};
 
const UINT vbByteSize = (UINTvertices.size() * sizeof(Vertex); 
ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
 
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
 
// 1. buffer 생성
VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
        mCommandList.Get(), vertices, vbByteSize, VertexBufferUploader);
 
IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
    mCommandList.Get(), indices.data(), ibByteSize, IndexBufferUploader);
 
UINT VertexByteStride = sizeof(Vertex);
UINT VertexBufferByteSize = (UINT)vertices.size() * sizeof(Vertex);
 
// 2. descriptor 생성
D3D12_VERTEX_BUFFER_VIEW vbv; 
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = VertexByteStride;
vbv.SizeInBytes = VertexBufferByteSize;
 
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = DXGI_FORMAT_R16_UINT;
ibv.SizeInBytes = ibByteSize;
 
// 3. binding
mCommandList->IASetVertexBuffers(01&vbv);
mCommandList->IASetIndexBuffer(&ibv);
 
// 4. indexed 그리기 명령 제출
mCommandList->DrawIndexedInstanced(
    indices.size(), 
    1000);
 
// 5. 기본 도형 위상 구조 설정    
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cs

 

 

Comments