지난 포스팅에 이어서 프레임워크를 제작해보자.
Shader
쉐이더는 vertexShader와 vsBlob, pixelShader와 psBlob을 묶어줘야한다.
#pragma once
enum ShaderScope
{
SS_None = 0,
SS_VertexShader = (1 << 0),
SS_PixelShader = (1 << 1),
SS_Both = SS_VertexShader | SS_PixelShader
};
class Shader
{
public:
Shader(ComPtr<ID3D11Device> device);
virtual ~Shader();
virtual void Create(const wstring& path, const string& name, const string& version) abstract;
ComPtr<ID3DBlob> GetBlob() { return _blob; }
protected:
void LoadShaderFromFile(const wstring& path, const string& name, const string& version);
protected:
wstring _path;
string _name;
ComPtr<ID3D11Device> _device;
ComPtr<ID3DBlob> _blob;
};
class VertexShader : public Shader
{
using Super = Shader;
public:
VertexShader(ComPtr<ID3D11Device> device);
~VertexShader();
ComPtr<ID3D11VertexShader> GetComPtr() { return _vertexShader; }
virtual void Create(const wstring& path, const string& name, const string& version) override;
protected:
ComPtr<ID3D11VertexShader> _vertexShader;
};
class PixelShader : public Shader
{
using Super = Shader;
public:
PixelShader(ComPtr<ID3D11Device> device);
~PixelShader();
ComPtr<ID3D11PixelShader> GetComPtr() { return _pixelShader; }
virtual void Create(const wstring& path, const string& name, const string& version) override;
protected:
ComPtr<ID3D11PixelShader> _pixelShader;
};
#include "pch.h"
#include "Shader.h"
Shader::Shader(ComPtr<ID3D11Device> device)
: _device(device)
{
}
Shader::~Shader()
{
}
void Shader::LoadShaderFromFile(const wstring& path, const string& name, const string& version)
{
_path = path;
_name = name;
const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
HRESULT hr = ::D3DCompileFromFile(
path.c_str(),
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
name.c_str(),
version.c_str(),
compileFlag,
0,
_blob.GetAddressOf(),
nullptr);
CHECK(hr);
}
VertexShader::VertexShader(ComPtr<ID3D11Device> device) : Super(device)
{
}
VertexShader::~VertexShader()
{
}
void VertexShader::Create(const wstring& path, const string& name, const string& version)
{
LoadShaderFromFile(path, name, version);
HRESULT hr = _device->CreateVertexShader(_blob->GetBufferPointer(), _blob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
CHECK(hr);
}
PixelShader::PixelShader(ComPtr<ID3D11Device> device) : Super(device)
{
}
PixelShader::~PixelShader()
{
}
void PixelShader::Create(const wstring& path, const string& name, const string& version)
{
LoadShaderFromFile(path, name, version);
HRESULT hr = _device->CreatePixelShader(_blob->GetBufferPointer(), _blob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
CHECK(hr);
}
쉐이더를 공통적으로 묶어 생성한 뒤 Create만 호출해 주면 원하는 경로를 읽어와서 쉐이더를 얻을 수 있다.
또한 쉐이더 단계에서 쓰는 ConstantBuffer를 합쳐보자.
#pragma once
template<typename T>
class ConstantBuffer
{
public:
ConstantBuffer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
: _device(device), _deviceContext(deviceContext)
{
}
~ConstantBuffer() { }
ComPtr<ID3D11Buffer> GetComPtr() { return _constantBuffer; }
void Create()
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_DYNAMIC; // CPU_Write + GPU_Read
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.ByteWidth = sizeof(T);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr =_device->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());
CHECK(hr);
}
void CopyData(const T& data)
{
D3D11_MAPPED_SUBRESOURCE subResource;
ZeroMemory(&subResource, sizeof(subResource));
_deviceContext->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
::memcpy(subResource.pData, &data, sizeof(data));
_deviceContext->Unmap(_constantBuffer.Get(), 0);
}
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
ComPtr<ID3D11Buffer> _constantBuffer;
};
PipeLine
Rasterizer,SamplerState,BlendState를 따로 분리해서 원할 때 값을 세팅해 적용해보자.
#include "pch.h"
#include "RasterizerState.h"
RasterizerState::RasterizerState(ComPtr<ID3D11Device> device)
:_device(device)
{
}
RasterizerState::~RasterizerState()
{
}
void RasterizerState::Create()
{
D3D11_RASTERIZER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.FillMode = D3D11_FILL_SOLID;
desc.CullMode = D3D11_CULL_BACK;
desc.FrontCounterClockwise = false;
HRESULT hr = _device->CreateRasterizerState(&desc, _rasterizerState.GetAddressOf());
CHECK(hr);
}
#include "pch.h"
#include "BlendState.h"
BlendState::BlendState(ComPtr<ID3D11Device> device) : _device(device)
{
}
BlendState::~BlendState()
{
}
void BlendState::Create(D3D11_RENDER_TARGET_BLEND_DESC blendDesc /*= { true, D3D11_BLEND_SRC_ALPHA, D3D11_BLEND_INV_SRC_ALPHA, D3D11_BLEND_OP_ADD, D3D11_BLEND_ONE, D3D11_BLEND_ZERO, D3D11_BLEND_OP_ADD, D3D11_COLOR_WRITE_ENABLE_ALL }*/, float factor /*= 0.f*/)
{
_blendFactor = factor;
D3D11_BLEND_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BLEND_DESC));
desc.AlphaToCoverageEnable = false;
desc.IndependentBlendEnable = false;
desc.RenderTarget[0] = blendDesc;
HRESULT hr = _device->CreateBlendState(&desc, _blendState.GetAddressOf());
CHECK(hr);
}
#include "pch.h"
#include "SamplerState.h"
SamplerState::SamplerState(ComPtr<ID3D11Device> device)
:_device(device)
{
}
SamplerState::~SamplerState()
{
}
void SamplerState::Create()
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
desc.BorderColor[0] = 1;
desc.BorderColor[1] = 0;
desc.BorderColor[2] = 0;
desc.BorderColor[3] = 1;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
desc.MaxAnisotropy = 16;
desc.MaxLOD = FLT_MAX;
desc.MinLOD = FLT_MIN;
desc.MipLODBias = 0.0f;
HRESULT hr = _device->CreateSamplerState(&desc, _samplerState.GetAddressOf());
CHECK(hr);
}
여기까지 정리가 됐다면 렌더하는 부분에서 공통적으로 많이 나오는 부분을 하나의 파이프라인으로 묶어보자.
즉, 파이프라인을 하나의 대포라고 하고 물체를 계속 갈아 끼우는 형태인 것이다.
여기서 주의 깊게 생각해야하는 점은 한번만 만들면 같은 물체(스켈레톤)끼리는 계속 같이 사용할 수 있는 것이 무엇이고 각 객체(스켈레톤 마다)마다 필요한 점이 무엇인지 잘 생각해야한다.
같이 공유하는 것은 PipelineInfo로 정리한다.
#pragma once
struct PipelineInfo
{
shared_ptr<InputLayout> inputLayout;
shared_ptr<VertexShader> vertexShader;
shared_ptr<PixelShader> pixelShader;
shared_ptr<RasterizerState> rasterizerState;
shared_ptr<BlendState> blendState;
D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
};
class Pipeline
{
public:
Pipeline(ComPtr<ID3D11DeviceContext> deviceContext);
~Pipeline();
void UpdatePipeline(PipelineInfo info);
void SetVertexBuffer(shared_ptr<VertexBuffer> buffer);
void SetIndexBuffer(shared_ptr<IndexBuffer> buffer);
template<typename T>
void SetConstantBuffer(uint32 slot, uint32 scope, shared_ptr<ConstantBuffer<T>> buffer)
{
if (scope & SS_VertexShader)
_deviceContext->VSSetConstantBuffers(slot, 1, buffer->GetComPtr().GetAddressOf());
if (scope & SS_PixelShader)
_deviceContext->PSSetConstantBuffers(slot, 1, buffer->GetComPtr().GetAddressOf());
}
void SetTexture(uint32 slot, uint32 scope, shared_ptr<Texture> texture);
void SetSamplerState(uint32 slot, uint32 scope, shared_ptr<SamplerState> samplerState);
void Draw(uint32 vertexCount, uint32 startVertexLocation);
void DrawIndexed(uint32 indexCount, uint32 startIndexLocation, uint32 baseVertexLocation);
private:
ComPtr<ID3D11DeviceContext> _deviceContext;
};
#include "pch.h"
#include "Pipeline.h"
Pipeline::Pipeline(ComPtr<ID3D11DeviceContext> deviceContext)
:_deviceContext(deviceContext)
{
}
Pipeline::~Pipeline()
{
}
void Pipeline::UpdatePipeline(PipelineInfo info)
{
// IA
_deviceContext->IASetInputLayout(info.inputLayout->GetComPtr().Get());
_deviceContext->IASetPrimitiveTopology(info.topology);
// VS
if(info.vertexShader)
_deviceContext->VSSetShader(info.vertexShader->GetComPtr().Get(), nullptr, 0);
// RS
if(info.rasterizerState)
_deviceContext->RSSetState(info.rasterizerState->GetComPtr().Get());
// PS
if(info.pixelShader)
_deviceContext->PSSetShader(info.pixelShader->GetComPtr().Get(), nullptr, 0);
// OM
if(info.blendState)
_deviceContext->OMSetBlendState(info.blendState->GetComPtr().Get(), info.blendState->GetBlendFactor(), info.blendState->GetSampleMask());
}
void Pipeline::SetVertexBuffer(shared_ptr<VertexBuffer> buffer)
{
uint32 stride = buffer->GetStride();
uint32 offset = buffer->GetOffset();
_deviceContext->IASetVertexBuffers(0, 1, buffer->GetComPtr().GetAddressOf(), &stride, &offset);
}
void Pipeline::SetIndexBuffer(shared_ptr<IndexBuffer> buffer)
{
_deviceContext->IASetIndexBuffer(buffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
}
void Pipeline::SetTexture(uint32 slot, uint32 scope, shared_ptr<Texture> texture)
{
if (scope & SS_VertexShader)
_deviceContext->VSSetShaderResources(slot, 1, texture->GetComPtr().GetAddressOf());
if (scope & SS_PixelShader)
_deviceContext->PSSetShaderResources(slot, 1, texture->GetComPtr().GetAddressOf());
}
void Pipeline::SetSamplerState(uint32 slot, uint32 scope, shared_ptr<SamplerState> samplerState)
{
if (scope & SS_VertexShader)
_deviceContext->VSSetSamplers(slot, 1, samplerState->GetComPtr().GetAddressOf());
if (scope & SS_PixelShader)
_deviceContext->PSSetSamplers(slot, 1, samplerState->GetComPtr().GetAddressOf());
}
void Pipeline::Draw(uint32 vertexCount, uint32 startVertexLocation)
{
_deviceContext->Draw(vertexCount, startVertexLocation);
}
void Pipeline::DrawIndexed(uint32 indexCount, uint32 startIndexLocation, uint32 baseVertexLocation)
{
_deviceContext->DrawIndexed(indexCount, startIndexLocation, baseVertexLocation);
}
GameObject
이제 모든 물체마다 PipelineInfo를 가지고 있고 나 이런식으로 렌더링 해줘요 라는 식으로 진행하고 Pipeline을 진행하는 것이 더 나을 것이다.
그렇기 때문에 하나의 게임 오브젝트, 마치 유니티 처럼 동작하기 위해 클래스를 나눠서 관리해보자.
#pragma once
class GameObject
{
public:
GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
~GameObject();
void Update();
void Render(shared_ptr<Pipeline> pipeline);
private:
ComPtr<ID3D11Device> _device;
shared_ptr<Geometry<VertexTextureData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
shared_ptr<IndexBuffer> _indexBuffer;
shared_ptr<InputLayout> _inputLayout;
shared_ptr<VertexShader> _vertexShader;
shared_ptr<RasterizerState> _rasterizerState;
shared_ptr<PixelShader> _pixelShader;
shared_ptr<Texture> _texture1;
shared_ptr<BlendState> _blendState;
shared_ptr<SamplerState> _samplerState;
private:
// SRT
TransformData _transformData;
shared_ptr<ConstantBuffer<TransformData>> _constantBuffer;
Vec3 _localPosition = { 0.f, 0.f, 0.f };
Vec3 _localRotation = { 0.f, 0.f, 0.f };
Vec3 _localScale = { 1.f, 1.f, 1.f };
};
#include "pch.h"
#include "GameObject.h"
GameObject::GameObject(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
:_device(device)
{
// 물체의 기하학적인 모형을 만들어줘
_geometry = make_shared<Geometry<VertexTextureData>>();
GeometryHelper::CreateRectangle(_geometry);
// GPU야 이런 모양으로 버퍼를 만들어줘
_vertexBuffer = make_shared<VertexBuffer>(device);
_vertexBuffer->Create(_geometry->GetVertices());
_indexBuffer = make_shared<IndexBuffer>(device);
_indexBuffer->Create(_geometry->GetIndices());
// VertexShader는 이거야
_vertexShader = make_shared<VertexShader>(device);
_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");
// 쉐이더를 바탕으로 도형을 어떻게 분석할지 알려줄게
_inputLayout = make_shared<InputLayout>(device);
_inputLayout->Create(VertexTextureData::descs, _vertexShader->GetBlob());
// PixelShader는 이거야
_pixelShader = make_shared<PixelShader>(device);
_pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");
// RS는 이렇게 세팅할게
_rasterizerState = make_shared<RasterizerState>(device);
_rasterizerState->Create();
// BS는 이렇게 세팅할게
_blendState = make_shared<BlendState>(device);
_blendState->Create();
// 상수버퍼 생성할게
_constantBuffer = make_shared<ConstantBuffer<TransformData>>(device, deviceContext);
_constantBuffer->Create();
// 사용할 텍스처 세팅할게
_texture1 = make_shared<Texture>(device);
_texture1->Create(L"Skeleton.png");
// 샘플링은 이렇게 해
_samplerState = make_shared<SamplerState>(device);
_samplerState->Create();
}
GameObject::~GameObject()
{
}
void GameObject::Update()
{
_localPosition.x += 0.001f;
Matrix matScale = Matrix::CreateScale(_localScale / 3);
Matrix matRotation = Matrix::CreateRotationX(_localRotation.x);
matRotation *= Matrix::CreateRotationY(_localRotation.y);
matRotation *= Matrix::CreateRotationZ(_localRotation.z);
Matrix matTranslation = Matrix::CreateTranslation(_localPosition);
Matrix matWorld = matScale * matRotation * matTranslation; // SRT
_transformData.matWorld = matWorld;
_constantBuffer->CopyData(_transformData);
}
void GameObject::Render(shared_ptr<Pipeline> pipeline)
{
PipelineInfo info;
info.inputLayout = _inputLayout;
info.vertexShader = _vertexShader;
info.pixelShader = _pixelShader;
info.rasterizerState = _rasterizerState;
info.blendState = _blendState;
pipeline->UpdatePipeline(info);
pipeline->SetVertexBuffer(_vertexBuffer);
pipeline->SetIndexBuffer(_indexBuffer);
pipeline->SetConstantBuffer(0, SS_VertexShader, _constantBuffer);
pipeline->SetTexture(0, SS_PixelShader, _texture1);
pipeline->SetSamplerState(0, SS_PixelShader, _samplerState);
pipeline->DrawIndexed(_geometry->GetIndexCount(), 0, 0);
}
아직 완벽히 분리되지 않았지만 완전 깔끔해졌다.
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 엔진 구조로 제작하기 - Component, MeshRenderer (2) | 2024.09.05 |
---|---|
[DirectX] 프레임워크 제작기 - Transform (0) | 2024.09.04 |
[DirectX] 프레임워크 제작기 - Graphics, IA, Geomotry (1) | 2024.09.02 |
[DirectX] 행렬 - SRT 변환 행렬과 좌표계 변환 행렬 및 예제 (0) | 2024.08.31 |
[DirectX] DirectX 11 입문하기 - Constant Buffer와 State (0) | 2024.08.29 |