Texture
2D 이미지라 생각, 2D 이미지를 표현하는 방법을 배운다.
- Lib에 DirectXTex.lib를 추가해야함.
// EnginePch.h
// ...
#include <DirectXTex/DirectXTex.h>
#include <DirectXTex/DirectXTex.inl>
// ...
#ifdef _DEBUG
#pragma comment(lib, "DirectXTex\\DirectXTex_debug.lib")
#else
#pragma comment(lib, "DirectXTex\\DirectXTex.lib")
#endif
// ...
Texture Class
Texture(2D)를 읽어오고 / DirectX 관리를 위한 Class
#pragma once
class Texture
{
public:
void Init(const wstring& path);
D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle() { return _srvHandle; }
public:
void CreateTexture(const wstring& path);
void CreateView();
private:
ScratchImage _image;
ComPtr<ID3D12Resource> _tex2D;
ComPtr<ID3D12DescriptorHeap> _srvHeap;
D3D12_CPU_DESCRIPTOR_HANDLE _srvHandle;
};
#include "pch.h"
#include "Texture.h"
#include "Engine.h"
void Texture::Init(const wstring& path)
{
CreateTexture(path);
CreateView();
}
void Texture::CreateTexture(const wstring& path)
{
// 파일 확장자 얻기
wstring ext = fs::path(path).extension(); // fs(file system) C++17부터 지원된다.
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);
// 이미지파일을 이용해 texture생성
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);
if (FAILED(hr))
assert(nullptr);
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()));
if (FAILED(hr))
assert(nullptr);
/*
::UpdateSubresources(RESOURCE_CMD_LIST.Get(),
RESOURCE_CMD_LIST : 기존의 Command List는 Render begin ~ end 사이에 있어야하는데
이미지로딩할때는 굳이 begin ~ end를 기다릴 필요가 없기에 새로운 Command List를 만듦
*/
::UpdateSubresources(RESOURCE_CMD_LIST.Get(),
_tex2D.Get(),
textureUploadHeap.Get(),
0, 0,
static_cast<unsigned int>(subResources.size()),
subResources.data());
GEngine->GetCmdQueue()->FlushResourceCommandQueue();
}
리소스 로드용 (RESOURCE_CMD_LIST
) 커멘드 리스트를 만들어보자.
class CommandQueue
{
// ...
ComPtr<ID3D12CommandAllocator> _cmdAlloc; // 기존에 사용하던 커멘드 리스트
ComPtr<ID3D12GraphicsCommandList> _cmdList;
ComPtr<ID3D12CommandAllocator> _resCmdAlloc; // 리소스로드용 커멘드Alloc
ComPtr<ID3D12GraphicsCommandList> _resCmdList; // 리소스로드용 커멘드리스트
FlushResourceCommandQueue()
: 리소스 동작 명령
void CommandQueue::FlushResourceCommandQueue()
{
_resCmdList->Close();
ID3D12CommandList* cmdListArr[] = { _resCmdList.Get() };
_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);
WaitSync(); // GPU가 다 읽어간다면
// 리셋 시킨다.
_resCmdAlloc->Reset();
_resCmdList->Reset(_resCmdAlloc.Get(), nullptr);
}
텍스쳐를 넘기기 위해 RootSignature도 추가되어야 할 부분이 있다.
void RootSignature::CreateRootSignature()
{
CD3DX12_DESCRIPTOR_RANGE ranges[] =
{
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, CBV_REGISTER_COUNT, 0), // b0~b4
// Texture(SRV(Shader Resource View))용 t0~t4를 할당
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, SRV_REGISTER_COUNT, 0), // t0~t4
};
// 나머지는 동일
}
추가적으로 Vertex값에 uv값을 추가해 Tuxture를 받을 준비를 해보자.
struct Vertex
{
Vec3 pos;
Vec4 color;
Vec2 uv;
};
쉐이더값도 Texture르 받을 준비
Texture2D tex_0 : register(t0);
// t0~t4까지 잡았는데 t1~t4는 어디갔나??
// 잡아놨다고 꼭 써야하는건 아님 우선은 t0만 쓴다.
SamplerState sam_0 : register(s0);
// Pixel쉐이더의 색상을 고르는 정책을 설명(우선은 받아들인다.)
// ...
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos, 1.f);
output.color = input.color;
output.uv = input.uv;
return output;
}
float4 PS_Main(VS_OUT input) : SV_Target
{
float4 color = tex_0.Sample(sam_0, input.uv);
return color;
}
참고로 Sampler는 아래와 같이 만들어만 두자(자세한 설명은 이후에)
void RootSignature::CreateSamplerDesc()
{
_samplerDesc = CD3DX12_STATIC_SAMPLER_DESC(0);
}
===
아래는 예전에 정리했던 버전이 있어서.. 중복된 부분이 있음. 참고하고 볼것
다시 리소스 View로 넘어와서 …
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);
}
참고로 file system을 사용하려면 C++17을 Enable해줘야 하고,
C++17에는 BYTE가 std에 선언되어 있어 충돌이 나게되는데 BYTE를 사용안한다고 알려줘야한다.
// EnginePch.h
#pragma once
// std::byte 사용하지 않음
#define _HAS_STD_BYTE 0
//...
Texture를 초기화 할때 커멘드 큐를 사용하는데 기존의 커멘드 큐의 경우 렌더 시작/종료에 종속적이니 렌더와 상관없이 동작하는 커멘드 큐를 새로 생성해 보자.
#pragma once
class SwapChain;
class DescriptorHeap;
class CommandQueue
{
// ...
// 리소스를 읽는데 사용하는 커멘드
ComPtr<ID3D12CommandAllocator> _resCmdAlloc;
ComPtr<ID3D12GraphicsCommandList> _resCmdList;
// ...
};
void CommandQueue::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain)
{
// ...
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));
// ...
Texture처리를 위해서 Signature의 변경이 필요하다
// EnginePch.h
// ...
enum class SRV_REGISTER : uint8
{
t0 = static_cast<uint8>(CBV_REGISTER::END),
t1,
t2,
t3,
t4,
END
};
enum
{
SWAP_CHAIN_BUFFER_COUNT = 2,
CBV_REGISTER_COUNT = CBV_REGISTER::END,
SRV_REGISTER_COUNT = static_cast<uint8>(SRV_REGISTER::END) - CBV_REGISTER_COUNT,
REGISTER_COUNT = CBV_REGISTER_COUNT + SRV_REGISTER_COUNT,
};
// ...
void RootSignature::CreateRootSignature()
{
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
// SRV(Shader Resource View)
// t0 ~ t4 추가
};
cbuffer TEST_B0 : register(b0)
{
float4 offset0;
};
cbuffer TEST_B1 : register(b1)
{
float4 offset1;
};
Texture2D tex_0 : register(t0);
SamplerState sam_0 : register(s0);
struct VS_IN
{
float3 pos : POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD;
// uv 좌표는 그림의 좌표를 말함 0~1까지 존재
};
struct VS_OUT
{
float4 pos : SV_Position;
float4 color : COLOR;
float2 uv : TEXCOORD;
};
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos, 1.f);
output.color = input.color;
output.uv = input.uv;
return output;
}
float4 PS_Main(VS_OUT input) : SV_Target
{
// 픽셀 쉐이더의 아웃풋은 texture의 인풋과 관련이 있을 것이다
float4 color = tex_0.Sample(sam_0, input.uv);
return color;
}
void Shader::Init(const wstring& path)
{
CreateVertexShader(path, "VS_Main", "vs_5_0");
CreatePixelShader(path, "PS_Main", "ps_5_0");
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 },
};
// ...
void Game::Init(const WindowInfo& info)
{
GEngine->Init(info);
vector<Vertex> vec(4);
vec[0].pos = Vec3(-0.5f, 0.5f, 0.5f);
vec[0].color = Vec4(1.f, 0.f, 0.f, 1.f);
vec[0].uv = Vec2(0.f, 0.f);
vec[1].pos = Vec3(0.5f, 0.5f, 0.5f);
vec[1].color = Vec4(0.f, 1.f, 0.f, 1.f);
vec[1].uv = Vec2(1.f, 0.f);
vec[2].pos = Vec3(0.5f, -0.5f, 0.5f);
vec[2].color = Vec4(0.f, 0.f, 1.f, 1.f);
vec[2].uv = Vec2(1.f, 1.f);
vec[3].pos = Vec3(-0.5f, -0.5f, 0.5f);
vec[3].color = Vec4(0.f, 1.f, 0.f, 1.f);
vec[3].uv = Vec2(0.f, 1.f);
vector<uint32> indexVec;
{
indexVec.push_back(0);
indexVec.push_back(1);
indexVec.push_back(2);
}
{
indexVec.push_back(0);
indexVec.push_back(2);
indexVec.push_back(3);
}
mesh->Init(vec, indexVec);
shader->Init(L"..\\Resources\\Shader\\default.hlsli");
texture->Init(L"..\\Resources\\Texture\\veigar.jpg");
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();
}
void Mesh::Render()
{
CMD_LIST->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CMD_LIST->IASetVertexBuffers(0, 1, &_vertexBufferView); // Slot: (0~15)
CMD_LIST->IASetIndexBuffer(&_indexBufferView);
{
D3D12_CPU_DESCRIPTOR_HANDLE handle = GEngine->GetCB()->PushData(0, &_transform, sizeof(_transform));
GEngine->GetTableDescHeap()->SetCBV(handle, CBV_REGISTER::b0);
// Texture를 넣는다.(SetSRV 함수를 만들어야한다.)
GEngine->GetTableDescHeap()->SetSRV(_tex->GetCpuHandle(), SRV_REGISTER::t0);
}
GEngine->GetTableDescHeap()->CommitTable();
CMD_LIST->DrawIndexedInstanced(_indexCount, 1, 0, 0, 0);
}
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);
}