응애맘마조

230628 강의 본문

공부/3D강의

230628 강의

TH.Wert 2023. 6. 28. 19:32

어제 완성하지 못했던 본을 완성시켰습니다.

[flatten]
	if (input.Weights.x)
		output.Position = mul(output.Position, SkinWorld(input.Indices, input.Weights));
		
	output.Position = mul(output.Position, World);

Common.hlsl 파일에 뼈대를 구성할 코드가 추가되었습니다.

matrix SkinWorld(float4 indices, float4 weights)
{
	matrix transform = 0;
	transform += mul(weights.x, Bones[(uint) indices.x]);
	transform += mul(weights.y, Bones[(uint) indices.y]);
	transform += mul(weights.z, Bones[(uint) indices.z]);
	transform += mul(weights.w, Bones[(uint) indices.w]);
	return transform;
}

SkinWorld 함수는 행렬로써 비율을 1로 만드는 함수입니다.

bones[node->boneIndex] = bonesOffset[node->boneIndex] * node->W;
bones[node->boneIndex] = bones[node->boneIndex].Transpose();

앞으로는 뼈대의 위치를 갱신하기 위해 위의 코드가 추가되었습니다.

if (skeleton)
	skeleton->Set();

따라서 렌더링 할 때도 Set 함수로 런타임 중에 표기할 수 있도록 했습니다.

#pragma once

class Main : public Scene
{
private:
	Camera*				Cam;
	Grid*				grid;

	Actor*				temp;
	string				file;

	Assimp::Importer	importer;
	const aiScene*		scene;

public:
	Main();
	~Main();
	virtual void Init() override;
	virtual void Release() override; //해제
	virtual void Update() override;
	virtual void LateUpdate() override;//갱신
	virtual void Render() override;
	virtual void PreRender() override;
	virtual void ResizeScreen() override;

	void MakeHierarchy(aiNode* node, GameObject* node2);
	void MakeMesh(aiNode* node, GameObject* node2);
	void MakeMaterial();

	Matrix ToMatrix(aiMatrix4x4& value)
	{
		return Matrix
		(
			value.a1, value.b1, value.c1, value.d1,
			value.a2, value.b2, value.c2, value.d2,
			value.a3, value.b3, value.c3, value.d3,
			value.a4, value.b4, value.c4, value.d4
		);
	};
	void ReadBoneData(aiMesh* mesh, vector<class VertexWeights>& vertexWeights);
};

#define MAX_WEIGHTS 4
struct VertexWeights
{
	UINT	boneIdx[MAX_WEIGHTS];
	float	boneWeights[MAX_WEIGHTS];
	VertexWeights()
	{
		ZeroMemory(boneIdx, sizeof(UINT) * MAX_WEIGHTS);
		ZeroMemory(boneWeights, sizeof(float) * MAX_WEIGHTS);
	}
	void AddData(UINT boneId, float weight)
	{
		for (UINT i = 0; i < MAX_WEIGHTS; i++)
		{
			if (boneWeights[i] == 0.0f)
			{
				boneIdx[i] = boneId;
				boneWeights[i] = weight;
				return;
			}
		}
	}
	void Normalize()
	{
		float total = 0.0f;
		for (UINT i = 0; i < MAX_WEIGHTS; i++)
		{
			if (boneWeights[i] != 0.0f)
			{
				total += boneWeights[i];
			}
		}
		for (UINT i = 0; i < MAX_WEIGHTS; i++)
		{
			if (boneWeights[i] != 0.0f)
			{
				boneWeights[i] /= total;
			}
		}
	}
};
#include "stdafx.h"
#include "Main.h"

Main::Main()
{
}

Main::~Main()
{
}

void Main::Init()
{
	Cam = Camera::Create();
	Cam->LoadFile("Cam.xml");
	Camera::main = Cam;
	Cam->width = App.GetWidth();
	Cam->height = App.GetHeight();
	Cam->viewport.width = App.GetWidth();
	Cam->viewport.height = App.GetHeight();

	grid = Grid::Create();

	temp = Actor::Create();
}

void Main::Release()
{
}

void Main::Update()
{
	ImGui::Begin("Hierarchy");
	Cam->RenderHierarchy();
	temp->RenderHierarchy();
	grid->RenderHierarchy();
	ImGui::End();

	if (GUI->FileImGui("ModelImporter", "ModelImporter",
		".fbx,.obj,.x", "../Assets"))
	{
		file = ImGuiFileDialog::Instance()->GetCurrentFileName();
		string path = "../Assets/" + file;

		importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false);
		scene = importer.ReadFile
		(
			path,
			aiProcess_ConvertToLeftHanded
			| aiProcess_Triangulate
			| aiProcess_GenUVCoords
			| aiProcess_GenNormals
			| aiProcess_CalcTangentSpace
		);
		assert(scene != NULL and "Import Error");

		temp->ReleaseMember();
		temp->skeleton = new Skeleton();

		MakeMaterial();

		GameObject* empty = GameObject::Create("empty");
		temp->AddChild(empty);
		temp->Update();

		MakeHierarchy(scene->mRootNode, empty);

		{
			int tok = file.find_last_of(".");
			string checkPath = "../Contents/Skeleton/" + file.substr(0, tok);
			if (!PathFileExistsA(checkPath.c_str()))
			{
				CreateDirectoryA(checkPath.c_str(), NULL);
			}

			string filePath = file.substr(0, tok) + "/";
			temp->skeleton->file = filePath + file.substr(0, tok) + ".skel";
			temp->skeleton->BonesUpdate(temp);
			temp->skeleton->SaveFile(temp->skeleton->file);
		}
        
		MakeMesh(scene->mRootNode, empty);
		importer.FreeScene();
	}

	temp->skeleton->BonesUpdate(temp);

	Camera::ControlMainCam();
	Cam->Update();
	grid->Update();
	temp->Update();
}

void Main::LateUpdate()
{
}

void Main::PreRender()
{
}

void Main::Render()
{
	Cam->Set();
	grid->Render();
	temp->Render();
}

void Main::ResizeScreen()
{
}

void Main::MakeHierarchy(aiNode* node, GameObject* node2)
{
	Matrix tempMat = ToMatrix(node->mTransformation);
	Vector3 s, r, t; Quaternion q;
	tempMat.Decompose(s, q, t);
	r = Util::QuaternionToYawPtichRoll(q);

	node2->scale = s;
	node2->rotation = r;
	node2->SetLocalPos(t);

	temp->Update();

	temp->skeleton->bonesOffset[node2->boneIndex]
		= node2->W.Invert();

	for (int i = 0; i < node->mNumChildren; i++)
	{
		GameObject* child = GameObject::Create(node->mChildren[i]->mName.C_Str());
		node2->AddChild(child);
		MakeHierarchy(node->mChildren[i], child);
	}
}

void Main::MakeMesh(aiNode* node, GameObject* node2)
{
	//루트 노드에 담겨있는 메쉬 갯수만큼 반복
	for (int i = 0; i < node->mNumMeshes; i++)
	{
		int index = node->mMeshes[i];
		aiMesh* mesh = scene->mMeshes[index];
		aiMaterial* mtl = scene->mMaterials[mesh->mMaterialIndex];
		string mtlFile = mtl->GetName().C_Str();
		int tok = file.find_last_of(".");
		string filePath = file.substr(0, tok) + "/";
		mtlFile = filePath + mtlFile + ".mtl";
        
		GameObject* Current = node2;
		//메쉬가 두개 이상일때
		if (i != 0)
		{
			Current = 
				GameObject::Create(node2->name + "meshObject" + to_string(i));
			node2->AddChild(Current);
		}
		Current->shader = RESOURCE->shaders.Load("4.Cube.hlsl");
		Current->material =new Material();
		Current->material->LoadFile(mtlFile);
		Current->mesh = make_shared<Mesh>();
		Current->mesh->byteWidth = sizeof(VertexModel);
		Current->mesh->primitiveTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		Current->mesh->vertexType = VertexType::MODEL;
		Current->mesh->vertexCount = mesh->mNumVertices;
		Current->mesh->vertices = new VertexModel[mesh->mNumVertices];
		vector<UINT> indexList;

		vector<VertexWeights>	VertexWeights;
		VertexWeights.resize(mesh->mNumVertices);
		ReadBoneData(mesh, VertexWeights);

		for (int j = 0; j < mesh->mNumVertices; j++)
		{
			VertexModel* vertex = (VertexModel*)Current->mesh->vertices;
			//aiVector3D
			//텍스쳐 좌표가 있다면
			if (mesh->HasTextureCoords(0))
			{
				vertex[j].uv.x =mesh->mTextureCoords[0][j].x;
				vertex[j].uv.y =mesh->mTextureCoords[0][j].y;
			}
			if (mesh->HasNormals())
			{
				vertex[j].normal.x = mesh->mNormals[j].x;
				vertex[j].normal.y = mesh->mNormals[j].y;
				vertex[j].normal.z = mesh->mNormals[j].z;
			}
			if (mesh->HasPositions())
			{
				vertex[j].position.x = mesh->mVertices[j].x;
				vertex[j].position.y = mesh->mVertices[j].y;
				vertex[j].position.z = mesh->mVertices[j].z;
			}
			if (mesh->HasTangentsAndBitangents())
			{
				vertex[j].tangent.x = mesh->mTangents[j].x;
				vertex[j].tangent.y = mesh->mTangents[j].y;
				vertex[j].tangent.z = mesh->mTangents[j].z;
			}
			//본데이터가 있을때
			if (!VertexWeights.empty())
			{
				VertexWeights[j].Normalize();

				vertex[j].indices.x = (float)VertexWeights[j].boneIdx[0];
				vertex[j].indices.y = (float)VertexWeights[j].boneIdx[1];
				vertex[j].indices.z = (float)VertexWeights[j].boneIdx[2];
				vertex[j].indices.w = (float)VertexWeights[j].boneIdx[3];

				vertex[j].weights.x = VertexWeights[j].boneWeights[0];
				vertex[j].weights.y = VertexWeights[j].boneWeights[1];
				vertex[j].weights.z = VertexWeights[j].boneWeights[2];
				vertex[j].weights.w = VertexWeights[j].boneWeights[3];
			}
		}

		for (int j = 0; j < mesh->mNumFaces; j++)
		{
			for (int k = 0; k < mesh->mFaces[j].mNumIndices; k++)
			{
				indexList.push_back(mesh->mFaces[j].mIndices[k]);
			}
		}

		Current->mesh->indexCount = indexList.size();
		Current->mesh->indices = new UINT[indexList.size()];
		copy(indexList.begin(), indexList.end(), 
			stdext::checked_array_iterator<UINT*>
			(Current->mesh->indices, indexList.size()));

		Current->mesh->Reset();
        
		{
			int tok = file.find_last_of(".");
			string checkPath = "../Contents/Mesh/" + file.substr(0, tok);
			if (!PathFileExistsA(checkPath.c_str()))
			{
				CreateDirectoryA(checkPath.c_str(), NULL);
			}

			string filePath = file.substr(0, tok) + "/";
			string meshFile = mesh->mName.C_Str();
			Current->mesh->file = filePath + meshFile + ".mesh";
			Current->mesh->SaveFile(Current->mesh->file);
		}
	}
	for (UINT i = 0; i < node->mNumChildren; i++)
	{
		MakeMesh(node->mChildren[i],node2->children[node->mChildren[i]->mName.C_Str()]);
	}
}

void Main::MakeMaterial()
{
	for (int i = 0; i < scene->mNumMaterials; i++)
	{
		aiMaterial* srcMtl = scene->mMaterials[i];
		Material* destMtl = new Material();
		aiColor3D tempColor;

		destMtl->file = srcMtl->GetName().C_Str();

		//ambient
		srcMtl->Get(AI_MATKEY_COLOR_AMBIENT, tempColor);
		destMtl->ambient.x = tempColor.r;
		destMtl->ambient.y = tempColor.g;
		destMtl->ambient.z = tempColor.b;

		//diffuse
		srcMtl->Get(AI_MATKEY_COLOR_DIFFUSE, tempColor);
		destMtl->diffuse.x = tempColor.r;
		destMtl->diffuse.y = tempColor.g;
		destMtl->diffuse.z = tempColor.b;

		//specular
		srcMtl->Get(AI_MATKEY_COLOR_SPECULAR, tempColor);
		destMtl->specular.x = tempColor.r;
		destMtl->specular.y = tempColor.g;
		destMtl->specular.z = tempColor.b;

		//emissive
		srcMtl->Get(AI_MATKEY_COLOR_EMISSIVE, tempColor);
		destMtl->emissive.x = tempColor.r;
		destMtl->emissive.y = tempColor.g;
		destMtl->emissive.z = tempColor.b;

		//Shininess
		srcMtl->Get(AI_MATKEY_SHININESS, destMtl->shininess);
		//opacity
		srcMtl->Get(AI_MATKEY_OPACITY, destMtl->opacity);

		//Normal
		{
			aiString aifile;
			string TextureFile;
			aiReturn texFound;
			texFound = srcMtl->GetTexture(aiTextureType_NORMALS, 0, &aifile);
			TextureFile = aifile.C_Str();
			size_t index = TextureFile.find_last_of('/');
			TextureFile = TextureFile.substr(index + 1, TextureFile.length());

			//텍스쳐가 있다.
			if (texFound == AI_SUCCESS && file != "")
			{
				destMtl->ambient.w = 1.0f;
				destMtl->normalMap = make_shared<Texture>();

				size_t tok = file.find_last_of(".");
				string checkPath = "../Contents/Texture/" + file.substr(0, tok);
				if (!PathFileExistsA(checkPath.c_str()))
				{
					CreateDirectoryA(checkPath.c_str(), NULL);
				}
				string orgin = "../Assets/" + TextureFile;
				string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
				bool isCheck = true;
				CopyFileA(orgin.c_str(), copy.c_str(), isCheck);

				destMtl->normalMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
			}
		}

		//Diffuse
		{
			aiString aifile;
			string TextureFile;
			aiReturn texFound;
			texFound = srcMtl->GetTexture(aiTextureType_DIFFUSE, 0, &aifile);
			TextureFile = aifile.C_Str();
			size_t index = TextureFile.find_last_of('/');
			TextureFile = TextureFile.substr(index + 1, TextureFile.length());

			//텍스쳐가 있다.
			if (texFound == AI_SUCCESS && file != "")
			{
				destMtl->diffuse.w = 1.0f;
				destMtl->diffuseMap = make_shared<Texture>();

				size_t tok = file.find_last_of(".");
				string checkPath = "../Contents/Texture/" + file.substr(0, tok);
				if (!PathFileExistsA(checkPath.c_str()))
				{
					CreateDirectoryA(checkPath.c_str(), NULL);
				}
				string orgin = "../Assets/" + TextureFile;
				string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
				bool isCheck = true;
				CopyFileA(orgin.c_str(), copy.c_str(), isCheck);

				destMtl->diffuseMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
			}
		}

		//specular
		{
			aiString aifile;
			string TextureFile;
			aiReturn texFound;
			texFound = srcMtl->GetTexture(aiTextureType_SPECULAR, 0, &aifile);
			TextureFile = aifile.C_Str();
			size_t index = TextureFile.find_last_of('/');
			TextureFile = TextureFile.substr(index + 1, TextureFile.length());

			//텍스쳐가 있다.
			if (texFound == AI_SUCCESS && file != "")
			{
				destMtl->specular.w = 1.0f;
				destMtl->specularMap = make_shared<Texture>();

				size_t tok = file.find_last_of(".");
				string checkPath = "../Contents/Texture/" + file.substr(0, tok);
				if (!PathFileExistsA(checkPath.c_str()))
				{
					CreateDirectoryA(checkPath.c_str(), NULL);
				}
				string orgin = "../Assets/" + TextureFile;
				string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
				bool isCheck = true;
				CopyFileA(orgin.c_str(), copy.c_str(), isCheck);

				destMtl->specularMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
			}
		}

		//emissive
		{
			aiString aifile;
			string TextureFile;
			aiReturn texFound;
			texFound = srcMtl->GetTexture(aiTextureType_EMISSIVE, 0, &aifile);
			TextureFile = aifile.C_Str();
			size_t index = TextureFile.find_last_of('/');
			TextureFile = TextureFile.substr(index + 1, TextureFile.length());

			//텍스쳐가 있다.
			if (texFound == AI_SUCCESS && file != "")
			{
				destMtl->emissive.w = 1.0f;
				destMtl->emissiveMap = make_shared<Texture>();

				size_t tok = file.find_last_of(".");
				string checkPath = "../Contents/Texture/" + file.substr(0, tok);
				if (!PathFileExistsA(checkPath.c_str()))
				{
					CreateDirectoryA(checkPath.c_str(), NULL);
				}
				string orgin = "../Assets/" + TextureFile;
				string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
				bool isCheck = true;
				CopyFileA(orgin.c_str(), copy.c_str(), isCheck);

				destMtl->emissiveMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
			}
		}

		size_t tok = file.find_last_of(".");
		string checkPath = "../Contents/Material/" + file.substr(0, tok);
		if (!PathFileExistsA(checkPath.c_str()))
		{
			CreateDirectoryA(checkPath.c_str(), NULL);
		}

		string filePath = file.substr(0, tok) + "/";
		destMtl->file = filePath + destMtl->file + ".mtl";
		destMtl->SaveFile(destMtl->file);
	}
}

void Main::ReadBoneData(aiMesh* mesh, vector<class VertexWeights>& vertexWeights)
{
	//메쉬가 가지고 있는 본 개수 만큼
	for (UINT i = 0; i < mesh->mNumBones; i++)
	{
		//현재본이 하이어라이키에서 몇번째 인덱스인가?
		string boneName = mesh->mBones[i]->mName.C_Str();
		int boneIndex = temp->Find(boneName)->boneIndex;
		for (UINT j = 0; j < mesh->mBones[i]->mNumWeights; j++)
		{
			UINT vertexID = mesh->mBones[i]->mWeights[j].mVertexId;
			vertexWeights[vertexID].AddData(boneIndex, mesh->mBones[i]->mWeights[j].mWeight);
		}
	}
}

int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR param, int command)
{
	App.SetAppName(L"ObjLoader");
	App.SetInstance(instance);
    WIN->Create();
    D3D->Create();
	Main * main = new Main();
    main->Init();

	int wParam = (int)WIN->Run(main);

    main->Release();
	SafeDelete(main);
    D3D->DeleteSingleton();
	WIN->DeleteSingleton();

	return wParam;
}

최종적으로 완료된 Main 헤더와 cpp 파일입니다. 여기에서 이제 주의할 점은 로드를 하고 만약에 모델의 크기를 변경해야 한다면 로더에서 먼저 로드를 하고 사이즈를 조정한 뒤에 이번에 ImGui에서 추가한 BonesUpdate를 한 후에 Actor로 저장을 하고 게임 실행 창에서 사용해야 됩니다. 그냥 게임 창 내에서 사용하게 되면 이미 로드를 한 T-Pose 시점에서부터 이미 정렬이 되어 있기 때문에 나중에 맞지 않는 상황이 생기게 됩니다.

 

읽어주셔서 감사합니다.

'공부 > 3D강의' 카테고리의 다른 글

230630 강의  (0) 2023.06.30
230629 강의  (0) 2023.06.29
230627 강의  (0) 2023.06.27
230626 강의  (0) 2023.06.26
230623 강의  (0) 2023.06.23