지금까지 짠 코드는 하나의 클래스와 헤더파일에 전부 몰려있기 때문에 이를 분리해서 프레임워크를 제작해보자.
이것만 해도 2D 게임을 만드는 주축이 된다고 해도 무방하다.
파일 구조는 다음과 같다.
Graphics
Graphics 라는 클래스는 그래픽스와 관련된 모든 디바이스 를 관리하는 클래스가 될 것이다. 가장 공통적인 내용을 가지고 있을 것이다.
#pragma once
class Graphics
{
public:
Graphics(HWND hwnd);
~Graphics();
void RenderBegin();
void RenderEnd();
ComPtr<ID3D11Device> GetDevice() { return _device; }
ComPtr<ID3D11DeviceContext> GetDeviceContext() { return _deviceContext; }
private:
void CreateDeviceAndSwapChain();
void CreateRenderTargetView();
void SetViewport();
private:
HWND _hwnd;
uint32 _width = 0;
uint32 _height = 0;
private:
// Device & SwapChain
ComPtr<ID3D11Device> _device = nullptr;
ComPtr<ID3D11DeviceContext> _deviceContext = nullptr;
ComPtr<IDXGISwapChain> _swapChain = nullptr;
/// RTV
ComPtr<ID3D11RenderTargetView> _renderTargetView;
// Misc
D3D11_VIEWPORT _viewport = { 0 };
float _clearColor[4] = { 0.f, 0.f, 0.f, 0.f };
};
#include "pch.h"
#include "Graphics.h"
Graphics::Graphics(HWND hwnd)
{
_hwnd = hwnd;
CreateDeviceAndSwapChain();
CreateRenderTargetView();
SetViewport();
}
Graphics::~Graphics()
{
}
void Graphics::RenderBegin()
{
_deviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);
_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), _clearColor);
_deviceContext->RSSetViewports(1, &_viewport);
}
void Graphics::RenderEnd()
{
HRESULT hr = _swapChain->Present(1, 0);
CHECK(hr);
}
void Graphics::CreateDeviceAndSwapChain()
{
DXGI_SWAP_CHAIN_DESC desc;
ZeroMemory(&desc, sizeof(desc));
{
desc.BufferDesc.Width = GWinSizeX;
desc.BufferDesc.Height = GWinSizeY;
desc.BufferDesc.RefreshRate.Numerator = 60;
desc.BufferDesc.RefreshRate.Denominator = 1;
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 1;
desc.OutputWindow = _hwnd;
desc.Windowed = true;
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
}
HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
nullptr,
0,
D3D11_SDK_VERSION,
&desc,
_swapChain.GetAddressOf(),
_device.GetAddressOf(),
nullptr,
_deviceContext.GetAddressOf()
);
CHECK(hr);
}
void Graphics::CreateRenderTargetView()
{
HRESULT hr;
ComPtr<ID3D11Texture2D> backBuffer = nullptr;
hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf());
CHECK(hr);
_device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTargetView.GetAddressOf());
CHECK(hr);
}
void Graphics::SetViewport()
{
_viewport.TopLeftX = 0.f;
_viewport.TopLeftY = 0.f;
_viewport.Width = static_cast<float>(GWinSizeX);
_viewport.Height = static_cast<float>(GWinSizeY);
_viewport.MinDepth = 0.f;
_viewport.MaxDepth = 1.f;
}
이렇게 디바이스와 스왑체인을 생성하고 백 버퍼를 지정하거나 뷰 포트를 지정하는 일 들을 관리하는 Grahpics라는 클래스를 만들 수 있다.
InputAssembler
VerxtexBuffer와 IndexBuffer 그리고 inputLayout을 위주로 코드를 묶어보자.
어떤 도형의 기하학적인 표현을 통해 버퍼에 전달이 됐다면 IA 단계에서 버퍼를 이용할 것이다. 이를 코드로 묶자.
먼저 VertexBuffer는 다음과 같다. 기존에 Geometry를 생성하면서 같이 버퍼를 만들었는데 이제 Create 함수를 통해 한번에 정점에 대한 버퍼를 생성할 수 있다.
#pragma once
class VertexBuffer
{
public:
VertexBuffer(ComPtr<ID3D11Device> device);
~VertexBuffer();
ComPtr<ID3D11Buffer> GetComPtr() { return _vertexBuffer; }
uint32 GetStride() { return _stride; }
uint32 GetOffset() { return _offset; }
uint32 GetCount() { return _count; }
template<typename T>
void Create(const vector<T>& vertices)
{
_stride = sizeof(T);
_count = static_cast<uint32>(vertices.size());
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(sizeof(Vertex) * vertices.size());
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = vertices.data();
HRESULT hr = _device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
CHECK(hr);
}
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11Buffer> _vertexBuffer;
uint32 _stride = 0;
uint32 _offset = 0;
uint32 _count = 0;
};
이제 IndexBuffer이다. 이걸 사용하는 이유는 간단하게 중복되는 정점의 개수를 줄여 메모리를 아까기 위함이다.
#pragma once
class IndexBuffer
{
public:
IndexBuffer(ComPtr<ID3D11Device> device);
~IndexBuffer();
ComPtr<ID3D11Buffer> GetComPtr() { return _indexBuffer; }
uint32 GetStride() { return _stride; }
uint32 GetOffset() { return _offset; }
uint32 GetCount() { return _count; }
void Create(const vector<uint32>& indices);
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11Buffer> _indexBuffer;
uint32 _stride = 0;
uint32 _offset = 0;
uint32 _count = 0;
};
#include "pch.h"
#include "IndexBuffer.h"
IndexBuffer::IndexBuffer(ComPtr<ID3D11Device> device)
:_device(device)
{
}
IndexBuffer::~IndexBuffer()
{
}
void IndexBuffer::Create(const vector<uint32>& indices)
{
_stride = sizeof(uint32);
_count = static_cast<uint32>(indices.size());
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
desc.ByteWidth = (uint32)(_stride * _count);
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = indices.data();
HRESULT hr = _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf());
CHECK(hr);
}
InpytLayout은 우리가 넘겨주는 vertexBuffer를 어떤 쉐이더에서 어떻게 분석할 것인지에 대한 정의를 설정해주는 역할이었다.
#pragma once
class InputLayout
{
public:
InputLayout(ComPtr<ID3D11Device> device);
~InputLayout();
ComPtr<ID3D11InputLayout> GetComPtr() { return _inputLayout; }
void Create(const vector<D3D11_INPUT_ELEMENT_DESC>& descs, ComPtr<ID3DBlob> blob);
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11InputLayout> _inputLayout;
};
#include "pch.h"
#include "InputLayout.h"
InputLayout::InputLayout(ComPtr<ID3D11Device> device)
:_device(device)
{
}
InputLayout::~InputLayout()
{
}
void InputLayout::Create(const vector<D3D11_INPUT_ELEMENT_DESC>& descs, ComPtr<ID3DBlob> blob)
{
const int32 count = static_cast<int32>(descs.size());
_device->CreateInputLayout(descs.data(), count, blob->GetBufferPointer(), blob->GetBufferSize(), _inputLayout.GetAddressOf());
}
Geometry
기하학적인 정보들은 물체마다 고유하게 가지고 있을 것이다. 그렇기 때문에 Geometry를 따로 관리하기 위해 파일을 관리하자. 지금은 간단한 사각형만 다루고 있지만 3D 게임이라면 3D 모델을 읽어와 분석해서 가지고 있을 것이다.
#pragma once
template<typename T>
class Geometry
{
public:
Geometry()
{
}
~Geometry()
{
}
uint32 GetVertexCount() { return static_cast<uint32>(_vertices.size()); }
void* GetVertexData() { return _vertices.data(); }
const vector<T>& GetVertices() { return _vertices; }
uint32 GetIndexCount() { return static_cast<uint32>(_indices.size()); }
void* GetIndexData() { return _indices.data(); }
const vector<uint32>& GetIndices() { return _indices; }
void AddVertex(const T& vertex) { _vertices.push_back(vertex); }
void AddVertices(const vector<T>& vertices) { _vertices.insert(_vertices.end(), vertices.begin(), vertices.end()); }
void SetVertices(const vector<T>& vertices) { _vertices = vertices; }
void AddIndex(uint32 index) { _indices.push_back(index); }
void AddIndices(const vector<uint32>& indices) { _indices.insert(_indices.end(), indices.begin(), indices.end()); }
void SetIndices(const vector<uint32>& indices) { _indices = indices; }
private:
vector<T> _vertices;
vector<uint32> _indices;
};
#pragma once
#include "Geometry.h"
#include "VertexData.h"
class GeometryHelper
{
public:
static void CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry);
static void CreateRectangle(shared_ptr<Geometry<VertexColorData>> geometry, Color color);
};
#include "pch.h"
#include "GeometryHelper.h"
void GeometryHelper::CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry)
{
vector<VertexTextureData> vertices;
vertices.resize(4);
vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
vertices[0].uv = Vec2(0.f, 1.f);
vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
vertices[1].uv = Vec2(0.f, 0.f);
vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
vertices[2].uv = Vec2(1.f, 1.f);
vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
vertices[3].uv = Vec2(1.f, 0.f);
geometry->SetVertices(vertices);
vector<uint32> indices = { 0,1,2,2,1,3 };
geometry->SetIndices(indices);
}
void GeometryHelper::CreateRectangle(shared_ptr<Geometry<VertexColorData>> geometry, Color color)
{
vector<VertexColorData> vertices;
vertices.resize(4);
vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
vertices[0].color = color;
vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
vertices[1].color = color;
vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
vertices[2].color = color;
vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
vertices[3].color = color;
geometry->SetVertices(vertices);
vector<uint32> indices = { 0,1,2,2,1,3 };
geometry->SetIndices(indices);
}
#pragma once
struct VertexTextureData
{
Vec3 position = { 0,0,0 };
Vec2 uv = { 0,0 };
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
struct VertexColorData
{
Vec3 position = { 0,0,0 };
Color color = { 0,0,0,0 };
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
#include "pch.h"
#include "VertexData.h"
vector<D3D11_INPUT_ELEMENT_DESC> VertexTextureData::descs =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
vector<D3D11_INPUT_ELEMENT_DESC> VertexColorData::descs =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 프레임워크 제작기 - Transform (0) | 2024.09.04 |
---|---|
[DirectX] 프레임워크 제작기 - Shader, Pipeline, GameObject (1) | 2024.09.03 |
[DirectX] 행렬 - SRT 변환 행렬과 좌표계 변환 행렬 및 예제 (0) | 2024.08.31 |
[DirectX] DirectX 11 입문하기 - Constant Buffer와 State (0) | 2024.08.29 |
[DirectX] DirectX 11 입문하기 - 텍스쳐와 UV (2) | 2024.08.28 |