모델 띄우기
이제 우리만의 format으로 만들었던 데이터를 다시 메모리로 올리는 과정을 해보자.
Model 이라는 클래스를 만들고 여기에 한 모델이 가지고 있는 mesh며 material이며 추후엔 애니메이션 까지 등등을 가지고 있고 이걸 클라이언트에서 불러와 게임 내 화면에 보여주게하는 것이 목표이다.
이전 포스팅에서 했던 Export를 반대로 하면 되는 것이다.
#pragma once
struct ModelBone
{
wstring name;
int32 index;
int32 parentIndex;
shared_ptr<ModelBone> parent; // Cache
Matrix transform;
vector<shared_ptr<ModelBone>> children; // Cache
};
struct ModelMesh
{
void CreateBuffers();
wstring name;
// Mesh
shared_ptr<Geometry<ModelVertexType>> geometry = make_shared<Geometry<ModelVertexType>>();
shared_ptr<VertexBuffer> vertexBuffer;
shared_ptr<IndexBuffer> indexBuffer;
// Material
wstring materialName = L"";
shared_ptr<Material> material; // Cache
// Bones
int32 boneIndex;
shared_ptr<ModelBone> bone; // Cache;
};
#pragma once
struct ModelBone;
struct ModelMesh;
class Model : public enable_shared_from_this<Model>
{
public:
Model();
~Model();
public:
void ReadMaterial(wstring filename);
void ReadModel(wstring filename);
uint32 GetMaterialCount() { return static_cast<uint32>(_materials.size()); }
vector<shared_ptr<Material>>& GetMaterials() { return _materials; }
shared_ptr<Material> GetMaterialByIndex(uint32 index) { return _materials[index]; }
shared_ptr<Material> GetMaterialByName(const wstring& name);
uint32 GetMeshCount() { return static_cast<uint32>(_meshes.size()); }
vector<shared_ptr<ModelMesh>>& GetMeshes() { return _meshes; }
shared_ptr<ModelMesh> GetMeshByIndex(uint32 index) { return _meshes[index]; }
shared_ptr<ModelMesh> GetMeshByName(const wstring& name);
uint32 GetBoneCount() { return static_cast<uint32>(_bones.size()); }
vector<shared_ptr<ModelBone>>& GetBones() { return _bones; }
shared_ptr<ModelBone> GetBoneByIndex(uint32 index) { return (index < 0 || index >= _bones.size() ? nullptr : _bones[index]); }
shared_ptr<ModelBone> GetBoneByName(const wstring& name);
private:
void BindCacheInfo();
private:
wstring _modelPath = L"../Resources/Models/";
wstring _texturePath = L"../Resources/Textures/";
private:
shared_ptr<ModelBone> _root;
vector<shared_ptr<Material>> _materials;
vector<shared_ptr<ModelBone>> _bones;
vector<shared_ptr<ModelMesh>> _meshes;
};
#include "pch.h"
#include "Model.h"
#include "Utils.h"
#include "FileUtils.h"
#include "tinyxml2.h"
#include <filesystem>
#include "Material.h"
#include "ModelMesh.h"
Model::Model()
{
}
Model::~Model()
{
}
void Model::ReadMaterial(wstring filename)
{
wstring fullPath = _texturePath + filename + L".xml";
auto parentPath = filesystem::path(fullPath).parent_path();
tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument();
tinyxml2::XMLError error = document->LoadFile(Utils::ToString(fullPath).c_str());
assert(error == tinyxml2::XML_SUCCESS);
tinyxml2::XMLElement* root = document->FirstChildElement();
tinyxml2::XMLElement* materialNode = root->FirstChildElement();
while (materialNode)
{
shared_ptr<Material> material = make_shared<Material>();
tinyxml2::XMLElement* node = nullptr;
node = materialNode->FirstChildElement();
material->SetName(Utils::ToWString(node->GetText()));
// Diffuse Texture
node = node->NextSiblingElement();
if (node->GetText())
{
wstring textureStr = Utils::ToWString(node->GetText());
if (textureStr.length() > 0)
{
auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
material->SetDiffuseMap(texture);
}
}
// Specular Texture
node = node->NextSiblingElement();
if (node->GetText())
{
wstring texture = Utils::ToWString(node->GetText());
if (texture.length() > 0)
{
wstring textureStr = Utils::ToWString(node->GetText());
if (textureStr.length() > 0)
{
auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
material->SetSpecularMap(texture);
}
}
}
// Normal Texture
node = node->NextSiblingElement();
if (node->GetText())
{
wstring textureStr = Utils::ToWString(node->GetText());
if (textureStr.length() > 0)
{
auto texture = RESOURCES->GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
material->SetNormalMap(texture);
}
}
// Ambient
{
node = node->NextSiblingElement();
Color color;
color.x = node->FloatAttribute("R");
color.y = node->FloatAttribute("G");
color.z = node->FloatAttribute("B");
color.w = node->FloatAttribute("A");
material->GetMaterialDesc().ambient = color;
}
// Diffuse
{
node = node->NextSiblingElement();
Color color;
color.x = node->FloatAttribute("R");
color.y = node->FloatAttribute("G");
color.z = node->FloatAttribute("B");
color.w = node->FloatAttribute("A");
material->GetMaterialDesc().diffuse = color;
}
// Specular
{
node = node->NextSiblingElement();
Color color;
color.x = node->FloatAttribute("R");
color.y = node->FloatAttribute("G");
color.z = node->FloatAttribute("B");
color.w = node->FloatAttribute("A");
material->GetMaterialDesc().specular = color;
}
// Emissive
{
node = node->NextSiblingElement();
Color color;
color.x = node->FloatAttribute("R");
color.y = node->FloatAttribute("G");
color.z = node->FloatAttribute("B");
color.w = node->FloatAttribute("A");
material->GetMaterialDesc().emissive = color;
}
_materials.push_back(material);
// Next Material
materialNode = materialNode->NextSiblingElement();
}
BindCacheInfo();
}
void Model::ReadModel(wstring filename)
{
wstring fullPath = _modelPath + filename + L".mesh";
shared_ptr<FileUtils> file = make_shared<FileUtils>();
file->Open(fullPath, FileMode::Read);
// Bones
{
const uint32 count = file->Read<uint32>();
for (uint32 i = 0; i < count; i++)
{
shared_ptr<ModelBone> bone = make_shared<ModelBone>();
bone->index = file->Read<int32>();
bone->name = Utils::ToWString(file->Read<string>());
bone->parentIndex = file->Read<int32>();
bone->transform = file->Read<Matrix>();
_bones.push_back(bone);
}
}
// Mesh
{
const uint32 count = file->Read<uint32>();
for (uint32 i = 0; i < count; i++)
{
shared_ptr<ModelMesh> mesh = make_shared<ModelMesh>();
mesh->name = Utils::ToWString(file->Read<string>());
mesh->boneIndex = file->Read<int32>();
// Material
mesh->materialName = Utils::ToWString(file->Read<string>());
//VertexData
{
const uint32 count = file->Read<uint32>();
vector<ModelVertexType> vertices;
vertices.resize(count);
void* data = vertices.data();
file->Read(&data, sizeof(ModelVertexType) * count);
mesh->geometry->AddVertices(vertices);
}
//IndexData
{
const uint32 count = file->Read<uint32>();
vector<uint32> indices;
indices.resize(count);
void* data = indices.data();
file->Read(&data, sizeof(uint32) * count);
mesh->geometry->AddIndices(indices);
}
mesh->CreateBuffers();
_meshes.push_back(mesh);
}
}
BindCacheInfo();
}
std::shared_ptr<Material> Model::GetMaterialByName(const wstring& name)
{
for (auto& material : _materials)
{
if (material->GetName() == name)
return material;
}
return nullptr;
}
std::shared_ptr<ModelMesh> Model::GetMeshByName(const wstring& name)
{
for (auto& mesh : _meshes)
{
if (mesh->name == name)
return mesh;
}
return nullptr;
}
std::shared_ptr<ModelBone> Model::GetBoneByName(const wstring& name)
{
for (auto& bone : _bones)
{
if (bone->name == name)
return bone;
}
return nullptr;
}
void Model::BindCacheInfo()
{
// Mesh에 Material 캐싱
for (const auto& mesh : _meshes)
{
// 이미 찾았으면 스킵
if (mesh->material != nullptr)
continue;
mesh->material = GetMaterialByName(mesh->materialName);
}
// Mesh에 Bone 캐싱
for (const auto& mesh : _meshes)
{
// 이미 찾았으면 스킵
if (mesh->bone != nullptr)
continue;
mesh->bone = GetBoneByIndex(mesh->boneIndex);
}
// Bone 계층 정보 채우기
if (_root == nullptr && _bones.size() > 0)
{
_root = _bones[0];
for (const auto& bone : _bones)
{
if (bone->parentIndex >= 0)
{
bone->parent = _bones[bone->parentIndex];
bone->parent->children.push_back(bone);
}
else
{
bone->parent = nullptr;
}
}
}
}
#pragma once
#include "Component.h"
class Model;
class Shader;
class Material;
class ModelRenderer : public Component
{
using Super = Component;
public:
ModelRenderer(shared_ptr<Shader> shader);
virtual ~ModelRenderer();
virtual void Update() override;
void SetModel(shared_ptr<Model> model);
void SetPass(uint8 pass) { _pass = pass; }
private:
shared_ptr<Shader> _shader;
uint8 _pass = 0;
shared_ptr<Model> _model;
};
#include "pch.h"
#include "ModelRenderer.h"
#include "Material.h"
#include "ModelMesh.h"
#include "Model.h"
ModelRenderer::ModelRenderer(shared_ptr<Shader> shader)
: Super(ComponentType::ModelRenderer), _shader(shader)
{
}
ModelRenderer::~ModelRenderer()
{
}
void ModelRenderer::Update()
{
if (_model == nullptr)
return;
// Bones
BoneDesc boneDesc;
const uint32 boneCount = _model->GetBoneCount();
for (uint32 i = 0; i < boneCount; i++)
{
shared_ptr<ModelBone> bone = _model->GetBoneByIndex(i);
boneDesc.transforms[i] = bone->transform;
}
RENDER->PushBoneData(boneDesc);
// Transform
auto world = GetTransform()->GetWorldMatrix();
RENDER->PushTransformData(TransformDesc{ world });
const auto& meshes = _model->GetMeshes();
for (auto& mesh : meshes)
{
if (mesh->material)
mesh->material->Update();
// BoneIndex
_shader->GetScalar("BoneIndex")->SetInt(mesh->boneIndex);
uint32 stride = mesh->vertexBuffer->GetStride();
uint32 offset = mesh->vertexBuffer->GetOffset();
DC->IASetVertexBuffers(0, 1, mesh->vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
DC->IASetIndexBuffer(mesh->indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
_shader->DrawIndexed(0, _pass, mesh->indexBuffer->GetCount(), 0, 0);
}
}
void ModelRenderer::SetModel(shared_ptr<Model> model)
{
_model = model;
const auto& materials = _model->GetMaterials();
for (auto& material : materials)
{
material->SetShader(_shader);
}
}
원하는 모양은 아니지만 모델이 어떻게든 로드가 된 모습이다.
계층 구조
탱크를 기준으로 한번 뽑아보자.
이 역시 모양은 어느정도 있지만 잘못 만들어 진것 같은 느낌이 든다.
이는 탱크라는 모델을 만들었을 때, Bone을 활용하지 않아서 그런다. 좌표가 있지만 이는 자신의 부모 계층을 기준으로 하는 좌표이기 때문에 이를 무시하고 만들게 되면 위의 사진처럼 나오게 되는 것이다.
#define MAX_MODEL_TRANSFORMS 50
cbuffer BoneBuffer
{
matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
};
uint BoneIndex;
쉐이더에 이 부분을 추가해 우리가 추가적으로 쉐이더에 Bone 정보를 건네준다. 그리고 그 Bone을 기준으로 추가적인 연산을 할 것이다.
그 부분은 이렇게 한다.
void ModelRenderer::Update()
{
if (_model == nullptr)
return;
// Bones
BoneDesc boneDesc;
const uint32 boneCount = _model->GetBoneCount();
for (uint32 i = 0; i < boneCount; i++)
{
shared_ptr<ModelBone> bone = _model->GetBoneByIndex(i);
boneDesc.transforms[i] = bone->transform;
}
RENDER->PushBoneData(boneDesc);
// Transform
auto world = GetTransform()->GetWorldMatrix();
RENDER->PushTransformData(TransformDesc{ world });
const auto& meshes = _model->GetMeshes();
for (auto& mesh : meshes)
{
if (mesh->material)
mesh->material->Update();
// BoneIndex
_shader->GetScalar("BoneIndex")->SetInt(mesh->boneIndex);
uint32 stride = mesh->vertexBuffer->GetStride();
uint32 offset = mesh->vertexBuffer->GetOffset();
DC->IASetVertexBuffers(0, 1, mesh->vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
DC->IASetIndexBuffer(mesh->indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);
_shader->DrawIndexed(0, _pass, mesh->indexBuffer->GetCount(), 0, 0);
}
}
간단히 모델이 가지고 있는 모든 본을 가져와 저장하고 그것을 shader에 전잘해주는 것이다.
MeshOutput VS(VertexTextureNormalTangent input)
{
MeshOutput output;
output.position = mul(input.position, BoneTransforms[BoneIndex]);
output.position = mul(output.position, W);
output.worldPosition = output.position.xyz;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3) W);
output.tangent = mul(input.tangent, (float3x3) W);
return output;
}
쉐이더에서 월드 행렬을 곱하기 이전에 각자 저장된 본들의 위치를 먼저 곱함으로써 자신의 본래 위치로 갈 수 있게끔 한다.
VS에 들어가는 Input 좌표는 진짜 자신의 메쉬가 나타내는 로컬 좌표이다. 계층구조와는 전혀 상관 없다는 의미이다.
가령 탱크의 포신이 있고 몸체가 있다고 할 때, 포신 메쉬가 VS 단계에 들어가면 그 때의 입력 값은 포신 자체의 로컬 스페이스 값이다.
당연한 얘기이다, Vertex 정보라 함은 내가 어떤 기하학적인 모형을 하고 잇는지를 알려주는 것이다. 그렇기 때문에 좌표계와 전혀 상관 없지 자기 자신을 기준으로한 좌표들을 가지고 있는 것이다.
맨 처음 이상하게 보인 이유도 Vertex 정보들을 파싱해 그대로 화면에 보여주었지만 이는 자기 자신을 기준으로 한 즉, 부모, 루트의 계층 구조를 전혀 생각하지 않고 Shader에서 들어온 Vertex 값을 바로 W, V, P에 곱해 output 했기 때문이다.
Bone, 루트를 기준으로 한 좌표계로 변환을 해주기 위해 Shader에서 Bone에 대한 정보를 받는 것이고 그에 따라 자기자신을 기준으로 나타낸 Vertex 정보를 Root를 기준으로 하는 좌표계로 변환하는 것이다. 이렇게 해야만 올바르게 동작하는 것이다.
ImGUI
GitHub에서 ImGUI 파일을 다운받은 후 우리가 사용하는 DirectX11 버전에 맞는 부분을 라이브러리 화 하지 않고 그냥 코어 코드를 전부 가져와 이식해보겠다.
이식을 하고 일정 코드를 맞춰 준후 다음과 같이 작성 했을 때, 아래 화면 같이 나오면 ImGUI를 쓸 준비가 된것이다.
#include "pch.h"
#include "ImGUIDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
void ImGUIDemo::Init()
{
}
void ImGUIDemo::Update()
{
// UI
Test();
}
void ImGUIDemo::Render()
{
}
void ImGUIDemo::Test()
{
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
//ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}
{
ImGui::Begin("KimMinseok", nullptr,
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoMove);
ImGui::Text("Minseok Hello ImGui!");
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
}
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 애니메이션 - 모델 애니메이션 (0) | 2024.10.09 |
---|---|
[DirectX] 애니메이션 - 스키닝, 데이터 (1) | 2024.10.02 |
[DirectX] 모델 - Assimp, Material 로딩, Mesh 로딩 (0) | 2024.09.27 |
[DirectX] Lighting & Material - Material, Normal Mapping (0) | 2024.09.25 |
[DirectX] Lighting & Material - Global Shader, Depth Stencil View, 조명 (0) | 2024.09.24 |