헛둘이 2023. 2. 21. 16:36

기존에는 정점들을 이용해 삼각형과 사각형을 만들어 사용했으나,

이제는 실제 3D FBX 모델을 사용하여 모델을 화면에 띄워본다.

 

FBX는 프로그래밍 외에도 여러 용도로 사용할 수 있기 때문에,

내부적으로 다양한 데이터를 담고 있음

 

따라서 FBXLoader를 통해서 내부 데이터를 읽고 파싱해서 긁어오는 과정이 필요하다.

FBX를 로드하는 방법은 Assimp 라이브러리를 쓰거나 Autodesk에서 제공하는 sdk를 사용하는 방법이 있음

 

이번 실습에서는 Autodesk에서 제공하는 fbx sdk를 통해 파일을 로드하는 것

 

FBXLoader 클래스 정의

FBXLoader 클래스에선 fbx 파일을 파싱해서 정점 정보, 인덱스 정보와 애니메이션 정보를 긁어 오는 역할을 하며,

이번 예제에서는 애니메이션에 관한 내용은 다루지 않음

 

내부에는 수많은 함수들이 있고,

그 중 LoadFBX 함수를 주축으로 한 private 함수들로 이루어져 있다.

 

void FBXLoader::LoadFbx(const wstring& path)
{
	// 파일 데이터 로드
	Import(path);

	// Animation	
	LoadBones(_scene->GetRootNode());
	LoadAnimationInfo();

	// 로드된 데이터 파싱 (Mesh/Material/Skin)
	ParseNode(_scene->GetRootNode());

	// 우리 구조에 맞게 Texture / Material 생성
	CreateTextures();
	CreateMaterials();
}

 

- Import 함수를 통해 파일 데이터를 메모리로 로드한다.

- 파일의 Mesh, Material, Skin 데이터를 긁어서 내부 변수인 FbxMeshInfo의 배열에 저장한다.

vector<FbxMeshInfo>					_meshes;

- FbxMeshInfo의 내부 구조

struct FbxMeshInfo
{
	wstring						name;
	vector<Vertex>					vertices;
	vector<vector<uint32>>				indices;
	vector<FbxMaterialInfo>				materials;
	vector<BoneWeight>				boneWeights; // 뼈 가중치
	bool						hasAnimation;
};

 

- 그리고 그 아래 CreateTextures, CreateMaterials는 파일에 같이 동봉된 텍스쳐 파일들을 리소스로 저장하는 부분

void FBXLoader::CreateTextures()
{
	for (size_t i = 0; i < _meshes.size(); i++)
	{
		for (size_t j = 0; j < _meshes[i].materials.size(); j++)
		{
			// DiffuseTexture
			{
				wstring relativePath = _meshes[i].materials[j].diffuseTexName.c_str();
				wstring filename = fs::path(relativePath).filename();
				wstring fullPath = _resourceDirectory + L"\\" + filename;
				if (filename.empty() == false)
					GET_SINGLE(Resources)->Load<Texture>(filename, fullPath);
			}

			// NormalTexture
			{
				wstring relativePath = _meshes[i].materials[j].normalTexName.c_str();
				wstring filename = fs::path(relativePath).filename();
				wstring fullPath = _resourceDirectory + L"\\" + filename;
				if (filename.empty() == false)
					GET_SINGLE(Resources)->Load<Texture>(filename, fullPath);
			}

			// SpecularTexture
			{
				wstring relativePath = _meshes[i].materials[j].specularTexName.c_str();
				wstring filename = fs::path(relativePath).filename();
				wstring fullPath = _resourceDirectory + L"\\" + filename;
				if (filename.empty() == false)
					GET_SINGLE(Resources)->Load<Texture>(filename, fullPath);
			}
		}
	}
}

- 이렇게 파일을 불러온 후에 어떻게 사용을 하는가?

 

 

 

 

MeshData 클래스 정의

- MeshData는 이 데이터를 우리가 쓸 수 있는 방식으로 저장하는 클래스이다.

- 이 함수의 MeshData::LoadFromFBX 함수 내에서 FBXLoader의 인스턴스를 만들어서 사용한다.

- MeshData::LoadFromFBX 일종의 팩토리 함수로, FBX 파일의 주소를 받아서 그 주소에서 데이터를 추출한 후 MeshData 인스턴스로 반환한다.

shared_ptr<MeshData> MeshData::LoadFromFBX(const wstring& path)
{
	FBXLoader loader;
	loader.LoadFbx(path);

	shared_ptr<MeshData> meshData = make_shared<MeshData>();

	for (int32 i = 0; i < loader.GetMeshCount(); i++)
	{
		shared_ptr<Mesh> mesh = Mesh::CreateFromFBX(&loader.GetMesh(i), loader);

		GET_SINGLE(Resources)->Add<Mesh>(mesh->GetName(), mesh);

		// Material 찾아서 연동
		vector<shared_ptr<Material>> materials;
		for (size_t j = 0; j < loader.GetMesh(i).materials.size(); j++)
		{
			shared_ptr<Material> material = GET_SINGLE(Resources)->Get<Material>(loader.GetMesh(i).materials[j].name);
			materials.push_back(material);
		}

		MeshRenderInfo info = {};
		info.mesh = mesh;
		info.materials = materials;
		meshData->_meshRenders.push_back(info);
	}

	return meshData;
}

 

- 함수 내부에서 Mesh의 static 함수인 Mesh::CreateFromFBX를 호출한다.

- Mesh::CreateFromFBX는 인자로 전달받은 meshInfo에서 정점 데이터, 인덱스 데이터를 추출해서 메쉬를 반환한다.

shared_ptr<Mesh> Mesh::CreateFromFBX(const FbxMeshInfo* meshInfo, FBXLoader& loader)
{
	shared_ptr<Mesh> mesh = make_shared<Mesh>();
	mesh->CreateVertexBuffer(meshInfo->vertices);

	for (const vector<uint32>& buffer : meshInfo->indices)
	{
		if (buffer.empty())
		{
			// FBX 파일이 이상하다. IndexBuffer가 없으면 에러 나니까 임시 처리
			vector<uint32> defaultBuffer{ 0 };
			mesh->CreateIndexBuffer(defaultBuffer);
		}
		else
		{
			mesh->CreateIndexBuffer(buffer);
		}
	}

	if (meshInfo->hasAnimation)
		mesh->CreateBonesAndAnimations(loader);
	
	return mesh;
}

- 이렇게 만들어진 함수들은 SceneManager에서 다음과 같이 사용된다.

 

#pragma region FBX
	{
		shared_ptr<MeshData> meshData = GET_SINGLE(Resources)->LoadFBX(L"..\\Resources\\FBX\\Dragon.fbx");

		vector<shared_ptr<GameObject>> gameObjects = meshData->Instantiate();

		for (auto& gameObject : gameObjects)
		{
			gameObject->SetName(L"Dragon");
			gameObject->SetCheckFrustum(false);
			gameObject->GetTransform()->SetLocalPosition(Vec3(0.f, 0.f, 300.f));
			gameObject->GetTransform()->SetLocalScale(Vec3(1.f, 1.f, 1.f));
			scene->AddGameObject(gameObject);
			gameObject->AddComponent(make_shared<TestDragon>());
		}
	}
#pragma endregion