응애맘마조
230628 강의 본문
어제 완성하지 못했던 본을 완성시켰습니다.
[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 시점에서부터 이미 정렬이 되어 있기 때문에 나중에 맞지 않는 상황이 생기게 됩니다.
읽어주셔서 감사합니다.