지난 시간에 분리한 device나 deviceContext는 많이 쓰는 애들이니까 나중엔 전역으로 만들어서 언제 어디서나 편하게 접근할 수 있게 하는 걸 고려할 수도 있다.
그다음 할 것은
Geometry, VS와 관련된 부분들 이렇게 2가지에 접근을 했다.
어떻게 분해해야 할까?
먼저 Game::CreateGeomerty에서 Gerometry라고 한 건 기하학적인 걸 만들어서야.
엄밀이 말하면 vertex buffer와 geometry는 다르다. geometry는 물체에 대한 정보, VertexBuffer는 GPU에 넘겨주는 버퍼를 얘기한다.
기하학적인 정보를 담기 위해 Vertex라는 구조체를 만들어서 사용했는데 이게 맞을까?
이게 셰이더랑 연관이 있어서 uv 좌표를 매핑해서 텍스쳐를 붙이는 용도가 아니라면 예전 버전처럼 color를 사용하는 것이 일반적일 수 있다.
struct Vertex
{
Vec3 position;
// Color color;
Vec2 uv;
};
그러다 보니까 Vertex라는 애를 여러 버전 중 하나 중 골라서 만들어 줘야 하는데 그것에 따라서 InputLayout이 정해지게 된다.
InputLayout도 셰이더와 관련이 있다.
Buffer를 분해하고, Geometry, InputLayout까지 포함해서 예쁘게 만들어 주는 것을 고려해야 한다.
순서는 상관이 없는데
VertexBuffer, IndexBuffer, InputLayout 이 시리즈들을 먼저 작업을 해본다.
Buffer는 결국 위치상 어디에 들어가야 하는지 생각을 해보면,
Geometry에서 기하학적인 걸 만들었다 가정을 하면 Game::Render를 보면 InputAssembler 과정에서 이 버퍼들을 사용했어. 결국 InputAssembler 단계에서 역사가 이루어진다.
01. InputAssembler 필터에 VertexBuffer, IndexBuffer, InputLayout 클래스를 생성한다.
1. VertexBuffer 이전
먼저 VertexBuffer를 생각해 보자.
Game::CreateGeometry에서 VertexData를 만들어 주고, 이걸 이용해서 VertexBuffer를 만들어 놨는데,
CreateBuffer를 호출하기 위해선 _graphics->GetDevice()로 device를 사용할 필요가 있다.
이 device를 어디서나 접근할 수 있게 싱글톤으로 만들어 주는 것도 방법일 수 있고, 그게 싫다면 VertexBuffer 만들 때 그 아이를 인자로 생성자에서 받아서 사용해 주면 되기 때문에 둘 중 어느 방법으로도 상관없다.
여기선 생성자로 받아준다.
VertexBuffer(ComPtr<ID3D11Device> device);
Game.h에서 _vertexBuffer를 복사하고, 주석 처리를 하고, VertexBuffer.h에 붙여 넣는다.
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11Buffer> _vertexBuffer = nullptr;
ComPtr은 굳이 초기화할 필요가 없다. 스마트 포인터 같은 것이기 때문에.
uint32 _stride = 0;
uint32 _offset = 0;
uint32 _count = 0;
이걸 미리 만들어 주는데, 나중에 꺼내서 사용하게 될 것이다.
Get함수들도 정의해 준다.
ComPtr<ID3D11Buffer> GetComPtr() { return _vertexBuffer; }
uint32 GetStride() { return _stride; }
uint32 GetOffset() { return _offset; }
uint32 GetCount() { return _count; }
vertexBuffer를 사용하는 것을 보면 GetAddressOf로 사용하는 곳이 있지만, 나중에 가면 만들 때마다 내용들을 꺼내서 사용할 일이 생기기 때문에 그 부분들을 만들어 주게 될 것이다.
여기서 말하는 Vertex는
struct Vertex
{
Vec3 position;
// Color color;
Vec2 uv;
};
요 아이였어.
나중에 다양한 형태의 정점이 있을 것이라서 그것에 따라 맞춰줘야 한다.
지금은 제한적이기 때문에 temeplate으로 만들어 주는 것을 고려할 수 있다.
template을 넣을 때는 구현을 .cpp에 하면 에러가 난다. template은 .h에서 구현을 끝내는 게 편하다.
변수로 선언했던 _stride와 _count의 값을 채운다. ByteWidth도 이 값을 사용해 구하게 한다.
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; // Input assembler에서 건내 주는 vertex buffer를 만들고 있는 것이기 때문
desc.ByteWidth = (uint32)(_stride * _count);
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = _vertices.data(); // &_vertices[0];와 같은 의미
HRESULT hr = _graphics->GetDevice()->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
// 설정한 값 desc, data에 의해가지고 GPU 쪽에 버퍼가 만들어 지면서 CPU의 메모리에서 들고 있었던 vertices에 관한 정보가
// 복사가 되어 넘어 간다. 그 다음 부터는 GPU의 메모리만 Read Only로 사용하겠구나 예측이 가능하다.
CHECK(hr);
}
이 함수를 호출하면 _vertexBuffer가 채워진다
빌드를 하면 에러가 난다.
_graphics가 선언되지 않았다고 에러가 난다. graphics를 넘겨받아서 사용하거나 VertexBuffer에서 처음에 device를 인자로 받아서 만드는 걸 고려할 수 있다.
VertexBuffer::VertexBuffer(ComPtr<ID3D11Device> device)
: _device(device)
{
}
이렇게 바로 _device를 채우고,
Create에서
HRESULT hr = _graphics->GetDevice()->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
를
HRESULT hr = _device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
이렇게 바로 _device에서 호출하게 바꿔준다.
빌드를 하면 에러가 줄어든다.
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;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // Input assembler에서 건내 주는 vertex buffer를 만들고 있는 것이기 때문
desc.ByteWidth = (uint32)(_stride * _count);
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = vertices.data(); // &_vertices[0];와 같은 의미
HRESULT hr = _device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
// 설정한 값 desc, data에 의해가지고 GPU 쪽에 버퍼가 만들어 지면서 CPU의 메모리에서 들고 있었던 vertices에 관한 정보가
// 복사가 되어 넘어 간다. 그 다음 부터는 GPU의 메모리만 Read Only로 사용하겠구나 예측이 가능하다.
CHECK(hr);
}
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11Buffer> _vertexBuffer = nullptr;
uint32 _stride = 0;
uint32 _offset = 0;
uint32 _count = 0;
};
Create에서 인자로 vertices를 받아 주고 있는데 기존 Game::CreateGeometry에서 가져올 때는 _vertices였어서 수정해 주는 것을 빼먹지 않게 주의한다.
다른 부분에서 에러가 나고 있는데,
Game.cpp에서 사용하던 VertexBuffer를 날려놔서 발생한 것이다.
pch.h에 #include "VertexBuffer.h"를 추가한다.
이렇게 언제 어디서든 사용할 수 있게 해 주고
Game.h로 가서
VertexBuffer* _vertexBuffer;
//ComPtr<ID3D11Buffer> _vertexBuffer = nullptr;
이렇게 추가해 준다. 원래 ComPtr로 되어 있던 부분을 이 방식으로 바꾸게끔 유도를 해주면 된다.
Game::Init에도 _vertexBuffer를 추가해 주면 된다. _
_vertexBuffer = new VertexBuffer(_graphics->GetDevice());
이렇게 해주면 VertexBuffer생성자에서 _device를 저장할 수 있게 된다.
그리고 Game::CreateGeometry에서
_vertexBuffer->Create(_vertices);
이렇게 만들어 주면 된다.
그리고 _vertexBuffer를 사용하던 곳을 보면
Render의 IA에서
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
이렇게 GetComPtr을 건네주면, 만들어 줬던 _vertexBufferf를 받아서 사용하게 된다.
여기까지 실행해 보면 똑같이 잘 실행이 된다.
이런 식으로 야금야금 진행하게 된다.
2. IndexBuffer 이전
IndexBuffer를 만들어 본다 가정
원래 어떻게 했냐 보면
Game::CreateGeometry에서 구조를 만들고, 정점 버퍼, 인덱스 버퍼를 만들었어.
#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 = nullptr;
uint32 _stride = 0;
uint32 _offset = 0;
uint32 _count = 0;
};
vertexBuffer를 참고하여 만들어 준 다음에 함수들을 구현해 준다.
#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; // Input assembler에서 건내 주는 index buffer를 만들고 있는 것이기 때문
desc.ByteWidth = (uint32)(_stride*_count);
D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = indices.data(); // &_indices[0];와 같은 의미
HRESULT hr = _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf());
CHECK(hr);
}
CreateBuffer를 하면 정보를 토대로 _indexBuffer가 만들어지면서, 채워질 것이고,
사용할 때는 GetComPtr을 써서 _indexBuffer를 꺼내서 사용하면 된다.
Game.h에서 VertexBuffer 밑에 IndexBuffer도 구현해 주면 된다.
pch.h에 IndexBuffer.h를 넣어준다.
#include "IndexBuffer.h"
Game.h에
IndexBuffer* _indexBuffer;
를 선언하고,
ComPtr <ID3 D11 Buffer> _indexBuffer = nullptr;
는 삭제한다.
Game::Init에 가서
_indexBuffer = new IndexBuffer(_graphics->GetDevice());
이렇게 IndexBuffer를 만들어 줬으니까,
Game::CreateGeometry에서 IndexBuffer를 만들어주는 부분을 삭제하고, 그 자리에
// IndexBuffer
{
_indexBuffer->Create(_indices);
}
이렇게 해 줄 수 있다.
빌드를 하면 에러가 난다.
_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);
을
_deviceContext->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
로 수정하면 된다.
실행하면 별 다른 문제 없이 실행이 된다.
IndexBuffer를 이전하는 데 성공했다.
3. InputLayout 이전
마지막으로 InputLayout을 옮겨야 하는데,
얘는 셰이더가 먼저 만들어져야 한다는 걸 볼 수 있다.
InputLayout.h에 필요한 것들을 옮긴다.
struct.h의
struct Vertex
{
Vec3 position;
// Color color;
Vec2 uv;
};
랑
Game::CreateInputLayout의
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}, // 12바이트 부터 컬러가 시작 offset
};
이것이 매핑되어야 한다.
만약 UV가 아니라 Color라면 그 부분이 바뀌어야 한다.
그리고 Default.hlsl의 셰이더까지
이렇게 3 총사가 서로 아다리가 맞아야 된다고 볼 수 있다.
void Create(const vector<D3D11_INPUT_ELEMENT_DESC>& descs, ComPtr<ID3DBlob> blob);
InputLayout클래스에서 Create를 할 때 vector를 이용해서 D3D11_INPUT_ELEMENT_DESC를 받아주고, 그리고 Game::CreateVS에서 shader 정보를 넣어준 _vsBlob도 받아준다.
vector로 받아준 이유는
{"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},
이것의 종류의 개수가 몇 개가 될지 모르기 때문이다.
#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 = nullptr;
};
이렇게 해주고, 함수들을 정의한다.
일단 생성자에 _device를 채우는 부분을 먼저 만든다.
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());
//세번째 인자로 Shader에 대한 정보를 받아주고 있어.
// 그래서 Shader를 먼저 만들어서 로드를 해야겠구나 알 수 있는 거.
}
pch.h에 가서
#include "InputLayout.h"
를 추가해서 언제 어디서든 사용할 수 있게 해 준 다음에
Game.h에 가서
ComPtr<ID3D11InputLayout> _inputLayout = nullptr;
은 삭제하고,
InputLayout* _inputLayout;
이걸로 사용할 수 있게 해 준다.
Game::Init으로 가서
_inputLayout = new InputLayout(_graphics->GetDevice());
그리고 CreateInputLayout에 가서
vector로 받아 주기로 했으니까 vector로 바꿔주고
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);
}
이렇게 바꿔주면 동일하게 동작을 하지 않을까 생각을 할 수 있다.
Game::Render에서 에러가 나는 부분은 GetComPtr를 쓰게 해 주면 된다.
_deviceContext->IASetInputLayout(_inputLayout->GetComPtr().Get());
실행하면 정상적으로 잘 실행된다.
4. 맺음말
여기까지 IA 단계까지 이전을 한 것이다.
Game.h에 ComPtr이 없게 하고, 래핑 하는 클래스로 바꿔준 다음에, 그 클래스끼리도 한번 더 묶어 줄 것이다. 물체에 종속적인 부분도 있고, 어떤 애들은 시스템 상에서 하나만 만들면 계속 사용할 수 있기 때문에, 래핑을 하면서 정리하는 부분이 들어갈 것이다.
어떤 부분을 어떻게 사용하여 특정 부분을 만드는데 쓰겠다 하는 게 핵심이다.
Game::Render의 RendBegin과 RenderEnd 사이에 있는 모든 것들은 하나의 물체에 종속적인 개념이다.
정리, 카메라, 애니 넣는 부분 넘어가고,
3D로 넘어가면 Shader를 많이 조작하게 될 거야. 이 부분이 핵심이다.
'DirectX' 카테고리의 다른 글
20. 프레임워크 제작_Shader (0) | 2024.01.03 |
---|---|
19. 프레임워크 제작_Geometry (0) | 2024.01.02 |
17. 프레임워크 제작_Graphics (0) | 2023.12.30 |
16. SimpleMath 실습 (0) | 2023.12.29 |
15. Screen 변환 행렬 (0) | 2023.12.22 |
댓글