2.Bone,Mesh 가져오기
이제 모델의 정보를 파싱해오기 위해서 루트노드(-1,-1)에서 부터 계층적으로 정보를 읽어주는 작업해주도록 하자.
지금은 스캘레톤을 저장하는 것 보다는 계층별로 접근하는 노드 하나하나가 Bone 뼈대라고 생각하고 저장해보자 이렇게 트리 구조의 정보를 가져올 떄는 재귀함수를 사용하는 것이 좋다.
fbx에서 transform 정보를 가져오려면 어떤 좌표로 저장되어 있는지 알아야한다. fbx에서는 transform이 Relative 좌표로 저장되어 있기 때문에 부모의 Transform Matrix를 가져와서 곱해주는 것으로 루트로 향하는 상대좌표로 바꾸어 준다.
이때 루트노드부터 재귀적으로 함수가 작동하기 때문에 루트로 향하는 상대좌표로 바꿔줄 수 있는 것이다.
Converter.cpp
//노드- 데이터 저장단위
void Converter::ReadModelData(aiNode* node, int32 index, int32 parent)
{
shared_ptr<asBone> bone = make_shared<asBone>();
bone->index = index;
bone->parent = parent;
bone->name = node->mName.C_Str();
//transform 추출 - 주소를 받아주기
Matrix transform(node->mTransformation[0]);
//fbx -> 4*4가 전치되어있어서 다시 전치해주어야한다.
// 상대좌표
bone->transform = transform.Transpose();
//루트에 가져온 메트릭스 - 재귀
Matrix matParent = Matrix::Identity;
if (parent >= 0)
matParent = _bones[parent]->transform;
//Local(Root) Transform
bone->transform = bone->transform * matParent;
_bones.push_back(bone);
//Mesh
ReadMeshData(node, index);
//계층타기 - 재귀
for (uint32 i = 0; i < node->mNumChildren; i++)
ReadModelData(node->mChildren[i], _bones.size(), index);
}
Mesh도 Bone을 가져올 때 같이 작동하도록 하자. Mesh는 하나의 Mesh에 SubMesh가 여러개 있을 수도 있기 때문에 이 서브 매쉬도 어떻게 관리할지도 생각해보아야한다.
각 노드에 저장된 매쉬데이터는 제일 상위노드인 Scene노드에서 몇번쨰 매쉬에 데이터가 저장되어 있는지 인덱스 정보를 가지고 있다. 그렇기 때문에 인덱스를 가지고 온 다음 Scene에서 mesh를 인덱스 번호를 통해 가져오면 된다. 각 서브Mesh는 정점의 크기를 통해 indices 벡터를 관리해주어서 각 서브 Mesh를 하나의 Vector에서 관리할 수 있도록 하자.
Converter.cpp
void Converter::ReadMeshData(aiNode* node, int32 bone)
{
if (node->mNumMeshes < 1)
return;
shared_ptr<asMesh> mesh = make_shared<asMesh>();
mesh->name = node->mName.C_Str();
mesh->boneIndex = bone;
for (uint32 i = 0; i < node->mNumMeshes; i++)
{
//Scene에서 매쉬 번호
uint32 index = node->mMeshes[i];
const aiMesh* srcMesh = _scene->mMeshes[index];
// Material Name - Material 과의 관계를 위해
const aiMaterial* material = _scene->mMaterials[srcMesh->mMaterialIndex];
mesh->materialName = material->GetName().C_Str();
//시작 정점번호 - 서브매쉬 구분을 위하여
const uint32 startVertex = mesh->vertices.size();
for (uint32 v = 0; v < srcMesh->mNumVertices; v++)
{
// Vertex
VetexType vertex;
::memcpy(&vertex.position, &srcMesh->mVertices[v], sizeof(Vec3));
// UV
if (srcMesh->HasTextureCoords(0))
::memcpy(&vertex.uv, &srcMesh->mTextureCoords[0][v], sizeof(Vec2));
// Normal
if (srcMesh->HasNormals())
::memcpy(&vertex.normal, &srcMesh->mNormals[v], sizeof(Vec3));
mesh->vertices.push_back(vertex);
}
// Index
for (uint32 f = 0; f < srcMesh->mNumFaces; f++)
{
aiFace& face = srcMesh->mFaces[f];
for (uint32 k = 0; k < face.mNumIndices; k++)
mesh->indices.push_back(face.mIndices[k] + startVertex);
}
}
_meshes.push_back(mesh);
}
3. 모델 띄우기
이제 최종적으로 저장된 파일을 메모리로 가져와서 띄워보자. 일단 파일을 읽어오고 그 파일을 쓰고 읽어오는 코드를 담은 FileUtils클래스를 만들어주자.
FileUtils.h
#pragma once
//파일입출력용
enum FileMode :uint8
{
Write,
Read,
};
class FileUtils
{
public:
FileUtils();
~FileUtils();
void Open(wstring filePath, FileMode mode);
//쓸때 어떤자료형 쓸지 모름
template<typename T>
void Write(const T& data)
{
DWORD numOfBytes = 0;
assert(::WriteFile(_handle, &data, sizeof(T), (LPDWORD)&numOfBytes, nullptr));
}
//템플릿특수화 - 예외적 상황가정
template<>
void Write<string>(const string& data)
{
return Write(data);
}
void Write(void* data, uint32 dataSize);
void Write(const string& data);
template<typename T>
void Read(OUT T& data)
{
DWORD numOfBytes = 0;
assert(::ReadFile(_handle, &data, sizeof(T), (LPDWORD)&numOfBytes, nullptr));
}
template<typename T>
T Read()
{
T data;
Read(data);
return data;
}
void Read(void** data, uint32 dataSize);
void Read(OUT string& data);
private:
HANDLE _handle = INVALID_HANDLE_VALUE;
};
FileUtils.cpp
#include "pch.h"
#include "FileUtils.h"
FileUtils::FileUtils()
{
}
FileUtils::~FileUtils()
{
if (_handle != INVALID_HANDLE_VALUE)
{
::CloseHandle(_handle);
_handle = INVALID_HANDLE_VALUE;
}
}
// 읽기 / 쓰기모드이냐에 따라 다르게
//WinAPI
void FileUtils::Open(wstring filePath, FileMode mode)
{
if (mode == FileMode::Write)
{
_handle = ::CreateFile(
filePath.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
}
else
{
_handle = ::CreateFile
(
filePath.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
}
assert(_handle != INVALID_HANDLE_VALUE);
}
void FileUtils::Write(void* data, uint32 dataSize)
{
uint32 numOfBytes = 0;
assert(::WriteFile(_handle, data, dataSize, reinterpret_cast<LPDWORD>(&numOfBytes), nullptr));
}
void FileUtils::Write(const string& data)
{
uint32 size = (uint32)data.size();
Write(size);
if (data.size() == 0)
return;
Write((void*)data.data(), size);
}
void FileUtils::Read(void** data, uint32 dataSize)
{
uint32 numOfBytes = 0;
assert(::ReadFile(_handle, *data, dataSize, reinterpret_cast<LPDWORD>(&numOfBytes), nullptr));
}
void FileUtils::Read(OUT string& data)
{
uint32 size = Read<uint32>();
if (size == 0)
return;
//사이즈만큼 동적할당
char* temp = new char[size + 1];
temp[size] = 0;
Read((void**)&temp, size);
data = temp;
delete[] temp;
}
이제 이 클래스를 활용하여 Bone 과 Mesh 정보를 하나로 모아서 모델정보로 저장하는 함수를 완성해주자 이때 각 구조체내부의 변수의 순서와 자료형을 잘 맞춰주자.
Converter.cpp
//바이너리 파일로 관리
void Converter::WriteModelFile(wstring finalPath)
{
auto path = filesystem::path(finalPath);
// 폴더가 없으면 만든다.
filesystem::create_directory(path.parent_path());
shared_ptr<FileUtils> file = make_shared<FileUtils>();
file->Open(finalPath, FileMode::Write);
// Bone Data
file->Write<uint32>(_bones.size());
for (shared_ptr<asBone>& bone : _bones)
{
//순서, 자료형 잘맞춰주기
file->Write<int32>(bone->index);
file->Write<string>(bone->name);
file->Write<int32>(bone->parent);
file->Write<Matrix>(bone->transform);
}
// Mesh Data
file->Write<uint32>(_meshes.size());
for (shared_ptr<asMesh>& meshData : _meshes)
{
file->Write<string>(meshData->name);
file->Write<int32>(meshData->boneIndex);
file->Write<string>(meshData->materialName);
// Vertex Data
file->Write<uint32>(meshData->vertices.size());
file->Write(&meshData->vertices[0], sizeof(VertexType) * meshData->vertices.size());
// Index Data
file->Write<uint32>(meshData->indices.size());
file->Write(&meshData->indices[0], sizeof(uint32) * meshData->indices.size());
}
}
이제 메모리로 가져와서 모델을 띄워보자. 일단 우리가 메모리로 가져오기 위해 엔진쪽에서 모델정보와 모델의 Bone, Mesh 정보를 가지고 있을 Model, ModelMesh 클래스를 만들어주자.
ModelMesh.h
#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;
};
ModelMesh.cpp
#include "pch.h"
#include "ModelMesh.h"
void ModelMesh::CreateBuffers()
{
vertexBuffer = make_shared<VertexBuffer>();
vertexBuffer->Create(geometry->GetVertices());
indexBuffer = make_shared<IndexBuffer>();
indexBuffer->Create(geometry->GetIndices());
}
Model.h
#pragma once
struct ModelBone;
struct ModelMesh;
class Model : public enable_shared_from_this<Model>
{
public:
Model();
~Model();
public:
//custom -> Memory
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;
};
Model.cpp
#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;
}
}
}
}
그리고 이 모델을 Render해주는 부분을 MeshRenderer처럼 컴포넌트로 만들어서 관리해주도록 하자.
ModelRenderer.h
#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;
};
ModelRenderer.cpp
#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;
auto world = GetTransform()->GetWorldMatrix();
RENDER->PushTransformData(TransformDesc{ world });
const auto& meshes = _model->GetMeshes();
for (auto& mesh : meshes)
{
if (mesh->material)
mesh->material->Update();
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);
}
}
이제 모델을 띄워보기위해 메인 클래스인 StaticMeshModel를 만들어주자.
StaticMeshModel.h
#pragma once
#include "IExecute.h"
class StaticMeshDemo : public IExecute
{
public:
void Init() override;
void Update() override;
void Render() override;
void CreateTower();
private:
shared_ptr<Shader> _shader;
shared_ptr<GameObject> _obj;
shared_ptr<GameObject> _camera;
};
StaticMeshModel.cpp
#include "pch.h"
#include "StaticMeshDemo.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 StaticMeshDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"15. ModelDemo.fx");
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
CreateTower();
RENDER->Init(_shader);
}
void StaticMeshDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.4f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(0.f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
RENDER->PushLightData(lightDesc);
}
{
_obj->Update();
}
}
void StaticMeshDemo::Render()
{
}
void StaticMeshDemo::CreateTower()
{
// CustomData -> Memory
shared_ptr<class Model> m1 = make_shared<Model>();
m1->ReadModel(L"Tower/Tower");
m1->ReadMaterial(L"Tower/Tower");
_obj = make_shared<GameObject>();
_obj->GetOrAddTransform()->SetPosition(Vec3(0, 0, 50));
_obj->GetOrAddTransform()->SetScale(Vec3(1.0f));
_obj->AddComponent(make_shared<ModelRenderer>(_shader));
{
_obj->GetModelRenderer()->SetModel(m1);
_obj->GetModelRenderer()->SetPass(1);
}
}
그리고 물체를 좀 더 잘 보이도록 빨간색 와이어프레임으로 보이게 쉐이더를 수정해주자
ModelDemo.fx
#include "00. Global.fx"
#include "00. Light.fx"
MeshOutput VS(VertexTextureNormalTangent input)
{
MeshOutput output;
output.position = mul(input.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;
}
float4 PS(MeshOutput input) : SV_TARGET
{
//ComputeNormalMapping(input.normal, input.tangent, input.uv);
//float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
float4 color = DiffuseMap.Sample(LinearSampler, input.uv);
return color;
}
float4 PS_RED(MeshOutput input) : SV_TARGET
{
return float4(1, 0, 0, 1);
}
technique11 T0
{
PASS_VP(P0, VS, PS)
PASS_RS_VP(P1, FillModeWireFrame, VS, PS_RED)
};
이렇게 해준 다음 실행해보면 타워가 잘 로드된것을 볼 수 있다.
'게임공부 > Directx11' 카테고리의 다른 글
[Directx11][C++][3D]12. ImGUI (4) | 2024.09.15 |
---|---|
[Directx11][C++][3D]10. Assimp(Model) (0) | 2024.09.14 |
[Directx11][C++][3D]8. Assimp(Material) (0) | 2024.09.14 |
[Directx11][C++][3D]7. Assimp(이론 및 설정) (0) | 2024.09.09 |
[Directx11][C++][3D]6. Normal Mapping (4) | 2024.09.07 |