다양한 종류의 3D 모델 파일 확장자, Fbx, 3DS, Obj, Md5, x 등등... 3DsMax, Blender같은 프로그램을 통해 제작되는 3D모델 파일들이 있다.
실제로 본 파일명은 fbx, obj 등이 있고 나머지는 생소한 파일명들 이기에 참고한 자료에서 다양한 확장자를 입력받아 동일한 형태로 변환할 수 있는 외부 라이브러리 'Assimp' 를 사용해 모델링 파일들을 Import 했다.
GitHub - assimp/assimp: The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clea
The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clean data structure. - GitHub - assimp/assimp: The official Open-Asset-Importer-Library Reposit...
github.com
Assimp를 이용해 가져온 파일들은 주로 게임을 위해 제작된 모델이 아니라 데이터가 크던가, 형식이 다르던가 하는 문제가 있어 별도의 에디터를 구성해 모델 파일들을 재생성 하기로 한다.
1. Importer 클래스를 통해 Scene이라 부를 '3D 모델에 대한 모든 정보를 담고있는 root 객체'를 반환한다.
Scene 객체 안에는 Materials(색상정보), Meshes(그려질정보), 카메라, 라이트 정보가 포함되어 있다.
Scene 안에는 Scene Root라 부르는 중심이되는 body를 포함한 모든 구조의 가상 루트를 만드는데 : Material(어떤 색상정보를 참조할지에 대한 정보), vertices, indices 등의 정보가 루트를 중심으로 계층구조 형식으로 정점끼리 이어져 있다.
Assimp에서 제공된 기능을 이용해 Converter라는 클래스로 우리가 쓸 데이터만 뽑아 가공해 용량을 줄이는 방법을 취한다.
Assimp는 열우선이기 때문에 행우선인 direct에서 사용하려면 행렬에 대한 변환이 필요
SceneRoot에서 재귀를 타며 모델을 구성하는 본에 존재하는 Transform정보와 Mesh 정보들을 추출해
추출한 정보들을 원하는 형태의 MeshData로 재가공
이때 fbx 파일에서 추출한 메시의 데이터는 .txt 형식으로 저장하면 처리가 느리고 크기가 커지기 때문에 byte 파일로 만들고 추출한 데이터에서 Root가 되는 본을 찾아 자식 본으로 내려가며 또 다시 저장된다.
> Assimp를 이용한 Converter.cpp
#include "stdafx.h"
#include "Converter.h"
#include "Types.h"
#include "Utilities/BinaryFile.h"
#include "Utilities/Xml.h"
Converter::Converter()
{
importer = new Assimp::Importer();
}
Converter::~Converter()
{
SafeDelete(importer);
}
void Converter::ReadFile(wstring file)
{
this->file = L"../../_Assets/" + file;
scene = importer->ReadFile
(
String::ToString(this->file),
aiProcess_ConvertToLeftHanded
| aiProcess_Triangulate
| aiProcess_GenUVCoords
| aiProcess_GenNormals
| aiProcess_CalcTangentSpace
);
assert(scene != NULL);
}
void Converter::ExportMesh(wstring savePath)
{
savePath = L"../../_Models/" + savePath + L".mesh";
ReadBoneData(scene->mRootNode, -1, -1);
ReadSkinData();
//Write CSV File
{
FILE* file;
fopen_s(&file, "../Vertices.csv", "w");
for (asBone* bone : bones)
{
string name = bone->Name;
fprintf(file, "%d,%s\n", bone->Index, bone->Name.c_str());
}
fprintf(file, "\n");
for (asMesh* mesh : meshes)
{
string name = mesh->Name;
printf("%s\n", name.c_str());
for (UINT i = 0; i < mesh->Vertices.size(); i++)
{
Vector3 p = mesh->Vertices[i].Position;
Vector4 indices = mesh->Vertices[i].BlendIndices;
Vector4 weights = mesh->Vertices[i].BlendWeights;
fprintf(file, "%f,%f,%f,", p.x, p.y, p.z);
fprintf(file, "%f,%f,%f,%f,", indices.x, indices.y, indices.z, indices.w);
fprintf(file, "%f,%f,%f,%f\n", weights.x, weights.y, weights.z, weights.w);
}
}
fclose(file);
}
WriteMeshData(savePath);
}
void Converter::ReadBoneData(aiNode * node, int index, int parent)
{
asBone* bone = new asBone();
bone->Index = index;
bone->Parent = parent;
bone->Name = node->mName.C_Str();
Matrix transform(node->mTransformation[0]);
D3DXMatrixTranspose(&bone->Transform, &transform);
Matrix matParent;
if (parent < 0)
D3DXMatrixIdentity(&matParent);
else
matParent = bones[parent]->Transform;
bone->Transform = bone->Transform * matParent;
bones.push_back(bone);
ReadMeshData(node, index);
for (UINT i = 0; i < node->mNumChildren; i++)
ReadBoneData(node->mChildren[i], bones.size(), index);
}
void Converter::ReadMeshData(aiNode * node, int bone)
{
if (node->mNumMeshes < 1) return;
asMesh* mesh = new asMesh();
mesh->Name = node->mName.C_Str();
mesh->BoneIndex = bone;
for (UINT i = 0; i < node->mNumMeshes; i++)
{
UINT index = node->mMeshes[i];
aiMesh* srcMesh = scene->mMeshes[index];
aiMaterial* material = scene->mMaterials[srcMesh->mMaterialIndex];
mesh->MaterialName = material->GetName().C_Str();
UINT startVertex = mesh->Vertices.size();
for (UINT v = 0; v < srcMesh->mNumVertices; v++)
{
Model::ModelVertex vertex;
memcpy(&vertex.Position, &srcMesh->mVertices[v], sizeof(Vector3));
if (srcMesh->HasTextureCoords(0))
memcpy(&vertex.Uv, &srcMesh->mTextureCoords[0][v], sizeof(Vector2));
if(srcMesh->HasNormals())
memcpy(&vertex.Normal, &srcMesh->mNormals[v], sizeof(Vector3));
mesh->Vertices.push_back(vertex);
}
for (UINT f = 0; f < srcMesh->mNumFaces; f++)
{
aiFace& face = srcMesh->mFaces[f];
for (UINT k = 0; k < face.mNumIndices; k++)
mesh->Indices.push_back(face.mIndices[k] + startVertex);
}
meshes.push_back(mesh);
}
}
void Converter::ReadSkinData()
{
for (UINT i = 0; i < scene->mNumMeshes; i++)
{
aiMesh* aiMesh = scene->mMeshes[i];
if (aiMesh->HasBones() == false) continue;
asMesh* mesh = meshes[i];
vector<asBoneWeights> boneWeights;
boneWeights.assign(mesh->Vertices.size(), asBoneWeights());
for (UINT b = 0; b < aiMesh->mNumBones; b++)
{
aiBone* aiMeshBone = aiMesh->mBones[b];
UINT boneIndex = 0;
for (asBone* bone : bones)
{
if (bone->Name == (string)aiMeshBone->mName.C_Str())
{
boneIndex = bone->Index;
break;
}
}//for(bone)
for (UINT w = 0; w < aiMeshBone->mNumWeights; w++)
{
UINT index = aiMeshBone->mWeights[w].mVertexId;
float weight = aiMeshBone->mWeights[w].mWeight;
boneWeights[index].AddWeights(boneIndex, weight);
}
}//for(b)
for (UINT w = 0; w < boneWeights.size(); w++)
{
boneWeights[i].Normalize();
asBlendWeight blendWeight;
boneWeights[w].GetBlendWeights(blendWeight);
mesh->Vertices[w].BlendIndices = blendWeight.Indices;
mesh->Vertices[w].BlendWeights = blendWeight.Weights;
}
}
}
void Converter::WriteMeshData(wstring savePath)
{
Path::CreateFolders(Path::GetDirectoryName(savePath));
BinaryWriter* w = new BinaryWriter();
w->Open(savePath);
w->UInt(bones.size());
for (asBone* bone : bones)
{
w->Int(bone->Index);
w->String(bone->Name);
w->Int(bone->Parent);
w->Matrix(bone->Transform);
SafeDelete(bone);
}
w->UInt(meshes.size());
for (asMesh* meshData : meshes)
{
w->String(meshData->Name);
w->Int(meshData->BoneIndex);
w->String(meshData->MaterialName);
w->UInt(meshData->Vertices.size());
w->Byte(&meshData->Vertices[0], sizeof(Model::ModelVertex) * meshData->Vertices.size());
w->UInt(meshData->Indices.size());
w->Byte(&meshData->Indices[0], sizeof(UINT) * meshData->Indices.size());
SafeDelete(meshData);
}
w->Close();
SafeDelete(w);
}
void Converter::ExportMaterial(wstring savePath, bool bOverwrite)
{
savePath = L"../../_Textures/" + savePath + L".material";
if (bOverwrite == false)
{
if (Path::ExistFile(savePath) == true)
return;
}
ReadMaterialData();
WriteMaterialData(savePath);
}
void Converter::ReadMaterialData()
{
for (UINT i = 0; i < scene->mNumMaterials; i++)
{
aiMaterial* srcMaterial = scene->mMaterials[i];
asMaterial* material = new asMaterial();
material->Name = srcMaterial->GetName().C_Str();
aiColor3D color;
srcMaterial->Get(AI_MATKEY_COLOR_AMBIENT, color);
material->Ambient = Color(color.r, color.g, color.b, 1.0f);
srcMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, color);
material->Diffuse = Color(color.r, color.g, color.b, 1.0f);
srcMaterial->Get(AI_MATKEY_COLOR_SPECULAR, color);
material->Specular = Color(color.r, color.g, color.b, 1.0f);
srcMaterial->Get(AI_MATKEY_SHININESS, material->Specular.a);
srcMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, color);
material->Emissive = Color(color.r, color.g, color.b, 1.0f);
aiString file;
srcMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &file);
material->DiffuseFile = file.C_Str();
srcMaterial->GetTexture(aiTextureType_SPECULAR, 0, &file);
material->SpecularFile = file.C_Str();
srcMaterial->GetTexture(aiTextureType_NORMALS, 0, &file);
material->NormalFile = file.C_Str();
materials.push_back(material);
}
}
void Converter::WriteMaterialData(wstring savePath)
{
string folder = String::ToString(Path::GetDirectoryName(savePath));
string file = String::ToString(Path::GetFileName(savePath));
Path::CreateFolders(folder);
Xml::XMLDocument* document = new Xml::XMLDocument();
Xml::XMLDeclaration* decl = document->NewDeclaration();
document->LinkEndChild(decl);
Xml::XMLElement* root = document->NewElement("Materials");
root->SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
root->SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
document->LinkEndChild(root);
for (asMaterial* material : materials)
{
Xml::XMLElement* node = document->NewElement("Material");
root->LinkEndChild(node);
Xml::XMLElement* element = NULL;
element = document->NewElement("Name");
element->SetText(material->Name.c_str());
node->LinkEndChild(element);
element = document->NewElement("DiffuseFile");
element->SetText(WriteTexture(folder, material->DiffuseFile).c_str());
node->LinkEndChild(element);
element = document->NewElement("SpecularFile");
element->SetText(WriteTexture(folder, material->SpecularFile).c_str());
node->LinkEndChild(element);
element = document->NewElement("NormalFile");
element->SetText(WriteTexture(folder, material->NormalFile).c_str());
node->LinkEndChild(element);
element = document->NewElement("Ambient");
element->SetAttribute("R", material->Ambient.r);
element->SetAttribute("G", material->Ambient.g);
element->SetAttribute("B", material->Ambient.b);
element->SetAttribute("A", material->Ambient.a);
node->LinkEndChild(element);
element = document->NewElement("Diffuse");
element->SetAttribute("R", material->Diffuse.r);
element->SetAttribute("G", material->Diffuse.g);
element->SetAttribute("B", material->Diffuse.b);
element->SetAttribute("A", material->Diffuse.a);
node->LinkEndChild(element);
element = document->NewElement("Specular");
element->SetAttribute("R", material->Specular.r);
element->SetAttribute("G", material->Specular.g);
element->SetAttribute("B", material->Specular.b);
element->SetAttribute("A", material->Specular.a);
node->LinkEndChild(element);
element = document->NewElement("Emissive");
element->SetAttribute("R", material->Emissive.r);
element->SetAttribute("G", material->Emissive.g);
element->SetAttribute("B", material->Emissive.b);
element->SetAttribute("A", material->Emissive.a);
node->LinkEndChild(element);
SafeDelete(material);
}
document->SaveFile((folder + file).c_str());
SafeDelete(document);
}
string Converter::WriteTexture(string saveFolder, string file)
{
if (file.length() < 1) return "";
string fileName = Path::GetFileName(file);
const aiTexture* texture = scene->GetEmbeddedTexture(file.c_str());
string path = "";
if (texture != NULL)
{
path = saveFolder + fileName;
if (texture->mHeight < 1)
{
BinaryWriter w;
w.Open(String::ToWString(path));
w.Byte(texture->pcData, texture->mWidth);
w.Close();
}
else
{
D3D11_TEXTURE2D_DESC destDesc;
ZeroMemory(&destDesc, sizeof(D3D11_TEXTURE2D_DESC));
destDesc.Width = texture->mWidth;
destDesc.Height = texture->mHeight;
destDesc.MipLevels = 1;
destDesc.ArraySize = 1;
destDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
destDesc.SampleDesc.Count = 1;
destDesc.SampleDesc.Quality = 0;
destDesc.Usage = D3D11_USAGE_IMMUTABLE;
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = texture->pcData;
ID3D11Texture2D* dest;
HRESULT hr;
hr = D3D::GetDevice()->CreateTexture2D(&destDesc, &subResource, &dest);
assert(SUCCEEDED(hr));
D3DX11SaveTextureToFileA(D3D::GetDC(), dest, D3DX11_IFF_PNG, saveFolder.c_str());
}
}
else
{
string directory = Path::GetDirectoryName(String::ToString(this->file));
string origin = directory + file;
String::Replace(&origin, "\\", "/");
if (Path::ExistFile(origin) == false)
return "";
path = saveFolder + fileName;
CopyFileA(origin.c_str(), path.c_str(), FALSE);
String::Replace(&path, "../../_Textures/", "");
}
return Path::GetFileName(path);
}
void Converter::ClipList(vector<wstring>* list)
{
for (UINT i = 0; i < scene->mNumAnimations; i++)
{
aiAnimation* anim = scene->mAnimations[i];
list->push_back(String::ToWString(anim->mName.C_Str()));
}
}
void Converter::ExportAnimClip(UINT index, wstring savePath)
{
savePath = L"../../_Models/" + savePath + L".clip";
asClip* clip = ReadClipData(scene->mAnimations[index]);
WriteClipData(clip, savePath);
}
asClip * Converter::ReadClipData(aiAnimation * animation)
{
asClip* clip = new asClip();
clip->Name = animation->mName.C_Str();
clip->FrameRate = (float)animation->mTicksPerSecond;
clip->FrameCount = (UINT)animation->mDuration + 1;
vector<asClipNode> aniNodeInfos;
for (UINT i = 0; i < animation->mNumChannels; i++)
{
aiNodeAnim* aniNode = animation->mChannels[i];
asClipNode aniNodeInfo;
aniNodeInfo.Name = aniNode->mNodeName;
UINT keyCount = max(aniNode->mNumPositionKeys, aniNode->mNumScalingKeys);
keyCount = max(keyCount, aniNode->mNumRotationKeys);
for (UINT k = 0; k < keyCount; k++)
{
asKeyframeData frameData;
bool bFound = false;
UINT t = aniNodeInfo.Keyframe.size();
if (fabsf((float)aniNode->mPositionKeys[k].mTime - (float)t) <= D3DX_16F_EPSILON)
{
aiVectorKey key = aniNode->mPositionKeys[k];
frameData.Time = (float)key.mTime;
memcpy_s(&frameData.Translation, sizeof(Vector3), &key.mValue, sizeof(aiVector3D));
bFound = true;
}
if (fabsf((float)aniNode->mRotationKeys[k].mTime - (float)t) <= D3DX_16F_EPSILON)
{
aiQuatKey key = aniNode->mRotationKeys[k];
frameData.Time = (float)key.mTime;
frameData.Rotation.x = key.mValue.x;
frameData.Rotation.y = key.mValue.y;
frameData.Rotation.z = key.mValue.z;
frameData.Rotation.w = key.mValue.w;
bFound = true;
}
if (fabsf((float)aniNode->mScalingKeys[k].mTime - (float)t) <= D3DX_16F_EPSILON)
{
aiVectorKey key = aniNode->mScalingKeys[k];
frameData.Time = (float)key.mTime;
memcpy_s(&frameData.Scale, sizeof(Vector3), &key.mValue, sizeof(aiVector3D));
bFound = true;
}
if (bFound == true)
aniNodeInfo.Keyframe.push_back(frameData);
}//for(k)
if (aniNodeInfo.Keyframe.size() < clip->FrameCount)
{
UINT count = clip->FrameCount - aniNodeInfo.Keyframe.size();
asKeyframeData keyFrame = aniNodeInfo.Keyframe.back();
for (UINT n = 0; n < count; n++)
aniNodeInfo.Keyframe.push_back(keyFrame);
}
clip->Duration = max(clip->Duration, aniNodeInfo.Keyframe.back().Time);
aniNodeInfos.push_back(aniNodeInfo);
}
ReadKeyframeData(clip, scene->mRootNode, aniNodeInfos);
return clip;
}
void Converter::ReadKeyframeData(asClip * clip, aiNode * node, vector<struct asClipNode>& aiNodeInfos)
{
asKeyframe* keyframe = new asKeyframe();
keyframe->BoneName = node->mName.C_Str();
for (UINT i = 0; i < clip->FrameCount; i++)
{
asClipNode* asClipNode = NULL;
for (UINT n = 0; n < aiNodeInfos.size(); n++)
{
if (aiNodeInfos[n].Name == node->mName)
{
asClipNode = &aiNodeInfos[n];
break;
}
}//for(n)
asKeyframeData frameData;
if (asClipNode == NULL)
{
Matrix transform(node->mTransformation[0]);
D3DXMatrixTranspose(&transform, &transform);
frameData.Time = (float)i;
D3DXMatrixDecompose(&frameData.Scale, &frameData.Rotation, &frameData.Translation, &transform);
}
else
{
frameData = asClipNode->Keyframe[i];
}
keyframe->Transforms.push_back(frameData);
}
clip->Keyframes.push_back(keyframe);
for (UINT i = 0; i < node->mNumChildren; i++)
ReadKeyframeData(clip, node->mChildren[i], aiNodeInfos);
}
void Converter::WriteClipData(asClip * clip, wstring savePath)
{
Path::CreateFolders(Path::GetDirectoryName(savePath));
BinaryWriter* w = new BinaryWriter();
w->Open(savePath);
w->String(clip->Name);
w->Float(clip->Duration);
w->Float(clip->FrameRate);
w->UInt(clip->FrameCount);
w->UInt(clip->Keyframes.size());
for (asKeyframe* keyframe : clip->Keyframes)
{
w->String(keyframe->BoneName);
w->UInt(keyframe->Transforms.size());
w->Byte(&keyframe->Transforms[0], sizeof(asKeyframeData) * keyframe->Transforms.size());
SafeDelete(keyframe);
}
w->Close();
SafeDelete(w);
}
Mesh(2) - CubeMap (0) | 2024.08.07 |
---|---|
Mesh(1) - 원기둥, 원 (0) | 2024.07.31 |
Terrain(2) - Normal 값을 통한 음영 (1) | 2024.07.18 |
Terrain(1) - 높이 설정 (0) | 2024.07.18 |
Texture(3) - 좌표지정모드 (0) | 2024.07.11 |