Texture mapping

ㅋㅋ·2022년 7월 18일
0

DirectX12강의

목록 보기
8/39

정점들의 색상 정보를 이미지를 통하여 건네주는 방법

UV좌표

정점과 맵핑하기 위한 이미지의 좌표계

상단 좌측 모서리 (0,0) 우측 모서리 (1,0)
하단 좌측 모서리 (0,1) 우측 모서리 (1,1)

0 ~ 1 사이의 값을 가지게 됨

랜더링 파이프라인에서 rasterizer 단계에서

삼각형의 픽셀을 계산하고 색상 정보를 적용하고 보간할 때 적용됨


텍스쳐 불러오기

DirectX에서 이미지를 불러올 수 있는 기능을 제공하지 않기 때문에

다른 라이브러리를 사용하여야 함

DirectXTex

DirectXTex.lib 파일과 DirectXTex.h, DirectXTex.inl 파일들을 이용


ScratchImage			 		_image;
ComPtr<ID3D12Resource>			_tex2D;

ComPtr<ID3D12DescriptorHeap>	_srvHeap;
D3D12_CPU_DESCRIPTOR_HANDLE		_srvHandle;

텍스쳐는 로드 후 변경없이 계속 사용하게 됨


	wstring ext = fs::path(path).extension();

	if (ext == L".dds" || ext == L".DDS")
		::LoadFromDDSFile(path.c_str(), DDS_FLAGS_NONE, nullptr, _image);
	else if (ext == L".tga" || ext == L".TGA")
		::LoadFromTGAFile(path.c_str(), nullptr, _image);
	else // png, jpg, jpeg, bmp
		::LoadFromWICFile(path.c_str(), WIC_FLAGS_NONE, nullptr, _image);

이미지 파일은 확장자에 따라서 load 함수가 달라짐


#define _HAS_STD_BYTE 0

#include <filesystem>
namespace fs = std::filesystem;

c++ 17의 byte 관련 에러가 날 시 _HAS_STD_BYTE를 0으로 정의하거나

헤더에서 using namespace std 한 것을 해제


void Texture::CreateTexture(const wstring& path)
{
	...

	HRESULT hr = ::CreateTexture(DEVICE.Get(), _image.GetMetadata(), &_tex2D);
	if (FAILED(hr))
		assert(nullptr);

	vector<D3D12_SUBRESOURCE_DATA> subResources;

	hr = ::PrepareUpload(DEVICE.Get(),
		_image.GetImages(),
		_image.GetImageCount(),
		_image.GetMetadata(),
		subResources);

	...
    
	const uint64 bufferSize = ::GetRequiredIntermediateSize(_tex2D.Get(), 0, static_cast<uint32>(subResources.size()));

	D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
	D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);

	ComPtr<ID3D12Resource> textureUploadHeap;
	hr = DEVICE->CreateCommittedResource(
		&heapProperty,
		D3D12_HEAP_FLAG_NONE,
		&desc,
		D3D12_RESOURCE_STATE_GENERIC_READ,
		nullptr,
		IID_PPV_ARGS(textureUploadHeap.GetAddressOf()));

	...

	::UpdateSubresources(RESOURCE_CMD_LIST.Get(),
		_tex2D.Get(),
		textureUploadHeap.Get(),
		0, 0,
		static_cast<unsigned int>(subResources.size()),
		subResources.data());

	GEngine->GetCmdQueue()->FlushResourceCommandQueue();
}

불러온 이미지를 CreateTexture 함수를 사용하여 텍스쳐로 만든다.

GPU로 텍스쳐의 크기만큼 메모리 사용 요청을 보내야 함


텍스쳐는 랜더링 시에 필요하긴 하지만 랜더링될 때마다 새롭게 로드하는게 아닌

한번 로드한 후 계속해서 사용되는 리소스임

기존의 command list는 RenderBegin, End시에만 정상적으로 보낼 수 있었기 때문에

새로 랜더링과 관계없이 요청할 수 있는 command list 필요

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));

command queue를 하나 더 만드는게 아닌 list만 하나더 만들면 된다.

void CommandQueue::FlushResourceCommandQueue()
{
	_resCmdList->Close();

	ID3D12CommandList* cmdListArr[] = { _resCmdList.Get() };
	_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);

	WaitSync();

	_resCmdAlloc->Reset();
	_resCmdList->Reset(_resCmdAlloc.Get(), nullptr);
}

필요 시에 텍스쳐를 로드할 수 있도록 구현


void RootSignature::Init(ComPtr<ID3D12Device> device)
{
	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
	};

	...
}

텍스쳐는 기존 b 레지스터가 아닌 다른 t 레지스터를 필요로 하기 때문에

root signature의 레지스터 사용 범위를 수정해야 함


struct Vertex
{
	Vec3 pos;
	Vec4 color;
	Vec2 uv;
};

vertex는 이제 uv 좌표를 포함하도록 수정


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;
};

vertex 변경 및 텍스쳐 사용에 따라 shader 파일 수정

SamplerState는 보간 시 사용하는 알고리즘이 저장될 레지스터


void Shader::Init(const wstring& path)
{
	...

	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, 0 }
	};

	...
}

shader 구현 부분에서도 shader 파일과 동일하게 맞춰준다.


float4 PS_Main(VS_OUT input) : SV_Target
{
    float4 color = tex_0.Sample(sam_0, input.uv);

    return color;
}

shader 파일에서 pixel shader가 uv 좌표를 가지고 texture 색상을 사용하도록 수정


D3D12_STATIC_SAMPLER_DESC _samplerDesc;

void RootSignature::CreateSamplerDesc()
{
	_samplerDesc = CD3DX12_STATIC_SAMPLER_DESC(0);
}

root signature에서 sampler로 사용할 알고리즘을 선택

현재는 가장 기본적인 알고리즘을 사용


void RootSignature::CreateRootSignature()
{
	...

	D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(_countof(param), param, 1, &_samplerDesc);
	sigDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;

	...
}

CD3DX12_ROOT_SIGNATURE_DESC 함수로 Descriptor를 만들 때 파라미터로 sampler를 넘겨준다.


void Texture::CreateView()
{
	D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
	srvHeapDesc.NumDescriptors = 1;
	srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	DEVICE->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&_srvHeap));

	_srvHandle = _srvHeap->GetCPUDescriptorHandleForHeapStart();

	D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
	srvDesc.Format = _image.GetMetadata().format;
	srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
	srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
	srvDesc.Texture2D.MipLevels = 1;
	DEVICE->CreateShaderResourceView(_tex2D.Get(), &srvDesc, _srvHandle);
}

Shader Resource View는 D3D12_SHADER_RESOURCE_VIEW_DESC 데이터를 만들어 주어야 한다.


class Mesh
{
public:
	...
    
	void SetTexture(shared_ptr<Texture> t) { _tex = t; }

private:
	...
    
	shared_ptr<Texture> _tex = {};
};

테스트를 위해 임시적으로 메쉬에 텍스쳐 삽입


void Mesh::Render()
{
	...
	{
		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();

	...
}

메쉬가 랜더링 될 때 텍스쳐의 메모리를 주고 해당 데이터들을 레지스터에 올려달라고 요청


class TableDescriptorHeap
{
public:
	...
	void SetSRV(D3D12_CPU_DESCRIPTOR_HANDLE srcHandle, SRV_REGISTER reg);

	...
    
	D3D12_CPU_DESCRIPTOR_HANDLE GetCPUHandle(SRV_REGISTER reg);
    
	...
};

void TableDescriptorHeap::SetSRV(D3D12_CPU_DESCRIPTOR_HANDLE srcHandle, SRV_REGISTER reg)
{
	D3D12_CPU_DESCRIPTOR_HANDLE destHandle = GetCPUHandle(reg);

	uint32 destRange = 1;
	uint32 srcRange = 1;
	DEVICE->CopyDescriptors(1, &destHandle, &destRange, 1, &srcHandle, &srcRange, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}

Table descriptor heap가 SRV 또한 설명할 수 있도록 구현


void Game::Init(const WindowInfo& info)
{
	...

	texture->Init(L"..\\Resources\\Texture\\icon_7.gif");

	GEngine->GetCmdQueue()->WaitSync();
}

void Game::Update()
{
	GEngine->RenderBegin();

	shader->Update();

	{
		Transform t;
		t.offset = Vec4(0.f, 0.f, 0.f, 0.f);
		mesh->SetTransform(t);

		mesh->SetTexture(texture);

		mesh->Render();
	}


	GEngine->RenderEnd();
}

texture를 초기화한 후 해당 텍스쳐를 렌더 시에 메쉬가 사용하도록 하여 테스트

0개의 댓글