필요한 개념
- Material은 Xml로 파일로 저장한다. 직접 외부에서 수정가능 하도록한다.
- Material(Color+ Texture)로 이루어져 있다.
<Color 값>
Color Ambient;
Color Diffuse;
Color Specular;
Color Emissive;
<텍스쳐 파일 경로>
string DiffuseFile;
string SpecularFile;
string NormalFile;
- *Material 텍스쳐 경로 문제
case 1) 파일명이 들어오지 않는다면 임의의 파일을 써줘야 한다(Tank가 그렇다.) -> .material texture 경로에 넣어줘야함
case 2) 내장 텍스쳐가 있다면
case 2-1) 배열의 이미지가 y축은 없고(0인경우, 높이가 없다.) x축만 가지고 있는경우 -> 1자로 쭉 들어가 있음
case 2-2) x,y축을 가지고 있는 경우
case 3) 내장 텍스쳐가 없다면(파일이 따로 있다.)
Converter.h
더보기
#pragma once
class Converter
{
public:
Converter();
~Converter();
void ReadFile(wstring file);
public:
//읽어 들여서 원하는 파일로 저장
void ExportMesh(wstring savePath);
private:
// 즉 원하는 정보를 -> 변수로 저장하고, 원하는 파일의 형태로 써준다.
// Scene Root를 재귀를 타서 Trasformation(Bone) 정보를 가져올 것이다.
void ReadBoneData(aiNode* node, int index, int parent);
// Scene Root를 재귀를 타서 Mesh 정보를 가져올 것이다.]
// bone : mesh가 참조할 자기의 Bone번호
void ReadMeshData(aiNode* node, int bone);
// 우리가 원하는 형태의 파일로 써준다.
void WriteMeshData(wstring savePath);
public:
// 디자인의 방법, 모델에 따라 Material파일을 임의로 수정할 필요가 생김
// 직접 수정해야 하기에 Xml로 저장한다.
// 2번째인자 : 덮어쓸지 여부(파일있으면 덮어씀), 만약 임의로 수정해서
// 덮어씌우면 안되면 false이다.
void ExportMaterial(wstring savePath, bool bOverWrite = true);
private:
void ReadMaterialData();
void WriteMaterialData(wstring savePath);
string WriteTexture(string saveFolder, string file);
private:
// 모델 파일 주소
wstring file;
// Scene 로드를 위해
Assimp::Importer* importer;
// Model 관련된 정보가 있다.
const aiScene* scene;
// 우리는 접두사로 as(Assimp)를 했고
// Assimp는 ai라는 접두사가 붙는다.
vector<struct asBone*> bones;
vector<struct asMesh*> meshes;
vector<struct asMaterial*> materials;
};
Converter.Cpp
더보기
#include "stdafx.h"
#include "Converter.h"
#include "Types.h"
#include "Utilities/BinaryFile.h"
#include "Utilities/Xml.h"
Converter::Converter()
{
importer = new Assimp::Importer();
}
Converter::~Converter()
{
SafeDelete(importer);
}
void Converter::ReadFile(wstring file)
{
this->file = L"../../_Assets/" + file;
// aiProcess_ConvertToLeftHanded : 왼손좌표계로 수정, 우리는 왼손좌표계, 행우선(가로)를 사용한다. 우리는 행우선으로
// x,y,z하는데
// 일반 3D 그래픽은 오른손좌표계, 열우선(세로)을 사용한다. 열우선은 세로 축 방향으로 x,y,z한다.
// 우리가 아는 삼각형으로 모델을 디자인 하는 방식 외에도 스컬팅, 넙스 등 여러가지 포맷이 있다.
// 그리는 방식이 많다.
// 그러나 우리는 삼각형으로 그린다.
// aiProcess_Triangulate : 삼각형으로 변환해서 데이터를 달라
// aiProcess_GenUVCoords : 우리가 위에 삼각형으로 바꿨으니까 UV도 변환해서 달라는 얘기
// aiProcess_GenNormals : 삼각형 단위로 바뀌었으니까, 노멀도 다시 계산해달라
// aiProcess_CalcTangentSpace : 나중에 NormalMap할때 설명해주심
scene = importer->ReadFile(String::ToString(this->file),
aiProcess_ConvertToLeftHanded | aiProcess_Triangulate | aiProcess_GenUVCoords |
aiProcess_GenNormals | aiProcess_CalcTangentSpace);
// 부를 수 없다면 scene이 null이 나온다.
assert(scene != nullptr);
}
void Converter::ExportMesh(wstring savePath)
{
// 확장자 .mesh로 임의의 확장자로 저장해서 mesh 정보 저장
savePath = L"../../_Models/" +savePath + L".mesh";
// Root노드의 부모는 -1이다.
ReadBoneData(scene->mRootNode, -1, -1);
WriteMeshData(savePath);
}
void Converter::ReadBoneData(aiNode* node, int index, int parent)
{
// 본 정보 읽기
asBone* bone = new asBone();
bone->Index = index;
// C_Str() -> const char로 리턴
bone->Name = node->mName.C_Str();
bone->Parent = parent;
// node->mTransformation[0] 시작주소를 넘긴다.
// mTransformation -> 배열이어서
// D3DXMATRIX(CONST FLOAT*); -> 얘는 시작부터 16개 까지 끊어버림
// 그래서 가능
Matrix transform(node->mTransformation[0]);
// Assimp는 열우선이어서 -> 행우선으로 바꿔준다.
// 전치
D3DXMatrixTranspose(&bone->Transform, &transform);
// 부모 자식 관계를 맺어줌
Matrix matParent;
if (parent < 0)
D3DXMatrixIdentity(&matParent);
// 어떤 사람들은 부모 자식 관계를 움직일때 root를 움직이는 방법을 쓴다. -> 안좋음, 나중에 꼬임
// 우리는 root를 Identity로 해버린다.
else
matParent = bones[parent]->Transform;
// 항상 행렬의 곱은 왼쪽이 기준이 된다. 왼쪽을 기점으로 얼마만큼 이동할지는
// 오른쪽 곱이 결정한다.
// 자기를 기점으로 부모 만큼 움직일 것이다.
bone->Transform = bone->Transform * matParent;
bones.push_back(bone);
// 메쉬 정보 읽기
// 각 노드가 가지고 있는 Mesh정보를 읽어들임
ReadMeshData(node, index);
// 2번째 인자 : 다음 애의 인덱스가 된다.
// 3번째 인자 : 자신의 인덱스가 자식의 부모가 된다.
for (UINT i = 0; i < node->mNumChildren; i++)
ReadBoneData(node->mChildren[i], bones.size(), index);
}
void Converter::ReadMeshData(aiNode* node, int bone)
{
// Bone은 있고 Mesh는 없는 경우가 있다.
// Mesh의 갯수가 1보다 작다면 할 필요가 없다.
if (node->mNumMeshes < 1) return;
asMesh* mesh = new asMesh();
mesh->Name = node->mName.C_Str();
// 본을 그릴때 본의 위치를 가지고 그려진다.
// 넘어오는 인자 bone의 번호를 그대로 참조해준다.
mesh->BoneIndex = bone;
for (UINT i = 0; i < node->mNumMeshes; ++i)
{
//Mesh 정보들을 순서대로 불러다가 써줌
// node->mMeshes에는 정보가 아닌 번호를 가지고 있다.
// 그 배열 번호는 씬에 있는 메시 배열의 번호이다.
// 이 번호로 씬에 있는 메쉬 정보를 가져옴
UINT index = node->mMeshes[i];
aiMesh* srcMesh = scene->mMeshes[index];
// 매터리얼도 번호를 가지고 있으므로 씬의 매터리얼 배열에서 값을 가져옴
aiMaterial* material = scene->mMaterials[srcMesh->mMaterialIndex];
mesh->MaterialName = material->GetName().C_Str();
// 매시마다 정점을 다가지고 있다.
// 매시가 여러개가 되어도 정점을 계속 이어서 저장하려고
// 이전에 쌓아놓은 크기에서 시작한다.
UINT startVertex = mesh->Vertices.size();
// 계속 반복면서 정점이 쌓인다.
for (UINT v = 0; v < srcMesh->mNumVertices; v++)
{
Model::ModelVertex vertex;
// 메쉬가 존재한다면 position은 항상 존재
memcpy(&vertex.Position, &srcMesh->mVertices[v], sizeof(Vector3));
// 텍스쳐 좌표가 없을 수도 있다.
// 데칼으로 해서 레어어를 여러겹을 해서 텍스쳐를 여러개 쓸 수 있다.(3D 프로그래밍)
// -> 게임에서는 문신, 데칼을 따른 방식으로 그림(일반적으로 0번으로 고정시켜서 함)
// 레이어 때문에 mTextureCoords는 2차원 배열이다. 그래서 0번하고 가져옴
if (srcMesh->HasTextureCoords(0))
memcpy(&vertex.Uv, &srcMesh->mTextureCoords[0][v], sizeof(Vector2));
// Normal을 가지고 있다면
if (srcMesh->HasNormals())
memcpy(&vertex.Normal, &srcMesh->mNormals[v], sizeof(Vector3));
mesh->Vertices.push_back(vertex);
}
// face라고 함, face안에는 index가 있다.
for (UINT f = 0; f < srcMesh->mNumFaces; f++)
{
// 구조체 같은 것은 &로 받아야 복사가 안일어나고 빠르다.
aiFace& face = srcMesh->mFaces[f];
// 0, 1, 2, 2, 1, 3(정점은 4개)하면 다음 사각형 인덱스는 4번 부터 시작해야 한다.
// 그래서 startVertex만큼 더해준다.
for (UINT k = 0; k < face.mNumIndices; k++)
mesh->Indices.push_back(face.mIndices[k] + startVertex);
}
meshes.push_back(mesh);
}
}
void Converter::WriteMeshData(wstring savePath)
{
//저장할때 Byte File로 저장 : txt파일보다
//Binary 파일이 데이터를 읽는데 훨씬 더 빠르다.
//File* file; 을 사용해서 작성도 가능하다
//But Window Api로 구현해 놓은 것이 있다. BinaryFile.h에 구현해 놓았다.
// CreateFolder는 해당 경로의 폴더만 만든다.
// CreateFolders는 해당 경로까지 폴더가 없으면 차례로 만들면서 내려온다.
Path::CreateFolders(Path::GetDirectoryName(savePath));
BinaryWriter* w = new BinaryWriter();
w->Open(savePath);
// 몇 바이트 읽어야 될 지 몰라서 갯수 먼저 써놓고 시작한다.
w->UInt(bones.size());
for (asBone* bone : bones)
{
// String이 없다면 구조체 단위(Byte 단위)로 쓰는 것이 빠르다.(정점 쓸때 이렇게 씀)
w->Int(bone->Index);
// 읽기 빠르게 할때는 문자를 256으로 고정해놓고 쓴다.(걍 한번에 구조체로 읽어들인다.)
// 그러나 여기서 구현해 놓은 String() 함수는 그 문자 크기 만큼 저장한다.
w->String(bone->Name);
w->Int(bone->Parent);
w->Matrix(bone->Transform);
SafeDelete(bone);
}
w->UInt(meshes.size());
for (asMesh* meshData : meshes)
{
w->String(meshData->Name);
w->Int(meshData->BoneIndex);
w->String(meshData->MaterialName);
// vertices가 몇 바이튼지 저장해놓고 구조체로 한번에 쓴다.
w->UInt(meshData->Vertices.size());
// 1번째 인자 : 시작주소, 2번째 인자 : 크기
w->Byte(&meshData->Vertices[0], sizeof(Model::ModelVertex) * meshData->Vertices.size());
w->UInt(meshData->Indices.size());
w->Byte(&meshData->Indices[0], sizeof(UINT) * meshData->Indices.size());
SafeDelete(meshData);
}
// Close() 안하면 파일 접근 불가
w->Close();
SafeDelete(w);
}
void Converter::ExportMaterial(wstring savePath, bool bOverWrite)
{
// 임의로 .material을 주었지만, 실제 우리가 사용하는 것은 xml이다.
savePath = L"../../_Textures/" + savePath + L".material";
// 덮어씌우지 않는다면
if (bOverWrite == false)
{
// 파일이 존재 한다면 수행하지 않음
if (Path::ExistFile(savePath) == true)
return;
}
ReadMaterialData();
WriteMaterialData(savePath);
}
void Converter::ReadMaterialData()
{
for (UINT i = 0; i < scene->mNumMaterials; ++i)
{
// Scene에 메터리얼 정보가 배열로 저장되어있다.
aiMaterial* srcMaterial = scene->mMaterials[i];
asMaterial* material = new asMaterial();
material->Name = srcMaterial->GetName().C_Str();
// 색을 불러온다.
// aiColor3D : r,g,b만 있다.
aiColor3D color;
// 매터리얼은 키, 값으로 구성되어있다. 키를 이용해 각 정보를 가져온다.
// 1번째 인자 : 앰비언트 키
// 함수에 들어가보면 2개로 이루어져있는 인자 함수가 없다.
// AI_MATKEY_COLOR_AMBIENT는 3개로 구성되어있는 인자다.
// AI_MATKEY_COLOR_AMBIENT 이 키에 대한 컬러값이 color 변수에 넘어감
srcMaterial->Get(AI_MATKEY_COLOR_AMBIENT, color);
// 빛은 반 투명이 없어서 1.0f으로 고정시키고 사용
// 빛은 반 투명이 없으므로 강도로 대체해서 사용하는 경우가 많다.(어차피 a는 불투명(1.0f)이어서)
material->Ambient = Color(color.r, color.g, color.b, 1.0f);
srcMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, color);
material->Diffuse = Color(color.r, color.g, color.b, 1.0f);
srcMaterial->Get(AI_MATKEY_COLOR_SPECULAR, color);
material->Specular = Color(color.r, color.g, color.b, 1.0f);
// AI_MATKEY_SHININESS : SHININESS는 Specular의 강도이다.
// specular에 alpha값에 강도를 넣어놓는다.
srcMaterial->Get(AI_MATKEY_SHININESS, material->Specular.a);
srcMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, color);
material->Emissive = Color(color.r, color.g, color.b, 1.0f);
// 텍스쳐를 불러온다.
aiString file;
// 텍스쳐는 경로명으로 넘어온다.
// 1번째 인자(키)에 해당하는 경로명이 file 변수에 리턴된다.
srcMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &file);
material->DiffuseFile = file.C_Str();
srcMaterial->GetTexture(aiTextureType_SPECULAR, 0, &file);
material->SpecularFile = file.C_Str();
srcMaterial->GetTexture(aiTextureType_NORMALS, 0, &file);
material->NormalFile = file.C_Str();
materials.push_back(material);
}
}
void Converter::WriteMaterialData(wstring savePath)
{
// 디버깅해서 보면 각 모델의 material 정보 중 텍스쳐 경로를 보면
// 어떤거는 파일이름만 되어있고, 어떤거는 없고 다 다르다.
// 케이스가 많아서 따로 처리를 해줘야한다.
//GetFileName-확장자 포함 파일명을 가져온다.
//GetFileNameWithoutExtexsion-확장자 제외 이름만 가져온다.
string folder = String::ToString(Path::GetDirectoryName(savePath));
string file = String::ToString(Path::GetFileName(savePath));
// 폴더를 만든다.
Path::CreateFolders(folder);
Xml::XMLDocument* document = new Xml::XMLDocument();
Xml::XMLDeclaration* decl = document->NewDeclaration();
document->LinkEndChild(decl);
// XML 형식으로 쓰겠다라는 문구
Xml::XMLElement* root = document->NewElement("Materials");
root->SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
root->SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
document->LinkEndChild(root);
for (asMaterial* material : materials)
{
// 대문자 XML::은 MS에서 제공하는 XML인데 우리는 tinyXMl 사용
// Material 노드 만듬
Xml::XMLElement* node = document->NewElement("Material");
// Materials 노드의 하위 노드로 붙음
root->LinkEndChild(node);
// ex) Tank는 Material이 2개니까, 2개로 하위노드로 붙어서 나옴
Xml::XMLElement* element = nullptr;
// material 이름 저장
element = document->NewElement("Name");
// SetAttribue()을 써도 되지만, Attribute 는 <> 안에 속성으로 들어가고
// SetText()를 쓰면 <> 이 사이에 들어간다. <>
element->SetText(material->Name.c_str());
// node의 자식으로 들어감
node->LinkEndChild(element);
// 텍스쳐 정보 저장
element = document->NewElement("DiffuseFile");
// 경로가 다 다르게 나온다. 임의의 경로 텍스쳐 경로로 바꿔줌
// 이미지 파일을 저장함
// 원래 있던 경로명을 다 제거하고 파일명만 쓸 것이다.
element->SetText(WriteTexture(folder, material->DiffuseFile).c_str());
node->LinkEndChild(element);
element = document->NewElement("SpecularFile");
element->SetText(WriteTexture(folder, material->SpecularFile).c_str());
node->LinkEndChild(element);
element = document->NewElement("NormalFile");
element->SetText(WriteTexture(folder, material->NormalFile).c_str());
node->LinkEndChild(element);
// Color 저장
element = document->NewElement("Ambient");
element->SetAttribute("R", material->Ambient.r);
element->SetAttribute("G", material->Ambient.g);
element->SetAttribute("B", material->Ambient.b);
element->SetAttribute("A", material->Ambient.a);
node->LinkEndChild(element);
element = document->NewElement("Diffuse");
element->SetAttribute("R", material->Diffuse.r);
element->SetAttribute("G", material->Diffuse.g);
element->SetAttribute("B", material->Diffuse.b);
element->SetAttribute("A", material->Diffuse.a);
node->LinkEndChild(element);
element = document->NewElement("Specular");
element->SetAttribute("R", material->Specular.r);
element->SetAttribute("G", material->Specular.g);
element->SetAttribute("B", material->Specular.b);
element->SetAttribute("A", material->Specular.a);
node->LinkEndChild(element);
element = document->NewElement("Emissive");
element->SetAttribute("R", material->Emissive.r);
element->SetAttribute("G", material->Emissive.g);
element->SetAttribute("B", material->Emissive.b);
element->SetAttribute("A", material->Emissive.a);
node->LinkEndChild(element);
SafeDelete(material);
}
// 정보들을 Xml에 담는다.
document->SaveFile((folder + file).c_str());
SafeDelete(document);
}
string Converter::WriteTexture(string saveFolder, string file)
{
// 이미지 파일을 생성을 하든, 복사를 하든 할 것임
// case 1)
// 파일명이 들어오지 않는다면 임의의 파일을 써줘야 한다.
// 이 경우는 실제 Material 파일을 열어서 수정해야 하는 부분이다.
// Tank가 그렇다.
if (file.length() < 0) return "";
string fileName = Path::GetFileName(file);
// GetEmbeddedTexture() : 이 경로 명에 내장 텍스쳐가 있다면 내장 텍스쳐를 리턴받고
// 없다면 null을 리턴함
// fbx 파일에는 텍스쳐 파일이나, 다른 기타 파일들이 모두 합쳐져서 압축되어 들어가 있다.
const aiTexture* texture = scene->GetEmbeddedTexture(file.c_str());
// case 2) 내장 텍스쳐가 있다면
// 내장 텍스쳐가 있다는 얘기(kachujin(.fbx)은 내장 텍스쳐가 있다.)
string path = "";
if (texture != nullptr)
{
// 배열의 이미지는 x,y축을 가지고 있다.
// 저장하려는 경로 + 파일명
path = saveFolder + fileName;
// case 2-1) 배열의 이미지가 y축은 없고(0인경우, 높이가 없다.) x축만 가지고 있는경우
// ex) kachujin
if (texture->mHeight < 1)
{
// 텍스쳐의 높이가 0인경우
// - 데이터가 한줄로 바이트 파일(png) 형식으로 써져 있다.
// 즉, 그대로 저장하면 이미지 파일이 된다.
// 텍스쳐 크기만큼 써버린다.
BinaryWriter w;
w.Open(String::ToWString(path));
// pcData : 이미지 파일 순서대로, 바이트 대로 저장되어 있다.
// pcData는 tga면 tga 순서대로, png면 png순서대로 저장되어있다. 그대로 쓰면됨
// Byte 파일로 저장
w.Byte(texture->pcData, texture->mWidth);
w.Close();
}
// case 2-2) x,y축을 가지고 있는 경우
// 높이가 있어서 텍스쳐 파일을 만들어서 씀
// 현재까지 이런 파일을 보지는 못했지만
// 이론상 있는 파일이다.
else
{
D3D11_TEXTURE2D_DESC destDesc;
ZeroMemory(&destDesc, sizeof(D3D11_TEXTURE2D_DESC));
destDesc.Width = texture->mWidth;
destDesc.Height = texture->mHeight;
destDesc.MipLevels = 1;
destDesc.ArraySize = 1;
destDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
destDesc.SampleDesc.Count = 1;
destDesc.SampleDesc.Quality = 0;
destDesc.Usage = D3D11_USAGE_IMMUTABLE;
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = texture->pcData;
ID3D11Texture2D* dest;
HRESULT hr;
hr = D3D::GetDevice()->CreateTexture2D(&destDesc, &subResource, &dest);
assert(SUCCEEDED(hr));
D3DX11SaveTextureToFileA(D3D::GetDC(), dest, D3DX11_IFF_PNG, saveFolder.c_str());
}
}
// case 3) 내장 텍스쳐가 없다면(파일이 따로 있다.)
// B787은 fbx지만 따로 texture 파일이 존재
// 모델 폴더에 파일이 있다면 파일을 복사해주고 경로를 맞도록 변경
// 아니라면 빈 문자열을 리턴함
else
{
string directory = Path::GetDirectoryName(String::ToString(this->file));
string origin = directory + file;
// /로 바꿔줌
String::Replace(&origin, "\\", "/");
// 파일이 없다면 빈문자열 리턴
if (Path::ExistFile(origin) == false)
return "";
// 있다면 그 경로에서 파일을 copy
path = saveFolder + fileName;
// 3번째 인자는 FALSE 옵션은 파일이 있어도 덮어씌우는 옵션
// save할 경로로 파일을 복사
CopyFileA(origin.c_str(), path.c_str(), FALSE);
// _Textures 문자열 제거, 경로만 쓰면 우리는 자동으로 받도록 처리해서
String::Replace(&path, "../../_Textures/", "");
}
// 어차피 saveFolder경로가 texture 경로, texture 경로에 있는 파일을 부를 거라서
return Path::GetFileName(path);
}
Types.h
더보기
#pragma once
#include "stdafx.h"
//Mesh와 Bone은 1대1 관계, mesh가 없는 본도 있습니다.(ex)HP바 같은 경우 가상본만 있으면 된다., 그릴 정보가 없어서)
// Bone 정보를 저장
// 부모 자식 관계르 저장
struct asBone
{
int Index;
string Name;
// Parent(Index)가 -1이라면 루트
int Parent; // Bone의 부모(포탑의 부모 등...)
Matrix Transform; // Bone의 행렬
};
// Mesh 정보를 저장
struct asMesh
{
// Bone의 Name과 동일
string Name;
int BoneIndex;
aiMesh* Mesh;
// 참조할 재질의 이름
string MaterialName;
// 정점형태
vector<Model::ModelVertex> Vertices;
// 인덱스
vector<UINT> Indices;
};
struct asMaterial
{
// Mesh가 가지고 있는 MaterialName과 이름이 동일하게 세팅함
string Name;
// Color 정보
Color Ambient;
Color Diffuse;
Color Specular;
Color Emissive;
// 우리가 실제로 보여질 텍스쳐 파일
// 텍스쳐 파일 경로
string DiffuseFile;
string SpecularFile;
string NormalFile;
// 나머지 조명배울때 쓸거라서 나중에 설명
};
Model.h
더보기
#pragma once
#define MAX_MODEL_TRANSFORMS 250
// Model을 부르는 코드
class ModelBone;
class ModelMesh;
class Model
{
public:
// 원래는 모델 부를때 VertexTextureNormal까지 하면 되는데
// 애니메이션할때 또 바꿔줘야되고 이래서.. 처음부터
// VertexTextureNormalTangentBlend으로 한다.
typedef VertexTextureNormalTangentBlend ModelVertex;
public:
Model();
~Model();
UINT BoneCount() { return bones.size(); }
vector<ModelBone*>& Bones() { return bones; }
// 본의 번호로 얻기
ModelBone* BoneByIndex(UINT index) { return bones[index]; }
// 본의 이름으로 얻기
ModelBone* BoneByName(wstring name);
UINT MeshCount() { return meshes.size(); }
vector<ModelMesh*>& Meshes() { return meshes; }
ModelMesh* MeshByIndex(UINT index) { return meshes[index]; }
ModelMesh* MeshByName(wstring name);
UINT MaterialCount() { return materials.size(); }
vector<Material*>& Materials() { return materials; }
Material* MaterialByIndex(UINT index) { return materials[index]; }
Material* MaterialByName(wstring name);
public:
void ReadMesh(wstring file);
void ReadMaterial(wstring file);
private:
void BindBone();
void BindMesh();
private:
ModelBone* root;
vector<ModelBone*> bones;
vector<ModelMesh*> meshes;
vector<Material*> materials;
};
Model.Cpp
더보기
#include "Framework.h"
#include "Model.h"
#include "Utilities/BinaryFile.h"
#include "Utilities/Xml.h"
Model::Model()
{
}
Model::~Model()
{
for (ModelBone* bone : bones)
SafeDelete(bone);
for (ModelMesh* mesh : meshes)
SafeDelete(mesh);
for (Material* material : materials)
SafeDelete(material);
}
ModelBone* Model::BoneByName(wstring name)
{
for (ModelBone* bone : bones)
{
if (bone->Name() == name)
return bone;
}
return NULL;
}
ModelMesh* Model::MeshByName(wstring name)
{
for (ModelMesh* mesh : meshes)
{
if (mesh->Name() == name)
return mesh;
}
return NULL;
}
Material* Model::MaterialByName(wstring name)
{
for (Material* material : materials)
{
if (material->Name() == name)
return material;
}
return NULL;
}
void Model::ReadMesh(wstring file)
{
file = L"../../_Models/" + file + L".mesh";
// 크기를 맞춰서 읽는다.(바이너리 파일이니까)
BinaryReader* r = new BinaryReader();
r->Open(file);
UINT count = 0;
count = r->UInt();
for (UINT i = 0; i < count; i++)
{
ModelBone* bone = new ModelBone();
bone->index = r->Int();
bone->name = String::ToWString(r->String());
bone->parentIndex = r->Int();
bone->transform = r->Matrix();
bones.push_back(bone);
}
count = r->UInt();
for (UINT i = 0; i < count; i++)
{
ModelMesh* mesh = new ModelMesh();
mesh->name = String::ToWString(r->String());
mesh->boneIndex = r->Int();
mesh->materialName = String::ToWString(r->String());
//VertexData
{
UINT count = r->UInt();
vector<Model::ModelVertex> vertices;
// assign : 미리 데이터의 크기를 잡아놓는다.
vertices.assign(count, Model::ModelVertex());
void* ptr = (void*)&(vertices[0]);
r->Byte(&ptr, sizeof(Model::ModelVertex) * count);
mesh->vertices = new Model::ModelVertex[count];
mesh->vertexCount = count;
copy
(
vertices.begin(), vertices.end(),
stdext::checked_array_iterator<Model::ModelVertex*>(mesh->vertices, count)
);
}
//IndexData
{
UINT count = r->UInt();
vector<UINT> indices;
indices.assign(count, UINT());
void* ptr = (void*)&(indices[0]);
r->Byte(&ptr, sizeof(UINT) * count);
mesh->indices = new UINT[count];
mesh->indexCount = count;
copy
(
indices.begin(), indices.end(),
stdext::checked_array_iterator<UINT*>(mesh->indices, count)
);
}
meshes.push_back(mesh);
}
r->Close();
SafeDelete(r);
BindBone();
}
void Model::BindBone()
{
// 0번이 루트
root = bones[0];
for (ModelBone* bone : bones)
{
// parentIndex가 -1이면 Root이다.
if (bone->parentIndex > -1)
{
bone->parent = bones[bone->parentIndex];
// 자식으로 추가해줌
bone->parent->childs.push_back(bone);
}
else
{
bone->parent = nullptr;
}
}
}
void Model::BindMesh()
{
for (ModelMesh* mesh : meshes)
{
// 배열에 있던거 가져다 연결해줌
mesh->bone = bones[mesh->boneIndex];
mesh->Binding(this);
}
}
void Model::ReadMaterial(wstring file)
{
file = L"../../_Textures/" + file + L".material";
Xml::XMLDocument* document = new Xml::XMLDocument();
Xml::XMLError error = document->LoadFile(String::ToString(file).c_str());
assert(error == Xml::XML_SUCCESS);
Xml::XMLElement* root = document->FirstChildElement();
Xml::XMLElement* materialNode = root->FirstChildElement();
do
{
Material* material = new Material();
Xml::XMLElement* node = NULL;
node = materialNode->FirstChildElement();
material->Name(String::ToWString(node->GetText()));
wstring directory = Path::GetDirectoryName(file);
String::Replace(&directory, L"../../_Textures", L"");
wstring texture = L"";
node = node->NextSiblingElement();
texture = String::ToWString(node->GetText());
if (texture.length() > 0)
material->DiffuseMap(directory + texture);
node = node->NextSiblingElement();
texture = String::ToWString(node->GetText());
if (texture.length() > 0)
material->SpecularMap(directory + texture);
node = node->NextSiblingElement();
texture = String::ToWString(node->GetText());
if (texture.length() > 0)
material->NormalMap(directory + texture);
Color color;
node = node->NextSiblingElement();
color.r = node->FloatAttribute("R");
color.g = node->FloatAttribute("G");
color.b = node->FloatAttribute("B");
color.a = node->FloatAttribute("A");
material->Ambient(color);
node = node->NextSiblingElement();
color.r = node->FloatAttribute("R");
color.g = node->FloatAttribute("G");
color.b = node->FloatAttribute("B");
color.a = node->FloatAttribute("A");
material->Diffuse(color);
node = node->NextSiblingElement();
color.r = node->FloatAttribute("R");
color.g = node->FloatAttribute("G");
color.b = node->FloatAttribute("B");
color.a = node->FloatAttribute("A");
material->Specular(color);
node = node->NextSiblingElement();
color.r = node->FloatAttribute("R");
color.g = node->FloatAttribute("G");
color.b = node->FloatAttribute("B");
color.a = node->FloatAttribute("A");
material->Emissive(color);
materials.push_back(material);
materialNode = materialNode->NextSiblingElement();
} while (materialNode != NULL);
BindMesh();
}
ModelMesh.h
더보기
#pragma once
// 이 안에다 Bone과 Mesh를 같이 넣어놓는다.
class ModelBone
{
public:
friend class Model;
private:
// Model에서만 생성할 수 있도록
ModelBone();
~ModelBone();
public:
int Index() { return index; }
int ParentIndex() { return parentIndex; }
ModelBone* Parent() { return parent; }
wstring Name() { return name; }
Matrix& Transform() { return transform; }
void Transform(Matrix& matrix) { transform = matrix; }
vector<ModelBone*>& Childs() { return childs; }
private:
int index;
wstring name;
int parentIndex;
ModelBone* parent;
Matrix transform;
// Model에서 Binding을 통해 세팅해줄것임
vector<ModelBone*> childs;
};
///////////////////////////////////////////////////////////////////////
// Renderer로 부터 상속받아서 해도 되지만, Transform 콜 소지가 있어서
// 그냥 작성한다.
class ModelMesh
{
public:
friend class Model;
private:
// Model에서만 생성할 수 있도록
ModelMesh();
~ModelMesh();
// Material 때문에 받는다.
void Binding(Model* model);
public:
void Pass(UINT val) { pass = val; }
void SetShader(Shader* shader);
void Update();
void Render();
wstring Name() { return name; }
int BoneIndex() { return boneIndex; }
class ModelBone* Bone() { return bone; }
// Matrix Transforms[MAX_MODEL_TRANSFORMS]를 복사해 주기 위한 함수
void Transforms(Matrix* transforms);
// Transform(Model의 위치를 움직일), world를 넣어줌
void SetTransform(Transform* transform);
private:
// Bone 정보를 넘길 것이다.
struct BoneDesc
{
// why 배열로 넘기나? 하나만 넘겨도 된다. index도 필요없다.
// 그러나 이후에 애니메이션 할때 필요하기 때문이다.
// 전체 본을 배열로 만든다음에 배열을 넘긴다는 개념
// 본의 전체개수는 250개로 설정한다.
// 사람의 관절이 250개가 넘지 않기 때문
// 본 전체의 행렬 배열과
Matrix Transforms[MAX_MODEL_TRANSFORMS];
// 그 배열에서 현재 그려질 매쉬가 참조할 본의 번호가 같이
// 넘어간다.
// Bone배열에서 Index값으로 Bone을 선택한다.
UINT Index;
float Padding[3];
} boneDesc;
private:
wstring name;
Shader* shader;
UINT pass = 0;
Transform* transform = nullptr;
PerFrame* perFrame = nullptr;
wstring materialName = L"";
Material* material;
int boneIndex;
class ModelBone* bone;
VertexBuffer* vertexBuffer;
UINT vertexCount;
Model::ModelVertex* vertices;
IndexBuffer* indexBuffer;
UINT indexCount;
UINT* indices;
ConstantBuffer* boneBuffer;
ID3DX11EffectConstantBuffer* sBoneBuffer;
};
ModelMesh.Cpp
더보기
#include "Framework.h"
#include "ModelMesh.h"
ModelBone::ModelBone()
{
}
ModelBone::~ModelBone()
{
}
///////////////////////////////////////////////////////////////////////
ModelMesh::ModelMesh()
{
boneBuffer = new ConstantBuffer(&boneDesc, sizeof(BoneDesc));
}
ModelMesh::~ModelMesh()
{
SafeDelete(transform);
SafeDelete(perFrame);
SafeDelete(material);
SafeDeleteArray(vertices);
SafeDeleteArray(indices);
SafeDelete(vertexBuffer);
SafeDelete(indexBuffer);
SafeDelete(boneBuffer);
}
void ModelMesh::Binding(Model* model)
{
// VertexBuffer, IndexBuffer 만드는 것 세팅
vertexBuffer = new VertexBuffer(vertices, vertexCount, sizeof(Model::ModelVertex));
indexBuffer = new IndexBuffer(indices, indexCount);
Material* srcMaterial = model->MaterialByName(materialName);
material = new Material();
material->CopyFrom(srcMaterial);
}
void ModelMesh::SetShader(Shader* shader)
{
// 모델을 렌더링하는 Shader가 바뀌면 그거에 따른거 처리
this->shader = shader;
// Perframe, Transform은 Shader를 세팅해서 해당 Shader에 값을 미뤄주도록 처리되어있다.
// 그래서 새로 세팅
SafeDelete(transform);
transform = new Transform(shader);
SafeDelete(perFrame);
perFrame = new PerFrame(shader);
sBoneBuffer = shader->AsConstantBuffer("CB_Bone");
material->SetShader(shader);
}
void ModelMesh::Update()
{
boneDesc.Index = boneIndex;
// 본 배열에서 하나 선택한 것이랑 월드랑 결합해서 최종위치를 구해낼것이다.
perFrame->Update();
transform->Update();
}
void ModelMesh::Render()
{
boneBuffer->Render();
sBoneBuffer->SetConstantBuffer(boneBuffer->Buffer());
perFrame->Render();
transform->Render();
material->Render();
vertexBuffer->Render();
indexBuffer->Render();
// 우리가 aiProcess로 삼각형 단위로 해달라고 해서, 이렇게 하면된다.
D3D::GetDC()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
shader->DrawIndexed(0, pass, indexCount);
}
void ModelMesh::Transforms(Matrix* transforms)
{
memcpy(boneDesc.Transforms, transforms, sizeof(Matrix) * MAX_MODEL_TRANSFORMS);
}
void ModelMesh::SetTransform(Transform* transform)
{
// world를 넣어줌
this->transform->Set(transform);
}
ModelRender.h
더보기
#pragma once
// Model 렌더링 코드가 들어감
class ModelRender
{
public:
ModelRender(Shader* shader);
~ModelRender();
void Update();
void Render();
public:
void ReadMesh(wstring file);
void ReadMaterial(wstring file);
// 모델의 위치
Transform* GetTransform() { return transform; }
Model* GetModel() { return model; }
// 메쉬들을 돌아가면서 Pass를 일괄적으로 설정한다.
void Pass(UINT pass);
// Transform 업데이트, 2번째 인자 : bone 행렬 전체
void UpdateTransform(ModelBone* bone = nullptr, Matrix& matrix = Matrix());
private:
// 본을 회전하기 위해 만든 함수
void UpdateBones(ModelBone* bone, Matrix& matrix);
private:
// 데이터를 읽었느냐
bool bRead = false;
Shader* shader;
// Model Wrapper class
Model* model;
Transform* transform;
Matrix transforms[MAX_MODEL_TRANSFORMS];
};
ModelRender.Cpp
더보기
#include "Framework.h"
#include "ModelRender.h"
ModelRender::ModelRender(Shader* shader)
: shader(shader)
{
model = new Model();
transform = new Transform(shader);
}
ModelRender::~ModelRender()
{
SafeDelete(model);
SafeDelete(transform);
}
void ModelRender::Update()
{
if (bRead == true)
{
bRead = false;
for (ModelMesh* mesh : model->Meshes())
mesh->SetShader(shader);
UpdateTransform();
}
for (ModelMesh* mesh : model->Meshes())
mesh->Update();
}
void ModelRender::Render()
{
// 렌더링의 기준은 Model Mesh이며, 모델이 그려질 transform을 세팅해준다.
for (ModelMesh* mesh : model->Meshes())
{
// 메쉬가 어느 위치에서 그려질지
mesh->SetTransform(transform);
mesh->Render();
}
}
void ModelRender::ReadMesh(wstring file)
{
model->ReadMesh(file);
}
void ModelRender::ReadMaterial(wstring file)
{
model->ReadMaterial(file);
bRead = true;
}
void ModelRender::Pass(UINT pass)
{
for (ModelMesh* mesh : model->Meshes())
mesh->Pass(pass);
}
void ModelRender::UpdateTransform(ModelBone* bone, Matrix& matrix)
{
if (bone != nullptr)
UpdateBones(bone, matrix);
for (UINT i = 0; i < model->BoneCount(); i++)
{
// 해당인덱스의 본을 가져옴
ModelBone* bone = model->BoneByIndex(i);
// Bone에 있는 Transform들이 전부 복사됨
transforms[i] = bone->Transform();
}
// ModelMesh에 Transforms 넘겨줌, ModelMesh가 boneBuffer를 Cbuffer로 넘겨주기 때문
for (ModelMesh* mesh : model->Meshes())
mesh->Transforms(transforms);
}
void ModelRender::UpdateBones(ModelBone* bone, Matrix& matrix)
{
// 본을 업데이트, 이동, 회전 등...
Matrix temp = bone->Transform();
// 원래 본을 기준으로 matrix 만큼 이동
bone->Transform(temp * matrix);
// 자식들도 이동
for (ModelBone* child : bone->Childs())
UpdateBones(child, matrix);
}
ModelDemo.h
더보기
#pragma once
#include "Systems/IExecute.h"
class ModelDemo : public IExecute
{
public:
virtual void Initialize() override;
virtual void Ready() override {};
virtual void Destroy() override {};
virtual void Update() override;
virtual void PreRender() override {};
virtual void Render() override;
virtual void PostRender() override {};
virtual void ResizeScreen() override {};
private:
void AirPlane();
void Tower();
// 탱크 모델을 불러들임
void Tank();
void Kachujin();
private:
Shader* shader;
ModelRender* airPlane = nullptr;
ModelRender* tower = nullptr;
ModelRender* tank = nullptr;
ModelRender* kachujin = nullptr;
CubeSky* sky;
Vector3 direction = Vector3(-1, -1, +1);
Shader* gridShader;
MeshGrid* grid;
};
ModelDemo.Cpp
더보기
#include "stdafx.h"
#include "ModelDemo.h"
#include "Converter.h"
void ModelDemo::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
Context::Get()->GetCamera()->Position(1, 36, -85);
shader = new Shader(L"38_Model.fx");
AirPlane();
Tower();
Tank();
Kachujin();
sky = new CubeSky(L"Environment/GrassCube1024.dds");
gridShader = new Shader(L"25_Mesh.fx");
grid = new MeshGrid(gridShader, 6, 6);
grid->GetTransform()->Scale(12, 1, 12);
grid->DiffuseMap(L"Floor.png");
}
void ModelDemo::Update()
{
sky->Update();
grid->Update();
if (airPlane != nullptr) airPlane->Update();
if (tower != nullptr) tower->Update();
if (tank != nullptr)
{
// 디버깅 모드로 보면 10번 본은 포탑이다.
ModelBone* bone = tank->GetModel()->BoneByIndex(10);
// 회전 시키기 위해
Transform transform;
// 180 만큼 돌기 위해 PI 곱해줌
float rotation = sinf(Time::Get()->Running() + 100) * Math::PI * Time::Delta();
transform.Rotation(0, rotation, 0);
tank->UpdateTransform(bone, transform.World());
tank->Update();
}
if (kachujin != nullptr) kachujin->Update();
}
void ModelDemo::Render()
{
ImGui::SliderFloat3("Direction", direction, -1, +1);
shader->AsVector("Direction")->SetFloatVector(direction);
gridShader->AsVector("Direction")->SetFloatVector(direction);
static int pass = 0;
ImGui::InputInt("Pass", &pass);
pass %= 2;
sky->Render();
grid->Render();
if (airPlane != nullptr)
{
airPlane->Pass(pass);
airPlane->Render();
}
if (tower != nullptr)
{
tower->Pass(pass);
tower->Render();
}
if (tank != nullptr)
{
tank->Pass(pass);
tank->Render();
}
if (kachujin != nullptr)
{
kachujin->Pass(pass);
kachujin->Render();
}
}
void ModelDemo::AirPlane()
{
airPlane = new ModelRender(shader);
airPlane->ReadMesh(L"B787/Airplane");
airPlane->ReadMaterial(L"B787/Airplane");
airPlane->GetTransform()->Scale(0.005f, 0.005f, 0.005f);
}
void ModelDemo::Tower()
{
tower = new ModelRender(shader);
tower->ReadMesh(L"Tower/Tower");
tower->ReadMaterial(L"Tower/Tower");
tower->GetTransform()->Position(-20, 0, 0);
tower->GetTransform()->Scale(0.01f, 0.01f, 0.01f);
}
void ModelDemo::Tank()
{
tank = new ModelRender(shader);
tank->ReadMesh(L"Tank/Tank");
tank->ReadMaterial(L"Tank/Tank");
tank->GetTransform()->Position(20, 0, 0);
}
void ModelDemo::Kachujin()
{
kachujin = new ModelRender(shader);
kachujin->ReadMesh(L"Kachujin/Mesh");
kachujin->ReadMaterial(L"Kachujin/Mesh");
kachujin->GetTransform()->Position(0, 0, -30);
kachujin->GetTransform()->Scale(0.025f, 0.025f, 0.025f);
}
.fx
더보기
#include "00_Global.fx"
float3 Direction = float3(-1, -1, 1);
struct VertexModel
{
float4 Position : Position;
float2 Uv : Uv;
float3 Normal : Normal;
float3 Tangent : Tangent;
float4 BlendIndices : BlendIndices;
float4 BlendWeights : BlendWeights;
};
#define MAX_MODEL_TRANSFORMS 250
cbuffer CB_Bone
{
matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
uint BoneIndex;
// CBuffer 전체가 배열이 되지 않는한, padding은 안 넣어줘도 된다.
};
struct VertexOutput
{
// SV_Position -> position0번으로 취급됨
float4 Position : SV_Position;
float3 Normal : Normal;
float2 Uv : Uv;
};
VertexOutput VS(VertexTextureNormal input)
{
VertexOutput output;
World = mul(BoneTransforms[BoneIndex], World);
// ModelMesh의 Transform이 넘어오나, Bone의 위치로 옮긴 다음, World로 옮겨야함
output.Position = WorldPosition(input.Position);
output.Position = ViewProjection(output.Position);
output.Normal = WorldNormal(input.Normal);
output.Uv = input.Uv;
return output;
}
float4 PS(VertexOutput input) : SV_Target
{
// 음영 넣기 위해
float NdotL = dot(normalize(input.Normal), -Direction);
return DiffuseMap.Sample(LinearSampler, input.Uv) * NdotL;
}
technique11 T0
{
P_VP(P0, VS, PS)
P_RS_VP(P1, FillMode_WireFrame, VS, PS)
}
결과
'DirectX11 3D > 기본 문법' 카테고리의 다른 글
<Directx11 3D> 42 - Animation Clip Read & Write (0) | 2022.02.16 |
---|---|
<DirectX11 3D> 41 - Animation (Mesh Skinning) (0) | 2022.02.15 |
<DirectX11 3D> 35 - Bone, Mesh Data Read + Write File (0) | 2022.02.10 |
<DirectX11 3D> 34. ModelEditor - Converter(Assimp) (0) | 2022.02.07 |
<DirectX11 3D> 32. Renderer (0) | 2022.02.07 |