프로그래밍 공부
작성일
2023. 11. 2. 15:49
작성자
WDmil
728x90

Assimp를 설치한다.

 

설치시 Cmake를 사용하여 lib파일을 뽑아서 사용한다.

 

Assimp는 FBX파일에서 데이터를 긁어올 수 있도록 하는것만 사용하고, 긁어온 데이터를 현재 프로젝트에서 사용할 수 있도록 재가공하는 방식은 사용자에 따라 달라진다.

 

자신이 구축해놓은 Framework에 맞게 데이터를 재가공해 사용하도록 하자.

#pragma once

class ModelExporter
{
public:
	ModelExporter(string name, string file);
	~ModelExporter();

	void ExportMaterial();

private:
	//Material
	void ReadMaterial();// 머티리얼 읽기.
	void WriterMaterial(); // 머티얼 써서 저장하기.
	string CreateTexture(string file);

private:
	Assimp::Importer* importer;
	const aiScene* scene;
	//aiScene 는 상수로 사용해야한다. 무조건.

	string name;

	vector<Material*> materials;
};

머티리얼을 생성하고 읽어오는 객체이다.

생성시, 객체의 이름과 파일경로를 받아서 생성된다.


생성자

ModelExporter::ModelExporter(string name, string file)
	:	name(name)
{
	importer = new Assimp::Importer();

	// 왼손자표계 컨버트 | 최대퀄리티로
	scene = importer->ReadFile(file,
		aiProcess_ConvertToLeftHanded | aiProcessPreset_TargetRealtime_MaxQuality);

	// fbx가 없을경우, 정보가 잘못되었을 경우 터트리는게 더 낫다.
	assert(scene != nullptr);
	/*
		NumVertices = 버텍스 수
		NumFaces = 삼각형 그리는 수
		Tangents = 데이터 노멀맵
	*/
}

각각 importer 와 scene 그리고 name, materials로 정해지는데,

 

importer는 객체를 긁어오는 주체가되고, scene는 현재 머티리얼을 생성할 때 Vertices의 기본설정을 담당한다.

 

fbx가 존재하지 않을경우, 또는 긁어오지 못했을 경우 assert를 사용해서 터트린다.

 


void ModelExporter::ExportMaterial()
{
	ReadMaterial();
	WriterMaterial();
}

머티리얼을 사용할 때 에는, 머티리얼을 읽은 뒤 쓰는 함수를 사용한다.


ReadMaterial

void ModelExporter::ReadMaterial()
{
	FOR(scene->mNumMaterials)
	{
		aiMaterial* srcMaterial = scene->mMaterials[i];
		Material* material = new Material();

		material->SetName(srcMaterial->GetName().C_Str());

		MaterialBuffer::Data* data = material->GetBuffer();

		aiColor3D color;
		srcMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, color);// 난반사 컬러 설정
		data->diffuse = Float4(color.r, color.g, color.b, 1.0f);

		srcMaterial->Get(AI_MATKEY_COLOR_SPECULAR, color);// 스펙큘러
		data->specular = Float4(color.r, color.g, color.b, 1.0f);

		srcMaterial->Get(AI_MATKEY_COLOR_AMBIENT, color);// 엠비언트
		data->ambient = Float4(color.r, color.g, color.b, 1.0f);

		srcMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, color);// 자체발광
		data->emissive = Float4(color.r, color.g, color.b, 1.0f);

		srcMaterial->Get(AI_MATKEY_SHININESS, data->shininess); // 빛 반사의 날카로움.

		aiString file;
		srcMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &file);
		material->SetDiffuseMap(ToWString(CreateTexture(file.C_Str())));
		file.Clear();

		srcMaterial->GetTexture(aiTextureType_SPECULAR, 0, &file);
		material->SetSpecularMap(ToWString(CreateTexture(file.C_Str())));
		file.Clear();

		srcMaterial->GetTexture(aiTextureType_NORMALS, 0, &file);
		material->SetNormalMap(ToWString(CreateTexture(file.C_Str())));
		file.Clear();

		materials.push_back(material);
	}
}

현재 생성된 scene에 대한 mNumMaterials(현재 vertices숫자)만큼 반복하여 각 Vertices만큼 데이터를 뽑아온다.

aiMerterial형태의 srcMateirl에 현재 반복중인 객체중 i번째 객체를 할당하고 시작한다.

 

FrameWork에 사용중인 Material에 데이터를 기입하기 위해 새로운 Material을 생성하고,

material에 필요데이터를 하나하나 기입해준다.

 

Material에 필요한 데이터들중, 이름과 데이터, 데이터에 해당하는 Diffsue, SpecUlar, AmBinet, Emissive값을 기입한다.

 

각 필요 파일데이터가 MaterialBUffer에 기입되었으면,

현재 해당 객체에 같이. 사용하는 맵 스팩큘러맵, 노멀맵을 기입하여 사용할 수 있도록 해준다.

 

file은 string변수 임으로 재사용해준다.

 

그리고 매번 반복될 때 마다. material을 push하여 로드해준다.

 


void ModelExporter::WriterMaterial()
{
	string savePath = "Models/Materials/" + name + "/";
	string file = name + ".mats";

	CreateFolders(savePath);

	// 배열 저장시 무조건 배열 길이부터 저장해야한다.
	BinaryWriter* writer = new BinaryWriter(savePath + file);

	writer->UInt(materials.size());

	for (Material* material : materials)
	{
		string path = savePath + material->GetName() + ".mat";
		material->Save(path);

		writer->String(path);

		delete material;
		
	}
	materials.clear();

	delete writer;
}

현재 머티리얼 데이터를 Save해주는 함수이다.

경로는 절대경로 Models/Materials에 해당 파일 이름을 가진 폴더를 만들고 각 파일데이터에 mat을 붙여 생성한다.

 

항상 배열을 저장시에는 배열 길이부터 저장하여. Writer를 사용할 수 있도록 한다.

 

UInt형 materials.size()하여. 현 머티리얼대로 값을 잡아주고,

 

for문으로 모든 material값을 지정하여 저장해준다.

 

material에 지정되어있는 Save함수를 사용하여 path 에 해당하는 경로에 저장하게 해준다.

wirter에 path를 String으로 사용한다.

 

저장이 끝났으면, materials는 삭제해준다.

저장경로

저장결과


CreateTexture

FBX파일에는 Texture이 포함되어있다. 이 Texture를 png 파일로 뽑아 저장해보자.

string ModelExporter::CreateTexture(string file)
{
	// 입력된 파일명의 길이가 0일 경우, 빈 문자열("")을 반환한다.
	if (file.length() == 0)
		return "";
	
	// 입력된 파일명에서 확장자를 제외한 부분을 가져와서 ".png확장자를 붙여준다. 원래 확장자는 무시한다.
	string fileName = GetFileNameWithoutExtension(file) + ".png"; // 확장자 설정
	string path = "Textures/Model/" + name + "/" + fileName;

	// 경로에 해당하는 폴더를 생성하는 함수를 호출
	CreateFolders(path); // 폴더 만들기

	// Assimp라이브러리를 사용하여 입력된 파일명에 해당하는 내장 텍스처를 긁어온다.
	const aiTexture* srcTexture = scene->GetEmbeddedTexture(file.c_str());

	// 내장 텍스처가 존재하지 않는경우 빈 문자열("")을 반환한다.
	// 프레임워크상 흰 텍스쳐가 적용된다.
	if (srcTexture == nullptr)
		return "";

	// 내장텍스처의 높이가 1보다 작은경우, 바이너리파일로 저장한다
	// 내장텍스처의 높이가 1보다 클 경우, 3D텍스처를 의미한다.
	// 일반적인 텍스처 와는 다르게 3D공간에 지정하여 렌더링해준다.
	if (srcTexture->mHeight < 1)
	{
		BinaryWriter w(path);
		w.Byte(srcTexture->pcData, srcTexture->mWidth);
		// 그냥 그대로 한줄씩 읽어버린뒤에 Byte로 밀어서 저장해버리면 png로 읽으면 읽어진다!
	}
	else
	{
		// 이미지 객체를 생성하고 텍스처 데이터를 이 객체에 저장한다.
		Image image; 
		image.width = srcTexture->mWidth; // 이미지의 너비크기(가로픽셀수)
		image.height = srcTexture->mHeight; // 이미지의 높이크기(세로픽셀수)
		image.pixels = (uint8_t*)(srcTexture->pcData); // 이미지데이터의 포인터 설정

		// 이미지 데이터의 각 행(row)이 차지하는 바이트 수를 설정.
		// 4는 일반적으로 RGBA색상 형식을 나타낸다. 각 픽셀당 4바이트를 사용.
		image.rowPitch = image.width * 4;

		// 이미지 데이터의 전체 크기를 나타내는 값으로 너비 * 높이 * 4바이트를 계산하여 설정한다.
		// 이미지 전체 데이터이의 크기를 나타낸다.
		image.slicePitch = image.width * image.height * 4;

		// WIC를 사용하여 이미지를 파일로 저장한다.
		SaveToWICFile(
			image,						// 1. 이미지 데이터를 저장하는 Image 구조체
			WIC_FLAGS_NONE,				// 2. WIC에 대한 플래그 설정. 여기서는 아무 특별한 플래그가 사용되지 않음을 나타낸다.
			GetWICCodec(WIC_CODEC_PNG), // 3. WIC 코덱을 얻어온다. 이 코드에서는 PNG 형식을 사용하여 이미지를 저장.
			ToWString(path).c_str()		// 4. 이미지를 저장할 파일 경로를 유니코드 문자열로 변환하여 전달한다.
		);
	}

	return path;
}

위 항목대로 데이터가 저장된다.

요로코롬 Scene을 만들어서 돌려주면

요렇게 나타난다.

 

모델은 Mixamo에서 가져왔다.

https://www.mixamo.com/#/

728x90