이번 강의의 주제는 Assimp 라이브러리를 활용하여 FBX 파일 내의 Material 정보를 추출하고, 이를 구조화된 커스텀 데이터(XML)로 저장하는 전체 과정을 구현하는 것입니다.
Assimp를 통해 로딩한 FBX 파일에는 다양한 머티리얼 정보가 포함되어 있으며, 이 정보들은 aiMaterial이라는 구조를 통해 접근할 수 있습니다. 본 강의에서는 이 머티리얼 정보를 우리만의 자료구조(asMaterial)로 변환하고, 이후 tinyxml2 라이브러리를 사용해 XML 형식으로 저장하는 기능을 구현합니다.
또한, FBX에 내장 텍스처가 포함된 경우 메모리에서 직접 추출하여 저장하고, 외부 텍스처가 존재할 경우에는 원본 파일 경로를 따라 지정된 폴더에 복사되도록 자동화하는 파이프라인도 포함됩니다.
이 모든 과정은 단순히 로딩에 그치지 않고, 파싱 → 변환 → 저장이라는 일련의 자동화된 구조를 갖추는 데 초점을 맞추고 있으며, 결과적으로 우리가 게임 엔진이나 툴에서 활용 가능한 형태로 데이터를 가공하는 데 목적이 있습니다.
FBX 파일을 다룰 때 가장 핵심이 되는 건 바로 구조를 이해하고, 그 구조를 어떻게 파싱해서 우리 방식으로 변환하느냐입니다.
이번 강의에서는 Assimp 라이브러리를 기반으로 머티리얼 데이터를 파싱하고, 우리가 원하는 XML 파일로 저장하는 전체적인 흐름을 배웁니다.
Assimp에서는 FBX 파일을 aiScene이라는 하나의 루트 객체로 표현합니다.
이 객체는 FBX에 포함된 모든 구성 요소—노드, 메쉬, 머티리얼, 애니메이션, 텍스처 등을 계층적으로 담고 있으며, 그 내부 구조는 다음과 같습니다:
aiScene: FBX 전체를 표현하는 루트 컨테이너입니다.aiNode: 계층 구조를 구성하며, 각 노드는 자식 노드를 갖고 있고 Transform 정보(SRT)를 포함합니다.aiMesh: 정점(Vertex), UV, 노멀, 머티리얼 인덱스를 포함한 메시 데이터입니다.aiMaterial: 이름, 색상 정보(Ambient, Diffuse, Specular, Emissive), 텍스처 파일 경로 등을 담고 있는 머티리얼입니다.aiTexture: 텍스처가 FBX 파일 안에 내장된 경우, 해당 데이터를 메모리에서 바로 접근할 수 있게 해주는 구조입니다.머티리얼 파싱 과정은 크게 3가지 요소를 분리하여 설계합니다:
Material, Mesh, Node를 서로 독립적으로 파싱하고,즉, 머티리얼(Material)은 단독으로 읽을 수 있지만, 메쉬나 노드는 계층을 고려한 설계가 필요합니다.
이번 강의에서 구현할 흐름은 다음과 같습니다:
FBX 파일 → Assimp 메모리 로딩 → Material 추출 → 우리만의 구조(asMaterial)로 변환 → XML 저장
보다 구체적으로 보면 다음과 같은 단계로 이루어집니다:
_scene 객체에 메모리로 로드합니다._scene->mMaterials[] 배열을 순회하면서 aiMaterial 정보를 파싱하고, 이를 asMaterial이라는 커스텀 구조체로 변환합니다._materials 벡터에 저장된 asMaterial들을 tinyxml2를 사용하여 XML 포맷으로 저장합니다.이 개념을 바탕으로 본격적인 코드 구현에 들어가게 되며, 이후 구조적으로 완성된 Material 시스템을 통해 텍스처 관리와 데이터 저장을 자동화할 수 있게 됩니다.
| 용어 | 설명 |
|---|---|
aiScene | FBX 파일 전체를 대표하는 루트 구조체로, 메쉬, 머티리얼, 노드, 애니메이션 등 모든 데이터를 포함합니다. |
aiNode | 계층 구조를 표현하는 단위로, 부모-자식 관계를 구성하며 각 노드는 메쉬 인덱스를 참조할 수 있습니다. |
aiMesh | 정점(Vertex), 노멀(Normal), UV, 머티리얼 인덱스 등의 정보를 담고 있는 메쉬 데이터 구조입니다. |
aiMaterial | 머티리얼의 이름, 색상 정보(Ambient, Diffuse, Specular 등), 텍스처 파일 경로 등의 정보를 담고 있는 구조체입니다. |
aiTexture | FBX 파일 내부에 포함된(임베디드된) 텍스처 데이터를 메모리로 읽어올 수 있게 해주는 구조체입니다. |
Assimp::Importer | Assimp에서 FBX 파일을 메모리로 로딩할 때 사용하는 주요 진입점 객체입니다. |
asMaterial | Assimp로부터 파싱한 머티리얼 정보를 우리가 정의한 커스텀 구조로 담기 위한 자료구조입니다. |
tinyxml2 | C++에서 사용 가능한 경량 XML 처리 라이브러리로, XML 파일 생성, 태그 추가, 속성 설정 등을 지원합니다. |
XMLElement | tinyxml2에서 사용되는 XML 태그 객체로, <Material>이나 <Diffuse>와 같은 요소를 표현합니다. |
SetAttribute() | XMLElement에 속성(R, G, B, A 등)을 설정할 때 사용하는 함수입니다. |
SetText() | XMLElement 태그 내의 텍스트 내용을 설정할 때 사용하는 함수입니다. |
WriteTexture() | 텍스처 파일을 내장 또는 외부 경로에서 읽어 지정된 위치로 저장하거나 복사해주는 함수입니다. |
좋습니다! 두 블로그의 코드를 기반으로 작성된 통합 코드 분석 내용을 강의 스타일로 완전하게 정리해드리겠습니다.
이 내용은 단순 요약이 아닌, 실제 강의 현장에서 코드와 함께 설명하기 적합한 형식으로 구성했습니다.
이번 시간에는 FBX의 Material 데이터를 로딩하고, XML로 저장하는 전체 로직을 코드 단위로 살펴보겠습니다.
우리가 구현한 파이프라인은 다음과 같은 구성요소들로 이뤄져 있습니다:
이제 하나씩 코드를 보며 설명하겠습니다.
Converter 클래스 설계class Converter {
public:
void ReadAssetFile(wstring file);
void ExportMaterialData(wstring savePath);
void ExportModelData(wstring savePath);
private:
void ReadMaterialData();
void WriteMaterialData(wstring finalPath);
string WriteTexture(string saveFolder, string file);
...
vector<shared_ptr<asMaterial>> _materials;
};
ReadAssetFile() : FBX 파일을 메모리로 로딩ReadMaterialData() : Assimp에서 머티리얼 정보 추출WriteMaterialData() : XML 파일로 저장WriteTexture() : 텍스처를 복사하거나 추출해서 저장_materials : 우리 방식으로 변환한 머티리얼 리스트ReadAssetFilevoid Converter::ReadAssetFile(wstring file) {
...
_scene = _importer->ReadFile(
Utils::ToString(fileStr),
aiProcess_ConvertToLeftHanded |
aiProcess_Triangulate |
aiProcess_GenUVCoords |
aiProcess_GenNormals |
aiProcess_CalcTangentSpace
);
assert(_scene != nullptr);
}
aiProcess_* 플래그는 FBX를 변환 및 정리할 때 사용하는 옵션들입니다._scene 객체 안에 모든 FBX 정보가 들어옵니다.ReadMaterialDatavoid Converter::ReadMaterialData() {
for (uint32 i = 0; i < _scene->mNumMaterials; i++) {
aiMaterial* srcMaterial = _scene->mMaterials[i];
shared_ptr<asMaterial> material = make_shared<asMaterial>();
material->name = srcMaterial->GetName().C_Str();
...
srcMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, color);
material->diffuse = Color(color.r, color.g, color.b, 1.f);
...
srcMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &file);
material->diffuseFile = file.C_Str();
_materials.push_back(material);
}
}
asMaterial 구조에 저장합니다.WriteTexture()로 넘깁니다.WriteMaterialDatavoid Converter::WriteMaterialData(wstring finalPath) {
auto path = filesystem::path(finalPath);
filesystem::create_directory(path.parent_path());
shared_ptr<tinyxml2::XMLDocument> document = make_shared<tinyxml2::XMLDocument>();
document->LinkEndChild(document->NewDeclaration());
tinyxml2::XMLElement* root = document->NewElement("Materials");
document->LinkEndChild(root);
for (shared_ptr<asMaterial> material : _materials) {
tinyxml2::XMLElement* node = document->NewElement("Material");
root->LinkEndChild(node);
auto addTextNode = [&](const char* name, const string& value) {
auto element = document->NewElement(name);
element->SetText(value.c_str());
node->LinkEndChild(element);
};
addTextNode("Name", material->name);
addTextNode("DiffuseFile", WriteTexture(folder, material->diffuseFile));
addTextNode("SpecularFile", WriteTexture(folder, material->specularFile));
addTextNode("NormalFile", WriteTexture(folder, material->normalFile));
auto addColorNode = [&](const char* tag, const Color& c) {
auto element = document->NewElement(tag);
element->SetAttribute("R", c.x);
element->SetAttribute("G", c.y);
element->SetAttribute("B", c.z);
element->SetAttribute("A", c.w);
node->LinkEndChild(element);
};
addColorNode("Ambient", material->ambient);
addColorNode("Diffuse", material->diffuse);
addColorNode("Specular", material->specular);
addColorNode("Emissive", material->emissive);
}
document->SaveFile(Utils::ToString(finalPath).c_str());
}
<Materials><Material> 태그를 생성하고 내부에 텍스트 정보 및 색상 정보를 삽입SetAttribute()로 RGBA 속성으로 저장됩니다.WriteTexturestring Converter::WriteTexture(string saveFolder, string file) {
string fileName = filesystem::path(file).filename().string();
const aiTexture* srcTexture = _scene->GetEmbeddedTexture(file.c_str());
if (srcTexture) {
// 내장 텍스처 저장 로직
if (srcTexture->mHeight == 0) {
// 바이너리 포맷 저장 구현 예정
} else {
// DX11 Texture → DDS 변환 및 저장
...
}
} else {
// 외부 텍스처 복사
string originStr = (_assetPath / folderName / file).string();
string pathStr = (saveFolder / fileName).string();
::CopyFileA(originStr.c_str(), pathStr.c_str(), false);
}
return fileName;
}
AssimpTool::Init()void AssimpTool::Init() {
shared_ptr<Converter> converter = make_shared<Converter>();
converter->ReadAssetFile(L"House/House.fbx");
converter->ExportMaterialData(L"House/House");
converter->ExportModelData(L"House/House");
}
좋습니다! 두 블로그의 내용을 결합한 핵심 내용을 기반으로, 전체 강의의 마무리를 짓는 ‘핵심 정리’ 파트를 강의하는 듯한 말투로 완성해드리겠습니다.
이번 강의의 핵심은, FBX 파일에서 Material 정보를 자동으로 추출하고, 이를 사람이 편하게 읽고 수정할 수 있는 XML 형태로 변환·저장하는 자동화 파이프라인을 구축하는 데 있습니다.
_scene이라는 루트 구조체 안에 정리됩니다.asMaterial이라는 커스텀 구조체에 담습니다.WriteMaterialData() 함수를 호출하면, 우리가 정의한 XML 포맷으로 머티리얼 데이터를 자동 저장할 수 있습니다.WriteTexture() 함수가 자동으로 경로를 파악해 해당 폴더로 저장하거나 복사해줍니다..xml 머티리얼 문서와 .dds 또는 복사된 텍스처 파일까지 완성되는 자동화 구조가 만들어집니다.aiMaterial을 → asMaterial로 변환 tinyxml2로 <Material> 구조로 XML로 출력 <Materials>
<Material>
<Name>cottage_texture</Name>
<DiffuseFile></DiffuseFile>
<SpecularFile></SpecularFile>
<NormalFile></NormalFile>
<Ambient R="0.2" G="0.2" B="0.2" A="1"/>
<Diffuse R="0.8" G="0.8" B="0.8" A="1"/>
<Specular R="0.2" G="0.2" B="0.2" A="20"/>
<Emissive R="0" G="0" B="0" A="1"/>
</Material>
</Materials>