오늘부터는 만들어둔 코드를 하나의 프레임워크로 만들어 볼 것이다.
1. Graphics
일단 만든코드를 기능에 따라 클래스로 나눠보자
Graphics 클래스에 선언해주자
이 클래스는
1. 디바이스 및 디바이스 컨텍스트 생성
- 단계: Direct3D 디바이스와 디바이스 컨텍스트를 생성
- 역할: GPU와 통신하고, 그리기 명령을 실행하며, 리소스를 생성하고 관리
2. 스왑 체인 생성
- 단계: 스왑 체인을 생성
- 역할: 렌더링된 이미지를 화면에 표시하는 버퍼를 관리
3. 렌더 타겟 뷰 생성
- 단계: 렌더 타겟 뷰를 생성
- 역할: 렌더링 결과를 출력할 대상(렌더 타겟)을 설정
4. 뷰포트 설정
- 단계: 뷰포트를 설정
- 역할: 화면에 그려질 영역을 설정
Graphics.h
#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();
/// <summary>
/// 버퍼를 묘사 Tag를 달아서 GPU에 알려주기 위함
/// </summary>
void CreateRenderTargetView();
/// <summary>
/// 뷰포트 묘사
/// </summary>
void SetViewport();
private:
HWND _hwnd;
uint32 _width = 0;
uint32 _height = 0;
private:
//Device & SwapChain
// I: 인터페이스 Comptr -> 스마트 포인터- 자동관리해줌, wrl에 있다.
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
//DXGI : DX와는 독립적으로 하위 수준 작업 관리 => 시스템,하드웨어 통신
//스왑체인-> 그리는 것과 보여주는 것 따로 해야 보통 방식 ->전면 후면 고속복사
ComPtr<IDXGISwapChain> _swapChain = nullptr;
///RTV 렌더타켓뷰
ComPtr<ID3D11RenderTargetView> _renderTargetView;
//Misc
D3D11_VIEWPORT _viewport = { 0 };
float _clearColor[4] = { 0.f,0.f,0.f,0.f };
};
Graphics.cpp
#include "pch.h"
#include "Graphics.h"
Graphics::Graphics(HWND hwnd)
{
_hwnd = hwnd;
// 초기화 순서 확인
_width = GWinSizeX;
_height = GwinSizeY;
CreateDeviceAndSwapChain();
CreateRenderTargetView();
SetViewport();
}
Graphics::~Graphics()
{
}
void Graphics::RenderBegin()
{
//여기다가 그릴것이다.
_deviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr); //OM: OUTPUT Mege
_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)); //다 0으로 초기화해줌 필요한 것만 따로 초기화
{
desc.BufferDesc.Width = _width; // 버퍼도 같은 크기로 800 x 600
desc.BufferDesc.Height = _height;
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 일종의 bool값
HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE, //어떤걸 쓸건지 -> gpu or software로 gpu
nullptr,
0,
nullptr, //지원하는 dx 버전 레벨 기본값은 내가 지원할 수 있는 상위버전 골라줌
0, //위에 배열 크기
D3D11_SDK_VERSION, //매크로
&desc,
_swapChain.GetAddressOf(), //**이면 주소값 가져오는 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()); //백버퍼 텍스처(PNG)로 가져오기
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>(_width);
_viewport.Height = static_cast<float>(_height);
_viewport.MinDepth = 0.f;
_viewport.MaxDepth = 1.f;
}
동일하게 작동하는 것을 볼 수 있다.
2.Input Assembler
InputAssembler에 해당하는 부분을 각각의 클래스로 만들어주자
일단 선언한 정점의 정보를 받아서 gpu쪽으로 넘겨주는
VertexBuffer에 해당하는 부분을 추출해서 클래스로 만들어주자
VertexBuffer.h
#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; //gpu만 read only
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(_stride*_count);
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = vertices.data(); //첫번째 시작주소 cpu값이 복사된다.
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;
};
VertexBuffer.cpp
#include "pch.h"
#include "VertexBuffer.h"
VertexBuffer::VertexBuffer(ComPtr<ID3D11Device> device)
:_device(device)
{
}
VertexBuffer::~VertexBuffer()
{
}
원래 클래스에서 VertexBuffer에 해당하는 부분도 바꿔주도록하자
Game.h
private:
//기하학적 도형 - cpu
vector<Vertex> _vertices;
VertexBuffer* _vertexBuffer;
Game.cpp
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
//_graphics=make_shared<Graphics>(hwnd):
_graphics = new Graphics(hwnd);
_vertexBuffer = new VertexBuffer(_graphics->GetDevice());
//삼각형 그리기 파트
CreateGeometry();
CreateVS();
CreateInputLayout();
CreatePS();
CreateRasterizerState();
CreateSamplerState();
CreateBlenderState();
CreateSRV();
CreateConstantBuffer();
}
void Game::Render()
{
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
}
void Game::CreateGeometry()
{
//정점버퍼
{
_vertexBuffer->Create(_vertices);
}
}
두번째로
정점의 인덱스정보를 담은 버퍼를 생성하는
IndexBuffer부분을 추출해서 클래스로 만들어주자
IndexBuffer.h
#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;
};
IndexBuffer.cpp
#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; //gpu만 read only
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
desc.ByteWidth = (uint32)(_stride * _count);
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = indices.data(); //첫번째 시작주소 cpu값이 복사된다.
HRESULT hr = _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf());
CHECK(hr);
}
클래스로 만든 부분을 수정해주자
Game.h
IndexBuffer* _indexBuffer;
Game.cpp
void Game::Init(HWND hwnd)
{
_indexBuffer = new IndexBuffer(_graphics->GetDevice());
}
void Game::Render()
{
_deviceContext->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
}
void Game::CreateGeometry()
{
//IndexBuffer
{
_indexBuffer->Create(_indices);
}
}
동일한 결과가 나온다.
마지막으로
쉐이더가 만들어진 상태에서 정점버퍼에 있는 내용을 쉐이더에서 어떻게 해석하는지에 대한 정보를 담은 부분인
IndexLayout부분을 추출해서 클래스로 만들어주자
이때 정점정보와 레이아웃, 쉐이더상의 정점의 구조가 매핑이 알맞게 되어야한다.
InputLayout.h
#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;
};
InputLayout.cpp
#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());
//VS과정의 입력에 관련있어서 vsBlob을 매개변수로 해줌
_device->CreateInputLayout(descs.data(), count, blob->GetBufferPointer(), blob->GetBufferSize(), _inputLayout.GetAddressOf());
}
클래스로 만든 부분을 수정해주자
Game.h
InputLayout* _inputLayout;
Game.cpp
void Game::Init(HWND hwnd)
{
_inputLayout = new InputLayout(_graphics->GetDevice());
}
void Game::Render()
{
_deviceContext->IASetInputLayout(_inputLayout->GetComPtr().Get());
}
void Game::CreateInputLayout()
{
//입력에 대한 정보 ~바이트부터 뛰면 칼러인지
vector<D3D11_INPUT_ELEMENT_DESC> layout
{
{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
{"TEXCOORD",0,DXGI_FORMAT_R32G32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
};
_inputLayout->Create(layout,_vsBlob);
}
동일한 결과가 나온다.
3.Geometry
하드코딩된 정점이 아닌 기하학적인 정보와 모형을 받아주는 Geometry 부분을 만들어보자
처음으로 기하학적인 정보를 받을 Geometry 클래스와
기하학적인 도형을 만들어주는 GeometryHelper클래스,
처음으로 기하학적인 정보 vertices와 Indices를 가지고 있을 Geometry 클래스를 만들자
이때 제네릭 벡터로 정점을 정의해서 어떤 타입이든 받을 수 있게하고 정점의 개수가 동적으로 바뀔 수 있게 함수를 선언해준다.
Geometry.h
#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;
};
Geometry.cpp
그리고 정점의 struct 정보를 가지고 있을 VertexData를 만들어주자
앞으로 정점에 대한 struct정보가 필요할 경우 추가해줄 곳이 될 것이다.
각 Stuct에서 desc를 가지고 있어서 각 정정 입력에 대한 InputLayout, 쉐이더에서 필요한 정보를 줄 수 있도록 하자.
VertexData.h
#pragma once
struct VertexTextureData
{
Vec3 position = { 0,0,0 }; //12바이트 0부터시작
Vec2 uv = { 0,0 };
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
struct VertexColorData
{
Vec3 position = { 0,0,0 }; //12바이트 0부터시작
Color color = { 0,0,0,0 }; //12부터시작
static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};
VertexData.cpp
#include "pch.h"
#include "VertexData.h"
static 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},
};
static 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},
};
그리고 마지막으로 사각형과 같은 기본적인 도형을 만드는 함수들이 포함된 GeometryHelper 클래스를 만들어주자
GeometryHelper.h
#pragma once
class GeometryHelper
{
public:
static void CreateRectangle(shared_ptr<Geometry<VertexColorData>> geometry,Color color);
static void CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry);
};
GeometryHelper.cpp
#include "pch.h"
#include "GeometryHelper.h"
void GeometryHelper::CreateRectangle(shared_ptr<Geometry<VertexColorData>> geometry, Color color)
{
vector<VertexColorData> vertices;
_vertices.resize(4);
//13 -> 012
//02 -> 213
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);
}
void GeometryHelper::CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry)
{
vector<VertexTextureData> vertices;
_vertices.resize(4);
//13 -> 012
//02 -> 213
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);
}
이제 우리의 메인코드인 Game 클래스를 고쳐주자
Game.h
private:
shared_ptr<Graphics> _graphics;
//기하학적 도형 - cpu
//vector<Vertex> _vertices;
//vector<uint32> _indices;
shared_ptr<Geometry<VertexTextureData>> _geometry;
shared_ptr<VertexBuffer> _vertexBuffer;
//인덱스버퍼 - 이거도 Geometry에 포함
shared_ptr<IndexBuffer> _indexBuffer;
shared_ptr<InputLayout> _inputLayout;
Game.cpp
void Game::Init(HWND hwnd)
{
_graphics = make_shared<Graphics>(hwnd);
_vertexBuffer = make_shared<VertexBuffer>(_graphics->GetDevice());
_indexBuffer = make_shared<IndexBuffer>(_graphics->GetDevice());
_inputLayout = make_shared<InputLayout>(_graphics->GetDevice());
_geometry = make_shared<Geometry<VertexTextureData>>();
}
void Game::Render()
{
uint32 stride = sizeof(VertexTextureData);
}
void Game::CreateGeometry()
{
//정점정보
GeometryHelper::CreateRectangle(_geometry);
//정점버퍼
_vertexBuffer->Create(_geometry->GetVertices());
//IndexBuffer
_indexBuffer->Create(_geometry->GetIndices());
}
이렇게하면 동일한 결과가 나오게 된다.
'게임공부 > Directx11' 카테고리의 다른 글
[Directx11][C++]11. 프레임워크 제작3(Pipeline) (0) | 2024.08.12 |
---|---|
[Directx11][C++]10. 프레임워크 제작2(Shader,Rasterizer) (0) | 2024.08.08 |
[Directx11]8. Simple Math (0) | 2024.07.15 |
[Directx11]7. RasterizerState, SampleState, BlendState (0) | 2024.07.10 |
[Directx11]6. Constant Buffer(상수 버퍼) (0) | 2024.07.09 |