Root Signature
GPU내의 램을 사용할 수 있도록 함
void RootSignature::Init(ComPtr<ID3D12Device> device)
{
D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(D3D12_DEFAULT);
sigDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
...
::D3D12SerializeRootSignature(&sigDesc, D3D_ROOT_SIGNATURE_VERSION_1, &blobSignature, &blobError);
device->CreateRootSignature(0, blobSignature->GetBufferPointer(), blobSignature->GetBufferSize(), IID_PPV_ARGS(&_signature));
}
INPUT_ASSEMBLER_INPUT 모드로 root signature 생성
추후 다른 기능이 필요할 때 모드를 추가
Mesh
정점 정보들과 버퍼를 관리하는 클래스
정점은 위치와 색 정보로 이루어져 있음
void Mesh::Init(vector<Vertex>& vec)
{
...
D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
DEVICE->CreateCommittedResource(
&heapProperty,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&_vertexBuffer));
...
}
GPU 메모리에 정보를 업로드 할때 D3D12_HEAP_TYPE_UPLOAD 플래그로 정의
원칙적으로는 해당 플래그로 할당받은 메모리는 업로드만 하고 GPU가 사용하면 안됨
NORMAL 플래그로 GPU가 사용하는 공간이 하나 더 있어야 함
해당 구현은 업로드 메모리와 사용 메모리를 한 곳에서 진행
나무나 돌 같은 고정된 메쉬는 NORMAL 타입으로 지정하는게 더 빠르다고 함
UPLOAD 타입은 실시간으로 변하는 메쉬를 업로드 하는 용
void Mesh::Init(vector<Vertex>& vec)
{
...
void* vertexDataBuffer = nullptr;
CD3DX12_RANGE readRange(0, 0);
_vertexBuffer->Map(0, &readRange, &vertexDataBuffer);
::memcpy(vertexDataBuffer, &vec[0], bufferSize);
_vertexBuffer->Unmap(0, nullptr);
_vertexBufferView.BufferLocation = _vertexBuffer->GetGPUVirtualAddress();
_vertexBufferView.StrideInBytes = sizeof(Vertex);
_vertexBufferView.SizeInBytes = bufferSize;
}
GPU 메모리 복사는 Map -> 복사 -> Unmap 순으로 이루어짐
void Mesh::Render()
{
CMD_LIST->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CMD_LIST->IASetVertexBuffers(0, 1, &_vertexBufferView);
CMD_LIST->DrawInstanced(_vertexCount, 1, 0, 0);
}
정점 정보들이 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST 플래그로 삼각형들이라는 것을 알려줌
렌더링은 엔진의 렌더링이 끝나기 전에 list에 요청 사항을 넣어두고
엔진이 그래픽카드로 요청할 때 넘어가도록 함
Shader
그래픽카드가 무엇을 해야할지 기술해둔 클래스(랜더링 파이프라인)
쉐이더는 보통 파일로 관리한다. (hlsl, hlsli (header))
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 }
};
...
}
vertex, pixel 쉐이더 사용하는데 함수명은 VS_Main, PS_Main이다.
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos, 1.f);
output.color = input.color;
return output;
}
float4 PS_Main(VS_OUT input) : SV_Target
{
return input.color;
}
vertex와 pixel 쉐이더에서 해야할 일을 쉐이더 파일에 구현
vertex 쉐이더에서 VS_IN 데이터가 들어와 VS_OUT으로 출력되고
pixel 쉐이더에서 VS_OUT 데이터가 입력이 들어오고 float4으로 출력
void Shader::CreateShader(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob, D3D12_SHADER_BYTECODE& shaderByteCode)
{
...
if (FAILED(::D3DCompileFromFile(path.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
, name.c_str(), version.c_str(), compileFlag, 0, &blob, &_errBlob)))
{
::MessageBoxA(nullptr, "Shader Create Failed !", nullptr, MB_OK);
}
shaderByteCode = { blob->GetBufferPointer(), blob->GetBufferSize() };
}
CreateShader 함수에서 쉐이더 파일을 파싱하여 해당 정보를 blob 포인터에 넣음
vector<Vertex> vec(3);
vec[0].pos = Vec3(0.f, 0.5f, 0.5f);
vec[0].color = Vec4(1.f, 0.f, 0.f, 1.f);
vec[1].pos = Vec3(0.5f, -0.5f, 0.5f);
vec[1].color = Vec4(0.f, 1.0f, 0.f, 1.f);
vec[2].pos = Vec3(-0.5f, -0.5f, 0.5f);
vec[2].color = Vec4(0.f, 0.f, 1.f, 1.f);
mesh->Init(vec);
임시로 삼각형을 위와 같이 만들어 테스트
화면상 중앙이 0,0 위와 오른쪽이 +방향인 좌표계