헛둘이 2023. 1. 16. 16:48

- 텍스쳐 매핑(Texture Mapping)이란?

3D 컴퓨터 그래픽에서 텍스쳐를 모델의 표면에 부착하는 것

 

- UV 좌표계란?

 

출처: https://filedownload.tistory.com/entry/UV%EC%A2%8C%ED%91%9C%EC%99%80-%EB%B7%B0-%EC%A2%8C%ED%91%9C%EA%B3%84

- 텍스쳐 이미지의 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 메모리에 올라가 있다.

 

- 이후 동일하게 출력하면 됨