6.1 정점(Vertex)
앞에서 말했듯, D3D의 정점에 공간적 위치 이외의 추가적인 자료를 부여할 수 있다. 원하는 자료를 가진 커스텀 정점 형식(vertex format)을 만들려면 우선 그러한 자료를 담을 구조체를 정의해야 한다.
struct Vertex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
정점 구조체를 정의한 다음에는 정점 구조체의 각 필드, 즉 정점의 각 성부느올 무엇을 해야하는지 D3D에게 알려주어야 한다. 그러한 정보를 D3D에게 알려주는 수단으로 쓰이는 것이 입력 배치 서술(input layout description)이다.
D3D12_INPUT_LAYOUT_DESC (d3d12.h) - Win32 apps
Describes the input-buffer data for the input-assembler stage.
docs.microsoft.com
D3D12_INPUT_LAYOUT_ELEMENT_DESC desc[] =
{
{"Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_PER_VERTEX_DATA, 0},
{"Color", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_PER_VERTEX_DATA, 0}
};
2. 정점 버퍼
정점들은 버퍼(buffer)라고 불리는 GPU자원 (ID3D12Resource)에 넣어야 한다. 이것을 정점 버퍼(vertex buffer)라고 한다. 버퍼는 다차원이 아니며, 밉맵(mip-map)이나 필터 ,다중표본화 기능이 없다. 앱에서 정점 같은 자료 원소들의 배열을 GPU에 제공해야 할 때에는 항상 버퍼를 사용한다.
정점 버퍼는 D3D12_RESOURCE_DESC 서술자(요소 중 width는 가로 길이가 아닌 버퍼의 바이트 개수를 뜻한다.)를 채운 후 ID3D12Device::CreateCommittedResrouce()를 사용해 생성한다.
정적 기하구조(프레임마다 변하지 않는 기하구조)를 그릴 때에는 디폴트 힙(D3D12_HEAP_TYPE_DEFAULT)에 넣는다. 하지만 CPU는 디폴 힙에 있는 정점 버퍼를 수정하지 못한다. 그래서 완전 처음 디폴트 힙에 넣기 전에 자원을 초기화하기 위해 임시로 업로드 힙(D3D12_HEAP_TYPE_UPLOAD)을 생성하여 그 힙에서 디폴트 힙으로 자원을 복사해준다.
ComPtr<ID3D12Resource> D3DUtil::CreateDefaultBuffer(
ID3D12Device* pDevice,
ID3D12GraphicsCommandList* pCommandList,
const void* initData,
UINT64 byteSize,
ComPtr<ID3D12Resource>& uploadBuffer)
{
ComPtr<ID3D12Resource> defaultBuffer;
// 업로드 힙에 버퍼 생성(임시로 사용)
ThrowIfFailed(pDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(uploadBuffer.GetAddressOf())));
// 디폴트 힙에 버퍼 생성
ThrowIfFailed(pDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())));
// 복사할 데이터 서술
D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;
subResourceData.RowPitch = byteSize;
// 디폴트 버퍼의 상태를 복사 목적지로 변경
pCommandList->ResourceBarrier(
1,
&CD3DX12_RESOURCE_BARRIER::Transition(
defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_COPY_DEST));
// uploadBuffer에 담긴 자료를 defaultBuffer에 복사한다.
UpdateSubresources(
pCommandList,
defaultBuffer.Get(),
uploadBuffer.Get(),
0u,
0u,
1u,
&subResourceData);
// GPU에서 읽어야 하므로 읽기모드로 전이시킨다.
pCommandList->ResourceBarrier(
1,
&CD3DX12_RESOURCE_BARRIER::Transition(
defaultBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_GENERIC_READ));
return defaultBuffer;
}
정점 버퍼를 파이프라인에 묶으려면 정점 버퍼를 서술하는 뷰를 만들어야 한다. rtv와는 달리, 정점 뷰에는 서술자 힙이 필요하지 않다.
https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_vertex_buffer_view
D3D12_VERTEX_BUFFER_VIEW (d3d12.h) - Win32 apps
Describes a vertex buffer view.
docs.microsoft.com
뷰 생성까지 완료했다면 파이프라인의 한 입력 슬롯에 묶어줄 수 있다. 그러면 정점들이 입력 조립기(IA) 단계로 공급된다.
mCommandList->IASetVertexBuffers(0u, 1u, &mBox->GetVertexBufferView());
하지만 이것은 정점들을 파이프라인에 공급할 준비가 된 것이지 그리는 것이 아니다. 실제로 그리려면 ID3D12Graphics
CommandList::DrawInstance()를 호출해야 한다. 이 메서드의 두 매개변수 VertexCountPerInstance와 StartVertexLocation에 의해 정점 버퍼의 정점들 중 그리기 호출에 쓰이는 일련의 정점들이 결정된다.