Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Archives
Today
Total
관리 메뉴

codingfarm

16. 정점 버퍼(Vertex Buffer) 본문

computer graphics/DirectX12

16. 정점 버퍼(Vertex Buffer)

scarecrow1992 2021. 7. 4. 19:39

0. 개요

단순히 정점버퍼에 대해 뿐만 아니라, GPU에서 이를 활용하기 위한 여러가지 작업에 대해서도 알아보겠다.

vertex buffer : GPU가 접근가능한 버퍼에 저장된 정점정보

 

정점 버퍼 및 뷰의 생성

앞서 배웠던 리소스의 생성과정과 동일하되. 추가적으로 해야할 작업들이 더있다.

 

1. 커스텀 정점형식 정의

  • Vertex 구조체를 정의한다.
  • 입력 배치 서술을 작성한다

 

2. 버퍼 생성

  •  D3D12_RESOURCE_DESC 구조체를 정의하여 생성될 자원에 대해 서술한다
  • D3D12_HEAP_PROPERTIES 구조체를 정의하여 자원이 저장될 heap에 대해 서술한다
  • D3D12_CLEAR_VALUE 구조체를 정의하여 리소스를 초기화할 값을 결정한다
  • ID3D12Device::CreateCommittedResource를 호출하여 resource를 생성한다.

 

3. 뷰 생성

  • ID3D12_DESCRIPTOR_HEAP_DESC를 정의한다.
  • ID3D12Device::CreateDescriptorHeap를 호출하여 descriptor heap을 생성한다.
  • 생성할 descriptor에 대한 D3D12_~_VIEW_DESC 구조체를 정의한다.
  • descriptor heap의 handle과 view_desc, buffer를 참조하여 Create~~를 호출한다. 

D3D12_VERTEX_BUFFER_VIEW 인스턴스 생성 후 view가 서술할 버퍼의 정보를 참조

vertex buffer view는 생성함수의 호출없이 생성 가능하다

또한 vertex buffer view는 별도의 descriptor heap에 저장되지 않는다. 그렇기에 ID3D12_DESCRIPTOR_HEAP_DESC를 정의할 필요가 없다.

 

3가지 각각의 과정에 대해 상세히 알아보자

 

1. 생성법

1-1. 커스텀 정점 형식(Custom Vertex Format)

GPU에서 정점 정보에 접근하기 위해선 정점에 대해 서술할 정점 구조체를 정의하고, 정점 구조체의 각 성분을 GPU에게 알리기 위한 입력 배치 서술을 작성해야한다.

  1. 정점 구조체를 정의한다
  2. 입력 배치 서술(input layout description)을 작성한다.

 

1-1-1. 정점 구조체

  • 정의 : 정점에 대해 서술할 data를 담는 구조체
  • 구성 : 정점의 공간적 위치 + 추가적인 자료(color, normal vector, texture...)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 위치 + 색상
struct Vertex1 {
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
 
// 위치 + 노말 벡터 + 2개의 
struct Vertex2 {
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex0;
    XMFLOAT2 Tex1;
};
cs

 

 

1-1-2. 입력 배치 서술

  • 정점 구조체의 각 성분에 대해 direct3D에게 통보
  • 하나의 정점 구조체는  D3D12_INPUT_LAYOUT_DESC로 대표됨
    • D3D12_GRAPHICS_PIPELINE_STATE_DESC 의 필드인 InputLayout의 type
    • PSO 생성시 Vertex 구조체의 필드 정보를 서술하는 D3D12_INPUT_ELEMENT_DESC를 pipeline에 묶을 수 있게 한다.
  • 정점 구조체의 필드들은 D3D12_INPUT_ELEMENT_DESC 형식의 배열 원소들로 각각 서술
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct D3D12_INPUT_LAYOUT_DESC {
  const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs; // array
 UINT                           NumElements;         // size of array
} D3D12_INPUT_LAYOUT_DESC;
 
 
typedef struct D3D12_INPUT_ELEMENT_DESC {
  LPCSTR                     SemanticName;
  UINT                       SemanticIndex;
  DXGI_FORMAT                Format;
  UINT                       InputSlot;
  UINT                       AlignedByteOffset;
  D3D12_INPUT_CLASSIFICATION InputSlotClass;
  UINT                       InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;
cs

D3D12_INPUT_LAYOUT_DESC 매개변수

  • NumElements : D3D12_INPUT_LAYOUT_DESC가 서술하고자 하는 vertex struct의 멤버 갯수
  • pInputElementDescs : vertex struct의 각 멤버에 대해 서술하는 서술구조체, 타입이 D3D12_INPUT_ELEMENT_DESC 이다. 

 

 

D3D12_INPUT_ELEMENT_DESC 

 

사용 예

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
// engine code
 
struct Vertex {
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex0;
    XMFLOAT2 Tex1;
};
 
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
 
Microsoft::WRL::ComPtr<ID3D12PipelineState> mPSO = nullptr;
 
 
void InitVertexBuffer() {
    ...
 
    mInputLayout = {   
    {"POSITION"0, DXGI_FORMAT_R32G32B32_FLOAT, 00,
        D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
    {"NORMAL"0, DXGI_FORMAT_R32G32B32_FLOAT, 012,
        D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
    {"TEXCOORD"0, DXGI_FORMAT_R32G32_FLOAT, 024,
        D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
    {"TEXCOORD"1, DXGI_FORMAT_R32G32_FLOAT, 032,
        D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
    };
 
    ...
}
 
 
void BuildPSO(){
 
    ...
 
 
    D3D12_INPUT_LAYOUT_DESC mInputLayoutDesc;
    mInputLayoutDesc.pInputElementDescs    = mInputLayout.data();
    mInputLayoutDesc.NumElements        = (UINT)mInputLayout.size();
 
    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
    ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
    psoDesc.InputLayout = mInputLayoutDesc;
    // psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
 
    ...
 
}
 
 
 
// HLSL code
VertexOut VS(float3 iPos : POSITION,
             float3 iNormal : NORMAL,
            float2 iTex0 : TEXCOORD0,
            float2 iTex1 : TEXCOORD1)
cs

InitVertexBuffer 함수 내에서 생성된 입력 배치 서술(mInputLayout)이 PSO를 생성하는 함수(BuildPSO)내에서 psoDesc의 필드에 결속되는것을 볼 수 있다. 그리고 추후 이 desc 구조체를 통해 생성된 PSO를 통해 파이프라인은 vertex buffer를 읽어내는 방법을 알아낼 수 있다.

즉, 이시점에서 D3D12_INPUT_LAYOUT_DESC는 vertex struct의 형태에 대한 정보만 담고 있을 뿐, Vertex struct 자체와는 엮이지는 않았다

 

 

1-2. 정점 버퍼 생성

vertex buffer또한 resource 이므로 여타 다른 버퍼들과 다르지 않다.

  1.  D3D12_RESOURCE_DESC 구조체를 정의하여 생성될 자원에 대해 서술한다
  2. D3D12_HEAP_PROPERTIES 구조체를 정의하여 자원이 저장될 heap에 대해 서술한다
  3. D3D12_CLEAR_VALUE 구조체를 정의하여 리소스를 초기화할 값을 결정한다
  4. ID3D12Device::CreateCommittedResource를 호출하여 resource를 생성한다.

예제 코드는 아래와 같다

1
2
3
4
5
6
7
8
9
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
 
ThrowIfFailed(md3dDevice.Get()->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(vbByteSize),
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
       IID_PPV_ARGS(VertexBufferGPU.GetAddressOf())));
cs

heap properties와 resource desc 구조체의 정의는 wrapper class를 이용했으며, clear value는 nullptr을 전달하여 1,2,3 과정을 최소화 하였다.

 

 

1-3. 정점 버퍼 뷰 생성

vertex buffer를 서술하기위한 descriptor를 만들어보자.

앞서 설명한대로 vertex buffer view의 생성은 여타 리소스들의 descriptor와는 약간 다르다

  1. ID3D12_DESCRIPTOR_HEAP_DESC를 정의한다.
  2. ID3D12Device::CreateDescriptorHeap를 호출하여 descriptor heap을 생성한다.
  3. 생성할 descriptor에 대한 D3D12_~_VIEW_DESC 구조체를 정의한다.
  4. descriptor heap의 handle과 view_desc, buffer를 참조하여 Create~~를 호출한다.

1, 2, 4 의 과정은 할 필요 없다. view를 생성하고 싶다면 그냥 D3D12_VERTEX_BUFFER_VIEW의 인스턴스를 생성 후 각 필드에 버퍼의 정보를 입력 하면 된다.

D3D12_VERTEX_BUFFER_VIEW 의 구조

1
2
3
4
5
typedef struct D3D12_VERTEX_BUFFER_VIEW {
  D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
  UINT                      SizeInBytes;
  UINT                      StrideInBytes;
} D3D12_VERTEX_BUFFER_VIEW;
cs

필드

멤버 의미
1. D3D12_GPU_VIRTUAL_ADDRESS
   BufferLocation;
$\bullet$ 생성할 뷰의 대상이 되는 정점 버퍼 자원의 가상 주소
$\bullet$ 이 주소는 ID3D12Resource::GetGPUVirtualAddress 메서드로 얻을 수 있다.
2. UINT SizeInBytes; $\bullet$ BufferLocation에서 시작하는 정점 버퍼의 크기(바이트 갯수)
3. UINT StrideInBytes; $\bullet$ 버퍼에 담긴 한 정점 원소의 크기(바이트 갯수)

 

 

D3D12_VERTEX_BUFFER_VIEW 생성 예제코드

1
2
3
4
5
6
7
8
9
10
11
12
struct Vertex {
   ...
};

Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
std::array<Vertex, 8> vertices = { ... };
...
 
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.SizeInBytes = (UINT)vertices.size() * sizeof(Vertex);
vbv.StrideInBytes = sizeof(Vertex);
cs

각 필드가 서로 다른 오브젝트의 데이터를 참조함에 주목하라

BufferLocation은 ID3D12Resource의 인스턴스를 가리키며, SizeInBytes 는 Vertex Instance 배열의 크기를 가지고, StrideInBytes 는 Vertex 구조체 자체의 크기를 가진다.

 

 

 

 

2. 사용법

지금까지 구조체를 통해 커스텀 정점 형식을 만들고, 입력 배치를 서술하고, 정점 버퍼를 만들고, 정점 버퍼 뷰 까지 생성하는 과정을 알아보았다.

이제 정점 정보를 GPU에 전달하기 위한 작업들에 대해 알아보았다.

 

2-1. 정점 정보 올리기

정점 정보를 vertex buffer에 올리는 방법에 대해 알아보겠다.

굉장히 다양한 방법이 있지만 여기서는 제일 직관적인 방법을 쓰겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
std::array<Vertex, 8> vertices = { ... };
 
ThrowIfFailed(md3dDevice.Get()->CreateCommittedResource(
    &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
    D3D12_HEAP_FLAG_NONE,
    &CD3DX12_RESOURCE_DESC::Buffer(vbByteSize),
    D3D12_RESOURCE_STATE_GENERIC_READ,
    nullptr,
    IID_PPV_ARGS(VertexBufferGPU.GetAddressOf())));
    
// 생성된 리소스에 vertices의 정보를 넣어야 한다.
// Copy the triangle data to the vertex buffer.
void* vertexDataBuffer = nullptr;
CD3DX12_RANGE vertexReadRange(00); // We do not intend to read from this resource on the CPU.
VertexBufferGPU->Map(0&vertexReadRange, &vertexDataBuffer);
::memcpy(vertexDataBuffer, &vertices[0], vbByteSize);
VertexBufferGPU->Unmap(0, nullptr);
cs

이해를 돕기 위해 CreateComittedResource 함수 호출부 까지 가저왔다.

결국 위 코드가 하는 행위는 vertices의 데이터를 VertexBufferGPU에 copy 하는것이다.

우선 CPU가 리소스에 데이터를 올리기 위해 D3D12_HEAP_TYPE_UPLOAD state로 자원을 생성한다.

그 후, 아래와 같은 구문을 통해 memcpy함수로 vertices의 정보를 버퍼에  복사하면된다.

그러면 resource에 vertices의 정보가 올라가는것이 완료된다.

각 함수들에 대한 정보는 추후 정리하겠다.

 

 


사실 바인딩과 명령제출은 vertex buffer가 아닌 command list가 수행하는 것이지만

vertex buffer가 연관된 부분인 만큼 여기서는 어떤식으로 이루어지는지 정도만 알아보겠다.

 

2-2. 바인딩

  • 정점 버퍼(resource)와 뷰(descriptor)까지 생성했다면, 정점 버퍼 뷰를 pipeline의 한 입력슬롯에 묶을 수(bind) 있다.
  • vertex buffer view의 binding이 끝나면 pipeline의 입력 조립 단계(IA)에 진입함
  • ID3D12GraphicsCommandList::IASetVertexBuffers 메서드로 vertex buffer를 pipeline에 묶을 수 있다.
1
2
3
4
5
6
void ID3D12GraphicsCommandList::IASetVertexBuffers(
  UINT                           StartSlot,
  UINT                           NumViews,
  const D3D12_VERTEX_BUFFER_VIEW *pViews
);
 
cs
필드 설명
1. UINT StartSlot; $\bullet$ 시작 슬롯. 즉, 첫째 vertex buffer를 묶을(binding) 입력 슬롯의 index
$\bullet$ 입력 슬롯은 총 16개이다(0~15)
2. UINT NumViews; $\bullet$ 입력 슬롯들에 묶을(binding) 정점 버퍼 갯수
$\bullet$ 시작 슬롯의 색인이 k이고 묶을 버퍼가 n개 이면, 버퍼들은 입력슬롯 $I_k$,$I_{k+1}$,⋯$I_{k+n−1}$에 묶이게 된다.
3. const
D3D12_VERTEX_BUFFER_VIEW *pViews
$\bullet$ binding될 정점 버퍼 뷰 배열의 첫 원소를 가리키는 포인터

 

메서드 호출 예

1
2
3
4
5
6
7
8
9
10
11
12
13
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
std::array<Vertex, 8> vertices = { ... };
 
...
 
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.SizeInBytes = (UINT)vertices.size() * sizeof(Vertex);
vbv.StrideInBytes = sizeof(Vertex);
 
...
 
mCommandList->IASetVertexBuffers(01&vbv);
cs

 

 

2-3. 명령제출

이제 정점 정보를 pipeline에 공급할 준비가 끝났다.

 ID3D12GraphicsCommandList::DrawInstanced 메서드를 호출하여 command list에 명령을 넣으면된다.

만약 index buffer를 사용한다면 DrawInstanced 대신 DrawIndexedInstanced를 호출하면 된다.

1
2
3
4
5
6
void DrawInstanced(
  UINT VertexCountPerInstance,
  UINT InstanceCount,
  UINT StartVertexLocation,
  UINT StartInstanceLocation
);
cs
멤버 설명
1. UINT VertexCountPerInstance; $\bullet$ 그릴 정점들의 갯수(인스턴스당)
2. UINT InstanceCount; $\bullet$ 그릴 인스턴스 갯수
$\bullet$ 인스턴싱이라는 고급기법에서 쓰임(지금은 1을 설정)
3. UINT StartVertexLocation; $\bullet$ 정점 버퍼에서 이 그리기 호출로 그릴 일련의 정점들 중 첫 정점의 index(0 기반)
4. UINT StartInstanceLocation; $\bullet$ 고급 기법인 인스턴싱에 쓰임(지금은 0을 설정)

 

 

별첨

vertex buffer에 한정된 이야기는 아니지만, 기본 도형 위상 구조를 설정할 수 있어야 한다.

1
2
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 
cs

 

 

 

마무리

pipeline이 정육면체의 vertex 정보를 받는 일련의 과정을 코드로 표현하면 아래와 같다.

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
struct Vertex {
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
 
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
int vertices_size;
 
void Init() {
    // 1. Custom Vertex Format
    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<Vertex, 36> vertices = {
        org_vertices[0], org_vertices[1], org_vertices[2],
        org_vertices[0], org_vertices[2], org_vertices[3],
        org_vertices[4], org_vertices[6], org_vertices[5],
        org_vertices[4], org_vertices[7], org_vertices[6],
        org_vertices[4], org_vertices[5], org_vertices[1],
        org_vertices[4], org_vertices[1], org_vertices[0],
        org_vertices[3], org_vertices[2], org_vertices[6],
        org_vertices[3], org_vertices[6], org_vertices[7],
        org_vertices[1], org_vertices[5], org_vertices[6],
        org_vertices[1], org_vertices[6], org_vertices[2],
        org_vertices[4], org_vertices[0], org_vertices[3],
        org_vertices[4], org_vertices[3], org_vertices[7] };
 
    vertices_size = (UINT)vertices.size();
    
    // 2. 정점 버퍼 생성
    ThrowIfFailed(md3dDevice.Get()->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(vbByteSize),
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(VertexBufferGPU.GetAddressOf())));
    
    // 2a. 정점 정보 업로드
    void* vertexDataBuffer = nullptr;
    CD3DX12_RANGE vertexReadRange(00); // We do not intend to read from this resource on the CPU.
    VertexBufferGPU->Map(0&vertexReadRange, &vertexDataBuffer);
    ::memcpy(vertexDataBuffer, &vertices[0], vbByteSize);
    VertexBufferGPU->Unmap(0, nullptr);
 
    // 3. 정점 뷰 생성
    D3D12_VERTEX_BUFFER_VIEW vbv;
    vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
    vbv.SizeInBytes = (UINT)vertices.size() * sizeof(Vertex);
    vbv.StrideInBytes = sizeof(Vertex);
 
}
 
 
void Update() {
    // 3a. binding
    mCommandList->IASetVertexBuffers(01&vbv);
 
    // 4. 기본 도형 위상 구조 설정    
    mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 
    // 5. 그리기 명령 제출
    mCommandList->DrawInstanced(
        vertices_size,
        100);
}
 
cs

 

 

 

 

'computer graphics > DirectX12' 카테고리의 다른 글

20. 삼각형 그리기  (0) 2021.07.27
18. 상수 버퍼  (0) 2021.07.18
12. 다중표본화(multisampling)  (0) 2021.07.04
8. 교환사슬과 더블버퍼링  (0) 2021.07.04
7. COM  (0) 2021.07.04
Comments