정점들의 색상 정보를 이미지를 통하여 건네주는 방법
UV좌표
정점과 맵핑하기 위한 이미지의 좌표계
상단 좌측 모서리 (0,0) 우측 모서리 (1,0)
하단 좌측 모서리 (0,1) 우측 모서리 (1,1)
0 ~ 1 사이의 값을 가지게 됨
랜더링 파이프라인에서 rasterizer 단계에서
삼각형의 픽셀을 계산하고 색상 정보를 적용하고 보간할 때 적용됨
텍스쳐 불러오기
DirectX에서 이미지를 불러올 수 있는 기능을 제공하지 않기 때문에
다른 라이브러리를 사용하여야 함
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를 초기화한 후 해당 텍스쳐를 렌더 시에 메쉬가 사용하도록 하여 테스트