4. Texture Mapping
- 텍스쳐 매핑(Texture Mapping)이란?
3D 컴퓨터 그래픽에서 텍스쳐를 모델의 표면에 부착하는 것
- UV 좌표계란?
- 텍스쳐 이미지의 2D 좌표를 3D 모델의 표면에 매핑할 때 사용되는 좌표계
- U는 가로축, V는 세로축을 나타낸다.
- 3D 모델의 표면의 정점에는 UV 좌표가 할당되어 있고, 그 좌표를 통해 텍스쳐 이미지를 3D 모델 표면에 매핑할 수 있다.
*Texture를 사용하면서 바뀌게 된 점
1. uv좌표가 추가됨에 따라 Shader에서 받는 인자도 추가됨
Texture2D tex_0 : register(t0);
SamplerState sam_0 : register(s0);
struct VS_IN
{
float3 pos : POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD;
};
struct VS_OUT
{
float4 pos : SV_Position;
float4 color : COLOR;
float2 uv : TEXCOORD;
};
2. Shader의 Input Layout에서 쉐이더에 전달되는 데이터를 추가해줘야 한다.
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 28, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA},
};
- 정의해준 InputLayout과 Shader 코드가 일치하지 않는다면 읽어오는 순간에 에러가 나게 된다.
3. Resource를 처리할 Resource CommandList를 추가해야 한다
- 기존 CommandQueue 클래스의 CommandList는 RenderBegin에서 Reset 되고, RenderEnd에서 Close 되므로
- 결국 RenderBegin과 RenderEnd 사이에 일감이 들어오는 구조로 되어 있었는데,
- 텍스쳐를 불러오거나 생성하는 등의 작업은 그 구간이 이루어질 필요가 없기 때문에 별도로 CommandList를 새로 파는 것이다.
ComPtr<ID3D12CommandAllocator> _resCmdAlloc;
ComPtr<ID3D12GraphicsCommandList> _resCmdList;
- 따라서 기존 버전에서 이름만 바꾼 버전으로 하나 더 만든다
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_resCmdAlloc));
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _resCmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_resCmdList));
4. ResourceCommandQueue가 생김에 따라 거기에 쌓인 명령어들을 실행시켜주는 분기를 추가
void CommandQueue::FlushResourceCommandQueue()
{
_resCmdList->Close();
ID3D12CommandList* cmdListArr[] = { _resCmdList.Get() };
_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);
WaitSync();
_resCmdAlloc->Reset();
_resCmdList->Reset(_resCmdAlloc.Get(), nullptr);
}
- 이 함수는 Texture 클래스의 CreateTexture에서 바로 실행되게 된다.
ComPtr<ID3D12Resource> textureUploadHeap;
hr = DEVICE->CreateCommittedResource(
&heapProperty,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(textureUploadHeap.GetAddressOf()));
if (FAILED(hr))
assert(nullptr);
::UpdateSubresources(RESOURCE_CMD_LIST.Get(),
_tex2D.Get(),
textureUploadHeap.Get(),
0, 0,
static_cast<unsigned int>(subResources.size()),
subResources.data());
GEngine->GetCmdQueue()->FlushResourceCommandQueue();
- UploadHeap은 GPU가 직접 접근할 수 있는 메모리 힙이다
- CPU는 UploadHeap에 데이터를 저장하고, GPU는 이를 사용하여 서브리소스를 업데이트한다.
5. RootSignature를 추가한다
CD3DX12_DESCRIPTOR_RANGE ranges[] =
{
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, CBV_REGISTER_COUNT, 0), // b0~b4
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, SRV_REGISTER_COUNT, 0), // t0~t4
};
- RootSignature에 CBV 레지스터 뿐만 아니라 SRV 레지스터도 사용하겠다는 내용을 전달한다.
- 쉐이더 코드에 Texture2D를 t0 레지스터로 사용하겠다는 의미
Texture2D tex_0 : register(t0);
SamplerState sam_0 : register(s0);
*s0 레지스터는 Root Signature에서 사용하겠다고 전달한 적이 없는데 저렇게 사용해도 되나?
void RootSignature::CreateSamplerDesc()
{
_samplerDesc = CD3DX12_STATIC_SAMPLER_DESC(0);
}
- RootSignature를 초기화할 때 SamplerState를 설정할 수 있는데
D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(_countof(param), param, 1, &_samplerDesc);
- 이렇게 Descriptor를 정의할 때 SamplerDesc를 넘겨주면 SamplerState를 정의한 것으로 간주한다.
- 즉, '샘플러를 사용하지만 명시적으로 표시하지 않았다' 라고 이해할 수 있다.
6. Mesh에 Texture 객체를 소유하도록 하고 SRV 데이터를 TableDescriptorHeap에 밀어넣는다
void Mesh::Render()
{
// 실제로는 그려줄 때는 이 뷰를 사용해서 그려주게 된다.
CMD_LIST->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CMD_LIST->IASetVertexBuffers(0, 1, &_vertexBufferView);
CMD_LIST->IASetIndexBuffer(&_indexBufferView);
// 1. Buffer에 데이터 세팅
// - GPU 메모리에 뚜껑을 열고 데이터를 집어넣는 부분이 Mesh의 Init에 비슷하게 구현되어 있음
// 2. Buffer의 주소를 Register에 전송
{
D3D12_CPU_DESCRIPTOR_HANDLE handle = GEngine->GetCB()->PushData(0, &_transform, sizeof(_transform));
GEngine->GetTableDescHeap()->SetCBV(handle, CBV_REGISTER::b0);
GEngine->GetTableDescHeap()->SetSRV(_tex->GetCpuHandle(), SRV_REGISTER::t0);
}
GEngine->GetTableDescHeap()->CommitTable();
CMD_LIST->DrawIndexedInstanced(_indexCount, 1, 0, 0, 0);
}
- SetSRV는 사실상 SetCBV랑 다른점이 거의 없고 CBV_REGISTER와 SRV_REGISTER의 차이만 있다.
GEngine->GetTableDescHeap()->SetSRV(_tex->GetCpuHandle(), SRV_REGISTER::t0);
- 불러온 이미지의 View의 핸들을 통해 t0 레지스터에 텍스쳐를 전달한다
*t0 레지스터가 감당하기에 텍스쳐의 크기가 너무 큰거 아닌가?
- t0 레지스터는 텍스쳐 리소스를 가리키는 서술자의 포인터임
- GPU 메모리에 있는 리소스를 CPU가 접근할 수 있도록 가리킨다.
*CPU 메모리에 있는걸 GPU가 가리키는거 아냐? 반대로 된거 아냐?
- 앞서 설명했듯 Resource Command List를 통해 텍스쳐 리소스를 업로드했기 때문에 GPU 메모리에 올라가 있다.
- 이후 동일하게 출력하면 됨