DirectX

19. 프레임워크 제작_Geometry

devRiripong 2024. 1. 2.
반응형

1. shared_ptr로 바꿔주기 

shared_ptr이 안되던 걸 해결하기 위해 pch.h의 //STL에

#include <memory>
#include <iostream>


를 추가한다.

Game.h로 가서 

Graphics* _graphics;

에서

shared_ptr<Graphics> _graphics;

이걸로 바꿔준다.

생 포인터를 사용하면 안 된다. 

VertexBuffer* _vertexBuffer;

이것도 

shared_ptr<VertexBuffer> _vertexBuffer;

이렇게

IndexBuffer* _indexBuffer;
InputLayout* _inputLayout;

이것도

shared_ptr<IndexBuffer> _indexBuffer; 
shared_ptr<InputLayout> _inputLayout;


이렇게 바꾼다.

Game::Init으로 가서

_graphics = new Graphics(hwnd); 
_vertexBuffer = new VertexBuffer(_graphics->GetDevice()); 
_indexBuffer = new IndexBuffer(_graphics->GetDevice()); 
_inputLayout = new InputLayout(_graphics->GetDevice());


이것도

_graphics = make_shared<Graphics>(hwnd); 
_vertexBuffer = make_shared<VertexBuffer>(_graphics->GetDevice());
_indexBuffer = make_shared<IndexBuffer>(_graphics->GetDevice());
_inputLayout = make_shared<InputLayout>(_graphics->GetDevice());


이렇게 바꿔준다.

이걸 한다고 만능은 아니고 사이클 문제를 염두하면서 작업을 해야 한다. 

 


2. Geometry란 


본격적으로 Geometry를 다뤄보자.

결국 

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);
}


여기의 InputLayout을 설정하는 부분과 

 

void Game::CreateGeometry()
{
// VertexData - CPU 메모리에 있는 거 
// 1 3
// 0 2
    {
    _vertices.resize(4);

    _vertices[0].position = Vec3(-0.5f, -0.5f, 0.f); 
    _vertices[0].uv = Vec2(0.f, 1.f); 
    // _vertices[0].color = Color(1.f, 0.f, 0.f, 1.f); 

    _vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
    _vertices[1].uv = Vec2(0.f, 0.f);
    // _vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);

    _vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
    _vertices[2].uv = Vec2(1.f, 1.f);
    // _vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);

    _vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
    _vertices[3].uv = Vec2(1.f, 0.f);
    // _vertices[3].color = Color(1.f, 0.f, 0.f, 1.f);
    }

vertex data와

 

// Index
{
    _indices = { 0, 1, 2, 2, 1, 3 }; // 시계방향 골랐으면 시계방향 유지해야 한다. 삼각형 2개 만들어 줬다.
}


index data를 설정하는 부분은

물체마다 고유하게 있을 수 있을거다. 

이것을 Geometry라고 부르게 될 것이다. 

나중에는 3D 모델을 받아서 읽어서 정점을 받게 해 줄 것이다.

지금 단계에서는 간단한 사각형인데, 이것도 Geometry라는 클래스로 빼주는 게 깔끔하다. 

 

3. Geometry 클래스 

 

00. Geometry 필터에 Geometry, GeometryHelper, VertexData 클래스를 추가한다. 

Geometry는 기하학적인 도형을 표현한다고 볼 수 있다. 
Game.h에서 보면 

vector<Vertex> _vertices; 
vector<uint32> _indices;

라고 볼 수 있다. 
이 두 가지가 세트로 묶여서 어떻게 생겼는지 나타낸다. 
Geometry로 이전한다. 

Vertex는 여러개가 되거나 구성요소 바뀔 수 있기 때문에 이렇게 하드 코딩을 하면 안 된다.  
template 처리를 한다.

template<typename T>
class Geometry
{
public: 

private: 
    vector<T> _vertices;
    vector<uint32> _indices; 
};

이렇게 template 처리를 하는 순간 .h 안에서 처리를 해줘야 한다.

일단 vertex 정보랑 index 정보를 채우는 두 가지가 있어야 한다.
그리고, vertex 정보가 몇개인지에 따라서 어떻게 그린다거나 하는 게 다른 인자에도 들어갈 수 있기 때문에 이걸 좀 더 포괄적으로 만들어 보자면, 

Vertex를 하나씩 넣는 방법이 있고, 원래 하는 것처럼 하나의 완성된 벡터를 건네주면 한 번에 세팅하는 방법이 있고, vertices내용을 추가해 달라는 버전도 만들 수 있다. 

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; }

이렇게 3총사를 만들어 준다.

이런 느낌으로 한 번 더 만들어 줄 건데, index 도 마찬가지로 해준다.

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; }


남은 건, Get하는 거 

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; }

 

정리하면 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라는 클래스라고 볼 수이다. 

 

4. VertexData 클래스


그럼 VertexData는 무엇을 하는 것일까? 


Struct.h의 

struct Vertex
{
    Vec3 position; 
    // Color color; 
    Vec2 uv; 
};


를 VertexData.h에 옮겨 준다. 나중에 여러 버전이 생길 것이기 때문에 일단 아래와 같이 두 종류로 만들어 준다.

#pragma once

struct VertexTextureData
{
    Vec3 position = { 0, 0, 0 };
    Vec2 uv = { 0, 0 };
};

struct VertexColorData
{
    Vec3 position = { 0, 0, 0 };
    Color color = { 0, 0, 0, 0 };
};


얘를 굳이 만들어준 이유 중 하나는 
이 VertexData.h에서 만들어 준 이 포멧 자체와 

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);
}

이 InputLayout이 묶여 있다. 

 

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},
};

이게 정확하게  VertexData.h의

struct VertexTextureData
{
    Vec3 position = { 0, 0, 0 };
    Vec2 uv = { 0, 0 };
};

이것과 매핑이 되어야 한다.

VertexTextureData에 static으로 만들어 주는 방법도 있다. 

struct VertexTextureData
{
    Vec3 position = { 0, 0, 0 };
    Vec2 uv = { 0, 0 }; 

    static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};


VertexTextureData에 해당하는 묘사 자체를 static으로 만들어 준다는 콘셉트이다.

VertexData.cpp에 가서 변수를 만들어 줘야 하는데 

vector<D3D11_INPUT_ELEMENT_DESC> VertexTextureData::descs = 
{

}


이걸 초기화를 해줘서 밀어 넣어주면 된다. 

Game::CreateInputLayout의 내용들을 복사해 주면

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, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};

묘사가 되는 거다.


offset값을 이렇게 매번 계산해도 되긴 하지만, 

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},
};

이렇게 D3D11_APPEND_ALIGNED_ELEMENT를 넣으면 알아서 계산을 해준다.

POSITION과 TEXCOORD를 원했던 VertexTextureData의 position과 uv를 묘사하는 INPUT_ELEMENT_DESC이라고 볼 수 있는 것이다. 

 

VertexData.h에 

struct VertexColorData
{
	Vec3 position = { 0, 0, 0 };
	Color color = { 0, 0, 0, 0 };

	static vector<D3D11_INPUT_ELEMENT_DESC> descs;
};

이걸 추가해 주고

 

Vertex.cpp에 마찬가지로 하나를 더 복사해서 

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},
};


이렇게 VertexData.h에 struct 2개를 만들어 준 다음에 그것에 맞게끔, descs를 연결해 줄 것이다. 그럼 VertexTextureData::descs나 VertexColorData::descs를 하면 사용하고 있는 아이에 맞는 descs을 사용할 수 있는 거. 

그 부분이 VertexData의 용도. 
앞으로는 VertexData.h에다가 사용할 정점에 대한 struct에 대한 정보를 늘려주면 된다. 

 

 

5. GeometryHelper 클래스

그다음에 GeometryHelper는 무엇을 하는 클래스일까 

나중에는 모델링 파일 FBX 파일을 파싱 해서 사용하겠지만, 기본 도형을 만들어주는 클래스를 하나 파주는 거다. 

#pragma once
class GeometryHelper
{
public:
	static void CreateRectangle(); 
};


어떤 형태로 만들어 줄 것인지를 전달해 줘야 한다. 
두 가지 버전 다 지원하게 해줘야 한다. 

static void CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry);


이렇게 해주고, 
pch.h에 추가한 삼총사를 넣어준다.

#include "Geometry.h"
#include "GeometryHelper.h"
#include "VertexData.h"


원래면 이렇게 추가해 주면 안 되고, 전방선언하고, cpp에서 추가하고 하는 이런 걸 꼼꼼히 챙겨야 하지만, 일단 빠르게 하기 위해 이렇게 해준다. 

GeometryHelper.h에 
VertexColorData를 받아주는 버전을 하나 더 만들어 준다.

static void CreateRectangle(shared_ptr<Geometry<VertexColorData>> geometry, Color color );


아직까지는 geometry가 채워지지 않은 상태다
원하는 정보들로 채워서 전달해 주게 될 것이다.
인자로 받는 버전으로 만들어 본 건데 크게 중요한 건 아니고 얼마든지 바꿔줄 수 있다. 
아웃풋으로 받아주는 버전으로도 만들 수 있고, 이름을 바꿔주는 것도 방법이다. 

#pragma once
class GeometryHelper
{
public:
    static void CreateRectangle(shared_ptr<Geometry<VertexColorData>> geometry, Color color );
    static void CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry); 
};

이렇게 만들어 주는 걸로 간다고 하면, 함수를 구현해 주면
Game::CreateGeometry의 내용을 복붙 해주면 된다. 

#include "pch.h"
#include "GeometryHelper.h"

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);
}

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);
}

정보에 맞게끔 버퍼가 만들어지는 것을 만들어 줬다. 

Geometry클래스는 인덱스 버퍼와 버텍스 버퍼를 들고 있는 아이고, 
그것을 채워주기 위해서 GeometryHelper나 FBX파일을 이용해서 정보를 채워주고, Geometry에 던져 주면서 사용한다. 

 

6. Geometry, VertexData, GeometryHelper 클래스 사용

이제 실습을 해보자면, 
Game.h에 가서 

shared_ptr<Geometry<VertexTextureData>> _geometry;

를 선언하고, 
이 물체를 만들고 싶다는 게 목표라면, 

Game.cpp의 Game::Init에 가서 

_geometry = make_shared<Geometry<VertexTextureData>>();

이렇게 얘를 만들어 주는 것부터 시작을 하고, 

Game::CreateGeometry로 가서 보면, 
원래는 여기서 VertexData를 채워줬었지만, 이건 엄밀히 보면 하드코딩이다.
이 부분이 방금 만든 것으로 대체된다. 

	// VertexData
	GeometryHelper::CreateRectangle();

인자로 무엇을 넘겨줘야 할까

일단 GeometryHelper.h에 

#include "Geometry.h"
#include "VertexData.h"

를 추가해 준다.

Game::Init에서 

	_geometry = make_shared<Geometry<VertexTextureData>>();

VertexColorData, VertexTextureData 둘 중 VertexTextureData를 넘겨받아 채워줬으니, 

void Game::CreateGeometry()
{
	// VertexData 
	GeometryHelper::CreateRectangle(_geometry);


이렇게 _geometry를 매개변수로 넣으면, 
shared_ptr <Geometry <VertexTextureData>> _geometry;
이 _geometry를 만들어 준 다음에, 채워주세요를 GeometryHelper를 통해가지고, 만들어주게 되는 거니까, 장황한 부분을 대체해 줄 수 있다. 

결국, CreateRectangle을 하면 인덱스를 채우고, 버텍스 데이터를 채우는 부분까지 1+1을 해줘서 채워주게 된다. 

그다음에 Game::CreateGeometry에서 VertexBuffer를 만들 때에는 

// VertexBuffer
{
	_vertexBuffer->Create(_geometry->GetVertices());
}


IndexBuffer도 마찬가지고, 

// IndexBuffer
{
	_indexBuffer->Create(_geometry->GetIndices()); 
}


이렇게 만들어주면 된다. 

void Game::CreateGeometry()
{
    // VertexData - CPU 메모리에 있는 거 
    GeometryHelper::CreateRectangle(_geometry);

    // VertexBuffer
    _vertexBuffer->Create(_geometry->GetVertices());

    // IndexBuffer
    _indexBuffer->Create(_geometry->GetIndices()); 
}


이 CreateGeometry도 한 바퀴 돌고 다른 곳에 옮겨서 작업을 할 것이다. 

빌드를 해보면 에러가 난다. 

void Game::Render()
{
    _graphics->RenderBegin();

    // IA - VS - RS - PS - OM
    {
    uint32 stride = sizeof(Vertex); 
    uint32 offset = 0;

sizeof(Vertex) 부분에서 에러가 나는데 

Vertex로 뭘 만들어야 할지가 명확하지 않기 때문에 이 부분도 바꿔줘야 한다. 

Game::Render 하단

_deviceContext->DrawIndexed(_indices.size(), 0, 0);

여기서도 에러가 나는데, vector <uint32> _indices; 를 이전해서 그런데 Gemometry가 알고 있을 거야. 

_deviceContext->DrawIndexed(_geometry->GetIndexCount(), 0, 0);

이렇게 바꿔주면 된다. 

Game::Render의 
uint32 stride = sizeof(Vertex); 
이 부분만 고쳐주면 된다. 
나중에는 사용한 것이 무엇이냐에 따라 대체해 주면 되지만,
지금은 VertexData에서 VertexTextureData를 사용하고 있으니까, 

uint32 stride = sizeof(VertexTextureData);

이렇게 바꿔주면 된다. 

이제 빌드가 된다. 

실행하면 동일하게 실행이 된다. 

Game::CreateInputLayout()도 VertexTextureData버전을 만든다고 한다면, 

void Game::CreateInputLayout()
{
	_inputLayout->Create(VertexTextureData::descs, _vsBlob);
}


이런 식으로 넘길 수가 있다. 

 

7. 맺음말 

이렇게 기하학적 도형을 만들고, 버퍼까지 만드는 부분이 세팅이 되었다. 

이제 셰이더와 관련된 부분들이 남았다. 

분리하면서 생각해야 한다. SRV랑 constantBuffer는 어떤 용도로 사용하는 것이었는지
어디에 꽂아서 인자로 넘기듯이 상용하는 애들이었는데 어떻게 만들지 고려를 해야 한다.

constant buffer도 PS에서 사용할 수 있고, texture도 VS에서 사용할 수 있다. 
Shader에 넘기는 인자들을 관리하는 것도 이번 시간에 유의 깊이 볼 부분 중 하나다. 

반응형

'DirectX' 카테고리의 다른 글

21. 프레임워크 제작_Pipeline  (0) 2024.01.05
20. 프레임워크 제작_Shader  (0) 2024.01.03
18. 프레임워크 제작_InputAssembler  (0) 2024.01.01
17. 프레임워크 제작_Graphics  (0) 2023.12.30
16. SimpleMath 실습  (0) 2023.12.29

댓글