일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 스토어드 프로시저
- Brute Force
- DP
- Dijkstra
- 이진탐색
- Trie
- Hash
- MYSQL
- union find
- Two Points
- Stored Procedure
- 다익스트라
- binary search
- two pointer
- String
- SQL
- 그래프
- Today
- Total
codingfarm
16. 정점 버퍼(Vertex Buffer) 본문
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에게 알리기 위한 입력 배치 서술을 작성해야한다.
- 정점 구조체를 정의한다
- 입력 배치 서술(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, 0, 0,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32,
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 이므로 여타 다른 버퍼들과 다르지 않다.
- D3D12_RESOURCE_DESC 구조체를 정의하여 생성될 자원에 대해 서술한다
- D3D12_HEAP_PROPERTIES 구조체를 정의하여 자원이 저장될 heap에 대해 서술한다
- D3D12_CLEAR_VALUE 구조체를 정의하여 리소스를 초기화할 값을 결정한다
- 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와는 약간 다르다
ID3D12_DESCRIPTOR_HEAP_DESC를 정의한다.ID3D12Device::CreateDescriptorHeap를 호출하여 descriptor heap을 생성한다.- 생성할 descriptor에 대한 D3D12_~_VIEW_DESC 구조체를 정의한다.
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(0, 0); // 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(0, 1, &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(0, 0); // 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(0, 1, &vbv);
// 4. 기본 도형 위상 구조 설정
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 5. 그리기 명령 제출
mCommandList->DrawInstanced(
vertices_size,
1, 0, 0);
}
|
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 |