이제 외부에서 3D 모델링 파일을 로드하는 기능을 추가할 것이다.
일단, 리소스를 관리하는 클래스 ResourceManager 클래스를 만들어주었다.
기존엔 텍스쳐 로드를 EngineBase에서 하고있었는데, 텍스쳐를 로드하는 코드도 모두 ResourceManager로 옮겨주었다.
ResourceManager에선 외부에서 기능을 쉽게 사용할 수 있도록 변수와 함수를 모두 static으로 선언해주었다.
그리고, 3D 모델링 파일을 로드하는 기능을 추가하기 위해, Assimp 라이브러리를 프로젝트에 추가하였다.
void ResourceManager::Load(const std::string& _FileName, const std::string& _Path)
{
if (LoadedMeshes.find(_FileName) != LoadedMeshes.end())
{
std::cout << _FileName << " is Already Loaded!" << std::endl;
return;
}
Assimp::Importer Importer;
std::string FullName = _Path + _FileName;
const aiScene* Scene = Importer.ReadFile(FullName, aiProcess_Triangulate | aiProcess_ConvertToLeftHanded);
DirectX::SimpleMath::Matrix Transform;
ProcessNode(Scene->mRootNode, Scene, LoadedMeshes[_FileName], Transform);
}
로드 함수이다. 외부에서 파일 로드를 할 때 이 함수만 호출해주면 된다.
함수를 호출하게 되면, ResourceManager 내부에서 static으로 선언된 자료구조에 MeshData를 저장해준다.
그리고 렌더러에서 메시를 세팅할 때, 해당 자료구조 내부의 데이터를 참조하여 메시를 세팅하게 된다.
위의 코드를 보면 PrecessNode 함수가 있는데, 이 함수가 실질적으로 파일을 로드하는 코드이다.
void ResourceManager::ProcessNode(aiNode* _Node, const aiScene* _Scene, std::list<EMeshData>& _MeshList, DirectX::SimpleMath::Matrix _Transform)
{
DirectX::SimpleMath::Matrix Mat;
ai_real* Temp = &_Node->mTransformation.a1;
float* MatTemp = &Mat._11;
for (int i = 0; i < 16; i++)
{
MatTemp[i] = Temp[i];
}
Mat = Mat.Transpose() * _Transform;
for (UINT i = 0; i < _Node->mNumMeshes; i++)
{
aiMesh* Mesh = _Scene->mMeshes[_Node->mMeshes[i]];
ProcessMesh(Mesh, _Scene, _MeshList);
EMeshData& NewMesh = _MeshList.back();
for (auto& Vertex : NewMesh.Vertices)
{
Vertex.Position = DirectX::SimpleMath::Vector3::Transform(Vertex.Position, Mat);
}
}
for (UINT i = 0; i < _Node->mNumChildren; i++)
{
ProcessNode(_Node->mChildren[i], _Scene, _MeshList, Mat);
}
}
보면, PrecessNOde를 재귀적으로 호출하고 있다.
그 이유는, 하나의 3D 모델링 파일엔 매쉬가 1개만 있는 것이 아니라 부모 자식 관계로 여러개의 메쉬가 얽혀있다.
이를 재귀적으로 탐색하여 모든 메쉬를 로드하는 것이다.
중간에 있는ProcessMesh 함수가 메쉬의 정보를 읽어오는 함수이다.
void ResourceManager::ProcessMesh(aiMesh* _Mesh, const aiScene* _Scene, std::list<EMeshData>& _MeshList)
{
EMeshData NewMesh;
NewMesh.Indices;
UINT IndicesCount = 0;
for (UINT i = 0; i < _Mesh->mNumFaces; i++)
{
IndicesCount += _Mesh->mFaces[i].mNumIndices;
}
NewMesh.Vertices.reserve(_Mesh->mNumVertices);
NewMesh.Indices.reserve(IndicesCount);
for (UINT i = 0; i < _Mesh->mNumVertices; i++)
{
EVertex NewVertex;
NewVertex.Position.x = _Mesh->mVertices[i].x;
NewVertex.Position.y = _Mesh->mVertices[i].y;
NewVertex.Position.z = _Mesh->mVertices[i].z;
NewVertex.Normal.x = _Mesh->mNormals[i].x;
NewVertex.Normal.y = _Mesh->mNormals[i].y;
NewVertex.Normal.z = _Mesh->mNormals[i].z;
NewVertex.Normal.Normalize();
if (_Mesh->mTextureCoords[0])
{
NewVertex.TexCoord.x = (float)_Mesh->mTextureCoords[0][i].x;
NewVertex.TexCoord.y = (float)_Mesh->mTextureCoords[0][i].y;
}
NewMesh.Vertices.push_back(NewVertex);
}
for (UINT i = 0; i < _Mesh->mNumFaces; i++)
{
aiFace& Face = _Mesh->mFaces[i];
for (UINT j = 0; j < Face.mNumIndices; j++)
{
NewMesh.Indices.push_back(Face.mIndices[j]);
}
}
if (_Mesh->mMaterialIndex >= 0)
{
aiMaterial* Material = _Scene->mMaterials[_Mesh->mMaterialIndex];
if (Material->GetTextureCount(aiTextureType_DIFFUSE) > 0)
{
aiString TexturePath;
Material->GetTexture(aiTextureType_DIFFUSE, 0, &TexturePath);
std::string TextureName = std::string(std::filesystem::path(TexturePath.C_Str()).filename().string());
NewMesh.TextureName = TextureName;
if (LoadedTextures.find(TextureName) == LoadedTextures.end())
{
LoadTexture(TextureName);
}
}
}
_MeshList.push_back(NewMesh);
}
버텍스, 노말, 텍스쳐좌표를 받아와서 저장하고 있으며, 해당 메쉬에 사용되는 텍스쳐의 이름도 받아주고 있다.
해당 텍스쳐가 로드되지 않은 상태라면, 로드까지 해주도록 하였다.
이렇게 메쉬 정보를 읽어오면, 렌더러에서 세팅을 해줄것이다.
SetModel("zeldaPosed001.fbx");
SetVSShader(L"VertexTest.hlsl");
SetPSShader(L"PixelTest.hlsl");
SetSampler("LINEARWRAP");
렌더러에서는 이렇게 모델, 쉐이더, 샘플러를 세팅해주도록 하였다.
void Renderer::SetModel(const std::string& _Name)
{
const std::list<EMeshData>& MeshData = ResourceManager::GetLoadedMeshList(_Name);
for (const EMeshData& Mesh : MeshData)
{
std::shared_ptr<RenderBase> NewRenderUnit = std::make_shared<RenderBase>();
NewRenderUnit->SetMesh(Mesh);
NewRenderUnit->CreateBuffer();
RenderUnits.push_back(NewRenderUnit);
}
}
모델 세팅하는 부분을 보면, 매쉬의 개수만큼 RenderUnit을 생성하여 각 유닛별로 메쉬를 1개씩 세팅해주었다.
서로 다른 매쉬이지만, 트랜스폼은 렌더러의 것을 모두 공유하기 때문에 여러개의 매쉬가 렌더러에 의해서 통제될 수 있도록 하였다.
void Renderer::SetVSShader(const std::wstring& _Shadername)
{
for (std::shared_ptr<RenderBase> _RenderUnit : RenderUnits)
{
_RenderUnit->SetVSShader(_Shadername);
}
}
void Renderer::SetPSShader(const std::wstring& _Shadername)
{
for (std::shared_ptr<RenderBase> _RenderUnit : RenderUnits)
{
_RenderUnit->SetPSShader(_Shadername);
}
}
void Renderer::SetSampler(const std::string& _Sampler)
{
for (std::shared_ptr<RenderBase> _RenderUnit : RenderUnits)
{
_RenderUnit->SetSampler(_Sampler);
}
}
렌더러에서 쉐이더, 샘플러를 세팅할 때에도 렌더유닛에 모두 세팅해주도록 하였다.
만약 특정 유닛에만 다른 쉐이더를 세팅하고 싶다면, 직접 호출하여 변경할 수도 있다.
잘 되는지 결과를 보도록 하자.
로드도 잘 되었고, 트랜스폼도 잘 작동한다.
이제 모델링 로드 기능을 구현하였으니, 이걸 토대로 더 많은 쉐이더 효과를 적용하며 공부해보고자 한다.
'프로젝트 > Direct X 그래픽스' 카테고리의 다른 글
프로젝트 : DirectX를 활용한 그래픽스 (17 - 큐브매핑) (0) | 2024.05.14 |
---|---|
프로젝트 : DirectX를 활용한 그래픽스 (16 - 림 라이트) (0) | 2024.05.14 |
프로젝트 : DirectX를 활용한 그래픽스 (14 - 구조 수정) (0) | 2024.05.12 |
프로젝트 : DirectX를 활용한 그래픽스 (13 - 조명 추가 (2) ) (0) | 2024.05.11 |
프로젝트 : DirectX를 활용한 그래픽스 (12 - 조명 추가 (1) ) (0) | 2024.05.08 |