DirectX12
[DirectX12] 26) Mesh / Animation
GiveZero
2023. 1. 22. 00:03
FBXLoader.h
더보기
#pragma once
struct FbxMaterialInfo
{
Vec4 diffuse;
Vec4 ambient;
Vec4 specular;
wstring name;
wstring diffuseTexName;
wstring normalTexName;
wstring specularTexName;
};
struct BoneWeight
{
using Pair = pair<int32, double>;
vector<Pair> boneWeights;
void AddWeights(uint32 index, double weight)
{
if (weight <= 0.f)
return;
auto findIt = std::find_if(boneWeights.begin(), boneWeights.end(),
[=](const Pair& p) { return p.second < weight; });
if (findIt != boneWeights.end())
boneWeights.insert(findIt, Pair(index, weight));
else
boneWeights.push_back(Pair(index, weight));
// 가중치는 최대 4개
if (boneWeights.size() > 4)
boneWeights.pop_back();
}
void Normalize()
{
double sum = 0.f;
std::for_each(boneWeights.begin(), boneWeights.end(), [&](Pair& p) { sum += p.second; });
std::for_each(boneWeights.begin(), boneWeights.end(), [=](Pair& p) { p.second = p.second / sum; });
}
};
struct FbxMeshInfo
{
wstring name;
vector<Vertex> vertices;
vector<vector<uint32>> indices;
vector<FbxMaterialInfo> materials;
vector<BoneWeight> boneWeights; // 뼈 가중치
bool hasAnimation;
};
struct FbxKeyFrameInfo
{
FbxAMatrix matTransform;
double time;
};
struct FbxBoneInfo
{
wstring boneName;
int32 parentIndex;
FbxAMatrix matOffset;
};
struct FbxAnimClipInfo
{
wstring name;
FbxTime startTime;
FbxTime endTime;
FbxTime::EMode mode;
vector<vector<FbxKeyFrameInfo>> keyFrames;
};
class FBXLoader
{
public:
FBXLoader();
~FBXLoader();
public:
void LoadFbx(const wstring& path);
public:
int32 GetMeshCount() { return static_cast<int32>(_meshes.size()); }
const FbxMeshInfo& GetMesh(int32 idx) { return _meshes[idx]; }
vector<shared_ptr<FbxBoneInfo>>& GetBones() { return _bones; }
vector<shared_ptr<FbxAnimClipInfo>>& GetAnimClip() { return _animClips; }
private:
void Import(const wstring& path);
void ParseNode(FbxNode* root);
void LoadMesh(FbxMesh* mesh);
void LoadMaterial(FbxSurfaceMaterial* surfaceMaterial);
void GetNormal(FbxMesh* mesh, FbxMeshInfo* container, int32 idx, int32 vertexCounter);
void GetTangent(FbxMesh* mesh, FbxMeshInfo* container, int32 idx, int32 vertexCounter);
void GetUV(FbxMesh* mesh, FbxMeshInfo* container, int32 idx, int32 vertexCounter);
Vec4 GetMaterialData(FbxSurfaceMaterial* surface, const char* materialName, const char* factorName);
wstring GetTextureRelativeName(FbxSurfaceMaterial* surface, const char* materialProperty);
void CreateTextures();
void CreateMaterials();
// Animation
void LoadBones(FbxNode* node) { LoadBones(node, 0, -1); }
void LoadBones(FbxNode* node, int32 idx, int32 parentIdx);
void LoadAnimationInfo();
void LoadAnimationData(FbxMesh* mesh, FbxMeshInfo* meshInfo);
void LoadBoneWeight(FbxCluster* cluster, int32 boneIdx, FbxMeshInfo* meshInfo);
void LoadOffsetMatrix(FbxCluster* cluster, const FbxAMatrix& matNodeTransform, int32 boneIdx, FbxMeshInfo* meshInfo);
void LoadKeyframe(int32 animIndex, FbxNode* node, FbxCluster* cluster, const FbxAMatrix& matNodeTransform, int32 boneIdx, FbxMeshInfo* container);
int32 FindBoneIndex(string name);
FbxAMatrix GetTransform(FbxNode* node);
void FillBoneWeight(FbxMesh* mesh, FbxMeshInfo* meshInfo);
private:
FbxManager* _manager = nullptr;
FbxScene* _scene = nullptr;
FbxImporter* _importer = nullptr;
wstring _resourceDirectory;
vector<FbxMeshInfo> _meshes;
vector<shared_ptr<FbxBoneInfo>> _bones;
vector<shared_ptr<FbxAnimClipInfo>> _animClips;
FbxArray<FbxString*> _animNames;
};
FBXLoader.cpp
더보기
#include "pch.h"
#include "FBXLoader.h"
#include "Mesh.h"
#include "Resources.h"
#include "Shader.h"
#include "Material.h"
FBXLoader::FBXLoader()
{
}
FBXLoader::~FBXLoader()
{
if (_scene)
_scene->Destroy();
if (_manager)
_manager->Destroy();
}
void FBXLoader::LoadFbx(const wstring& path)
{
// 파일 데이터 로드
Import(path);
// Animation
LoadBones(_scene->GetRootNode());
LoadAnimationInfo();
// 로드된 데이터 파싱 (Mesh/Material/Skin)
ParseNode(_scene->GetRootNode());
// 우리 구조에 맞게 Texture / Material 생성
CreateTextures();
CreateMaterials();
}
void FBXLoader::Import(const wstring& path)
{
// FBX SDK 관리자 객체 생성
_manager = FbxManager::Create();
// IOSettings 객체 생성 및 설정
FbxIOSettings* settings = FbxIOSettings::Create(_manager, IOSROOT);
_manager->SetIOSettings(settings);
// FbxImporter 객체 생성
_scene = FbxScene::Create(_manager, "");
// 나중에 Texture 경로 계산할 때 쓸 것
_resourceDirectory = fs::path(path).parent_path().wstring() + L"\\" + fs::path(path).filename().stem().wstring() + L".fbm";
_importer = FbxImporter::Create(_manager, "");
string strPath = ws2s(path);
_importer->Initialize(strPath.c_str(), -1, _manager->GetIOSettings());
_importer->Import(_scene);
_scene->GetGlobalSettings().SetAxisSystem(FbxAxisSystem::DirectX);
// 씬 내에서 삼각형화 할 수 있는 모든 노드를 삼각형화 시킨다.
FbxGeometryConverter geometryConverter(_manager);
geometryConverter.Triangulate(_scene, true);
_importer->Destroy();
}
void FBXLoader::ParseNode(FbxNode* node)
{
FbxNodeAttribute* attribute = node->GetNodeAttribute();
if (attribute)
{
switch (attribute->GetAttributeType())
{
case FbxNodeAttribute::eMesh:
LoadMesh(node->GetMesh());
break;
}
}
// Material 로드
const uint32 materialCount = node->GetMaterialCount();
for (uint32 i = 0; i < materialCount; ++i)
{
FbxSurfaceMaterial* surfaceMaterial = node->GetMaterial(i);
LoadMaterial(surfaceMaterial);
}
// Tree 구조 재귀 호출
const int32 childCount = node->GetChildCount();
for (int32 i = 0; i < childCount; ++i)
ParseNode(node->GetChild(i));
}
void FBXLoader::LoadMesh(FbxMesh* mesh)
{
_meshes.push_back(FbxMeshInfo());
FbxMeshInfo& meshInfo = _meshes.back();
meshInfo.name = s2ws(mesh->GetName());
const int32 vertexCount = mesh->GetControlPointsCount();
meshInfo.vertices.resize(vertexCount);
meshInfo.boneWeights.resize(vertexCount);
// Position
FbxVector4* controlPoints = mesh->GetControlPoints();
for (int32 i = 0; i < vertexCount; ++i)
{
meshInfo.vertices[i].pos.x = static_cast<float>(controlPoints[i].mData[0]);
meshInfo.vertices[i].pos.y = static_cast<float>(controlPoints[i].mData[2]);
meshInfo.vertices[i].pos.z = static_cast<float>(controlPoints[i].mData[1]);
}
const int32 materialCount = mesh->GetNode()->GetMaterialCount();
meshInfo.indices.resize(materialCount);
FbxGeometryElementMaterial* geometryElementMaterial = mesh->GetElementMaterial();
const int32 polygonSize = mesh->GetPolygonSize(0);
assert(polygonSize == 3);
uint32 arrIdx[3];
uint32 vertexCounter = 0; // 정점의 개수
const int32 triCount = mesh->GetPolygonCount(); // 메쉬의 삼각형 개수를 가져온다
for (int32 i = 0; i < triCount; i++) // 삼각형의 개수
{
for (int32 j = 0; j < 3; j++) // 삼각형은 세 개의 정점으로 구성
{
int32 controlPointIndex = mesh->GetPolygonVertex(i, j); // 제어점의 인덱스 추출
arrIdx[j] = controlPointIndex;
GetNormal(mesh, &meshInfo, controlPointIndex, vertexCounter);
GetTangent(mesh, &meshInfo, controlPointIndex, vertexCounter);
GetUV(mesh, &meshInfo, controlPointIndex, mesh->GetTextureUVIndex(i, j));
vertexCounter++;
}
const uint32 subsetIdx = geometryElementMaterial->GetIndexArray().GetAt(i);
meshInfo.indices[subsetIdx].push_back(arrIdx[0]);
meshInfo.indices[subsetIdx].push_back(arrIdx[2]);
meshInfo.indices[subsetIdx].push_back(arrIdx[1]);
}
// Animation
LoadAnimationData(mesh, &meshInfo);
}
void FBXLoader::LoadMaterial(FbxSurfaceMaterial* surfaceMaterial)
{
FbxMaterialInfo material{};
material.name = s2ws(surfaceMaterial->GetName());
material.diffuse = GetMaterialData(surfaceMaterial, FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
material.ambient = GetMaterialData(surfaceMaterial, FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
material.specular = GetMaterialData(surfaceMaterial, FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
material.diffuseTexName = GetTextureRelativeName(surfaceMaterial, FbxSurfaceMaterial::sDiffuse);
material.normalTexName = GetTextureRelativeName(surfaceMaterial, FbxSurfaceMaterial::sNormalMap);
material.specularTexName = GetTextureRelativeName(surfaceMaterial, FbxSurfaceMaterial::sSpecular);
_meshes.back().materials.push_back(material);
}
void FBXLoader::GetNormal(FbxMesh* mesh, FbxMeshInfo* container, int32 idx, int32 vertexCounter)
{
if (mesh->GetElementNormalCount() == 0)
return;
FbxGeometryElementNormal* normal = mesh->GetElementNormal();
uint32 normalIdx = 0;
if (normal->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
{
if (normal->GetReferenceMode() == FbxGeometryElement::eDirect)
normalIdx = vertexCounter;
else
normalIdx = normal->GetIndexArray().GetAt(vertexCounter);
}
else if (normal->GetMappingMode() == FbxGeometryElement::eByControlPoint)
{
if (normal->GetReferenceMode() == FbxGeometryElement::eDirect)
normalIdx = idx;
else
normalIdx = normal->GetIndexArray().GetAt(idx);
}
FbxVector4 vec = normal->GetDirectArray().GetAt(normalIdx);
container->vertices[idx].normal.x = static_cast<float>(vec.mData[0]);
container->vertices[idx].normal.y = static_cast<float>(vec.mData[2]);
container->vertices[idx].normal.z = static_cast<float>(vec.mData[1]);
}
void FBXLoader::GetTangent(FbxMesh* mesh, FbxMeshInfo* meshInfo, int32 idx, int32 vertexCounter)
{
if (mesh->GetElementTangentCount() == 0)
{
// TEMP : 원래는 이런 저런 알고리즘으로 Tangent 만들어줘야 함
meshInfo->vertices[idx].tangent.x = 1.f;
meshInfo->vertices[idx].tangent.y = 0.f;
meshInfo->vertices[idx].tangent.z = 0.f;
return;
}
FbxGeometryElementTangent* tangent = mesh->GetElementTangent();
uint32 tangentIdx = 0;
if (tangent->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
{
if (tangent->GetReferenceMode() == FbxGeometryElement::eDirect)
tangentIdx = vertexCounter;
else
tangentIdx = tangent->GetIndexArray().GetAt(vertexCounter);
}
else if (tangent->GetMappingMode() == FbxGeometryElement::eByControlPoint)
{
if (tangent->GetReferenceMode() == FbxGeometryElement::eDirect)
tangentIdx = idx;
else
tangentIdx = tangent->GetIndexArray().GetAt(idx);
}
FbxVector4 vec = tangent->GetDirectArray().GetAt(tangentIdx);
meshInfo->vertices[idx].tangent.x = static_cast<float>(vec.mData[0]);
meshInfo->vertices[idx].tangent.y = static_cast<float>(vec.mData[2]);
meshInfo->vertices[idx].tangent.z = static_cast<float>(vec.mData[1]);
}
void FBXLoader::GetUV(FbxMesh* mesh, FbxMeshInfo* meshInfo, int32 idx, int32 uvIndex)
{
FbxVector2 uv = mesh->GetElementUV()->GetDirectArray().GetAt(uvIndex);
meshInfo->vertices[idx].uv.x = static_cast<float>(uv.mData[0]);
meshInfo->vertices[idx].uv.y = 1.f - static_cast<float>(uv.mData[1]);
}
Vec4 FBXLoader::GetMaterialData(FbxSurfaceMaterial* surface, const char* materialName, const char* factorName)
{
FbxDouble3 material;
FbxDouble factor = 0.f;
FbxProperty materialProperty = surface->FindProperty(materialName);
FbxProperty factorProperty = surface->FindProperty(factorName);
if (materialProperty.IsValid() && factorProperty.IsValid())
{
material = materialProperty.Get<FbxDouble3>();
factor = factorProperty.Get<FbxDouble>();
}
Vec4 ret = Vec4(
static_cast<float>(material.mData[0] * factor),
static_cast<float>(material.mData[1] * factor),
static_cast<float>(material.mData[2] * factor),
static_cast<float>(factor));
return ret;
}
wstring FBXLoader::GetTextureRelativeName(FbxSurfaceMaterial* surface, const char* materialProperty)
{
string name;
FbxProperty textureProperty = surface->FindProperty(materialProperty);
if (textureProperty.IsValid())
{
uint32 count = textureProperty.GetSrcObjectCount();
if (1 <= count)
{
FbxFileTexture* texture = textureProperty.GetSrcObject<FbxFileTexture>(0);
if (texture)
name = texture->GetRelativeFileName();
}
}
return s2ws(name);
}
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);
}
}
}
}
void FBXLoader::CreateMaterials()
{
for (size_t i = 0; i < _meshes.size(); i++)
{
for (size_t j = 0; j < _meshes[i].materials.size(); j++)
{
shared_ptr<Material> material = make_shared<Material>();
wstring key = _meshes[i].materials[j].name;
material->SetName(key);
material->SetShader(GET_SINGLE(Resources)->Get<Shader>(L"Deferred"));
{
wstring diffuseName = _meshes[i].materials[j].diffuseTexName.c_str();
wstring filename = fs::path(diffuseName).filename();
wstring key = filename;
shared_ptr<Texture> diffuseTexture = GET_SINGLE(Resources)->Get<Texture>(key);
if (diffuseTexture)
material->SetTexture(0, diffuseTexture);
}
{
wstring normalName = _meshes[i].materials[j].normalTexName.c_str();
wstring filename = fs::path(normalName).filename();
wstring key = filename;
shared_ptr<Texture> normalTexture = GET_SINGLE(Resources)->Get<Texture>(key);
if (normalTexture)
material->SetTexture(1, normalTexture);
}
{
wstring specularName = _meshes[i].materials[j].specularTexName.c_str();
wstring filename = fs::path(specularName).filename();
wstring key = filename;
shared_ptr<Texture> specularTexture = GET_SINGLE(Resources)->Get<Texture>(key);
if (specularTexture)
material->SetTexture(2, specularTexture);
}
GET_SINGLE(Resources)->Add<Material>(material->GetName(), material);
}
}
}
void FBXLoader::LoadBones(FbxNode* node, int32 idx, int32 parentIdx)
{
FbxNodeAttribute* attribute = node->GetNodeAttribute();
if (attribute && attribute->GetAttributeType() == FbxNodeAttribute::eSkeleton)
{
shared_ptr<FbxBoneInfo> bone = make_shared<FbxBoneInfo>();
bone->boneName = s2ws(node->GetName());
bone->parentIndex = parentIdx;
_bones.push_back(bone);
}
const int32 childCount = node->GetChildCount();
for (int32 i = 0; i < childCount; i++)
LoadBones(node->GetChild(i), static_cast<int32>(_bones.size()), idx);
}
void FBXLoader::LoadAnimationInfo()
{
_scene->FillAnimStackNameArray(OUT _animNames);
const int32 animCount = _animNames.GetCount();
for (int32 i = 0; i < animCount; i++)
{
FbxAnimStack* animStack = _scene->FindMember<FbxAnimStack>(_animNames[i]->Buffer());
if (animStack == nullptr)
continue;
shared_ptr<FbxAnimClipInfo> animClip = make_shared<FbxAnimClipInfo>();
animClip->name = s2ws(animStack->GetName());
animClip->keyFrames.resize(_bones.size()); // 키프레임은 본의 개수만큼
FbxTakeInfo* takeInfo = _scene->GetTakeInfo(animStack->GetName());
animClip->startTime = takeInfo->mLocalTimeSpan.GetStart();
animClip->endTime = takeInfo->mLocalTimeSpan.GetStop();
animClip->mode = _scene->GetGlobalSettings().GetTimeMode();
_animClips.push_back(animClip);
}
}
void FBXLoader::LoadAnimationData(FbxMesh* mesh, FbxMeshInfo* meshInfo)
{
const int32 skinCount = mesh->GetDeformerCount(FbxDeformer::eSkin);
if (skinCount <= 0 || _animClips.empty())
return;
meshInfo->hasAnimation = true;
for (int32 i = 0; i < skinCount; i++)
{
FbxSkin* fbxSkin = static_cast<FbxSkin*>(mesh->GetDeformer(i, FbxDeformer::eSkin));
if (fbxSkin)
{
FbxSkin::EType type = fbxSkin->GetSkinningType();
if (FbxSkin::eRigid == type || FbxSkin::eLinear)
{
const int32 clusterCount = fbxSkin->GetClusterCount();
for (int32 j = 0; j < clusterCount; j++)
{
FbxCluster* cluster = fbxSkin->GetCluster(j);
if (cluster->GetLink() == nullptr)
continue;
int32 boneIdx = FindBoneIndex(cluster->GetLink()->GetName());
assert(boneIdx >= 0);
FbxAMatrix matNodeTransform = GetTransform(mesh->GetNode());
LoadBoneWeight(cluster, boneIdx, meshInfo);
LoadOffsetMatrix(cluster, matNodeTransform, boneIdx, meshInfo);
const int32 animCount = _animNames.Size();
for (int32 k = 0; k < animCount; k++)
LoadKeyframe(k, mesh->GetNode(), cluster, matNodeTransform, boneIdx, meshInfo);
}
}
}
}
FillBoneWeight(mesh, meshInfo);
}
void FBXLoader::FillBoneWeight(FbxMesh* mesh, FbxMeshInfo* meshInfo)
{
const int32 size = static_cast<int32>(meshInfo->boneWeights.size());
for (int32 v = 0; v < size; v++)
{
BoneWeight& boneWeight = meshInfo->boneWeights[v];
boneWeight.Normalize();
float animBoneIndex[4] = {};
float animBoneWeight[4] = {};
const int32 weightCount = static_cast<int32>(boneWeight.boneWeights.size());
for (int32 w = 0; w < weightCount; w++)
{
animBoneIndex[w] = static_cast<float>(boneWeight.boneWeights[w].first);
animBoneWeight[w] = static_cast<float>(boneWeight.boneWeights[w].second);
}
memcpy(&meshInfo->vertices[v].indices, animBoneIndex, sizeof(Vec4));
memcpy(&meshInfo->vertices[v].weights, animBoneWeight, sizeof(Vec4));
}
}
void FBXLoader::LoadBoneWeight(FbxCluster* cluster, int32 boneIdx, FbxMeshInfo* meshInfo)
{
const int32 indicesCount = cluster->GetControlPointIndicesCount();
for (int32 i = 0; i < indicesCount; i++)
{
double weight = cluster->GetControlPointWeights()[i];
int32 vtxIdx = cluster->GetControlPointIndices()[i];
meshInfo->boneWeights[vtxIdx].AddWeights(boneIdx, weight);
}
}
void FBXLoader::LoadOffsetMatrix(FbxCluster* cluster, const FbxAMatrix& matNodeTransform, int32 boneIdx, FbxMeshInfo* meshInfo)
{
FbxAMatrix matClusterTrans;
FbxAMatrix matClusterLinkTrans;
// The transformation of the mesh at binding time
cluster->GetTransformMatrix(matClusterTrans);
// The transformation of the cluster(joint) at binding time from joint space to world space
cluster->GetTransformLinkMatrix(matClusterLinkTrans);
FbxVector4 V0 = { 1, 0, 0, 0 };
FbxVector4 V1 = { 0, 0, 1, 0 };
FbxVector4 V2 = { 0, 1, 0, 0 };
FbxVector4 V3 = { 0, 0, 0, 1 };
FbxAMatrix matReflect;
matReflect[0] = V0;
matReflect[1] = V1;
matReflect[2] = V2;
matReflect[3] = V3;
FbxAMatrix matOffset;
matOffset = matClusterLinkTrans.Inverse() * matClusterTrans;
matOffset = matReflect * matOffset * matReflect;
_bones[boneIdx]->matOffset = matOffset.Transpose();
}
void FBXLoader::LoadKeyframe(int32 animIndex, FbxNode* node, FbxCluster* cluster, const FbxAMatrix& matNodeTransform, int32 boneIdx, FbxMeshInfo* meshInfo)
{
if (_animClips.empty())
return;
FbxVector4 v1 = { 1, 0, 0, 0 };
FbxVector4 v2 = { 0, 0, 1, 0 };
FbxVector4 v3 = { 0, 1, 0, 0 };
FbxVector4 v4 = { 0, 0, 0, 1 };
FbxAMatrix matReflect;
matReflect.mData[0] = v1;
matReflect.mData[1] = v2;
matReflect.mData[2] = v3;
matReflect.mData[3] = v4;
FbxTime::EMode timeMode = _scene->GetGlobalSettings().GetTimeMode();
// 애니메이션 골라줌
FbxAnimStack* animStack = _scene->FindMember<FbxAnimStack>(_animNames[animIndex]->Buffer());
_scene->SetCurrentAnimationStack(OUT animStack);
FbxLongLong startFrame = _animClips[animIndex]->startTime.GetFrameCount(timeMode);
FbxLongLong endFrame = _animClips[animIndex]->endTime.GetFrameCount(timeMode);
for (FbxLongLong frame = startFrame; frame < endFrame; frame++)
{
FbxKeyFrameInfo keyFrameInfo = {};
FbxTime fbxTime = 0;
fbxTime.SetFrame(frame, timeMode);
FbxAMatrix matFromNode = node->EvaluateGlobalTransform(fbxTime);
FbxAMatrix matTransform = matFromNode.Inverse() * cluster->GetLink()->EvaluateGlobalTransform(fbxTime);
matTransform = matReflect * matTransform * matReflect;
keyFrameInfo.time = fbxTime.GetSecondDouble();
keyFrameInfo.matTransform = matTransform;
_animClips[animIndex]->keyFrames[boneIdx].push_back(keyFrameInfo);
}
}
int32 FBXLoader::FindBoneIndex(string name)
{
wstring boneName = wstring(name.begin(), name.end());
for (UINT i = 0; i < _bones.size(); ++i)
{
if (_bones[i]->boneName == boneName)
return i;
}
return -1;
}
FbxAMatrix FBXLoader::GetTransform(FbxNode* node)
{
const FbxVector4 translation = node->GetGeometricTranslation(FbxNode::eSourcePivot);
const FbxVector4 rotation = node->GetGeometricRotation(FbxNode::eSourcePivot);
const FbxVector4 scaling = node->GetGeometricScaling(FbxNode::eSourcePivot);
return FbxAMatrix(translation, rotation, scaling);
}
Mesh.h
더보기
#pragma once
#include "Object.h"
class Material;
class StructuredBuffer;
struct IndexBufferInfo
{
ComPtr<ID3D12Resource> buffer;
D3D12_INDEX_BUFFER_VIEW bufferView;
DXGI_FORMAT format;
uint32 count;
};
struct KeyFrameInfo
{
double time;
int32 frame;
Vec3 scale;
Vec4 rotation;
Vec3 translate;
};
struct BoneInfo
{
wstring boneName;
int32 parentIdx;
Matrix matOffset;
};
struct AnimClipInfo
{
wstring animName;
int32 frameCount;
double duration;
vector<vector<KeyFrameInfo>> keyFrames;
};
class Mesh : public Object
{
public:
Mesh();
virtual ~Mesh();
void Create(const vector<Vertex>& vertexBuffer, const vector<uint32>& indexbuffer);
void Render(uint32 instanceCount = 1, uint32 idx = 0);
void Render(shared_ptr<class InstancingBuffer>& buffer, uint32 idx = 0);
static shared_ptr<Mesh> CreateFromFBX(const struct FbxMeshInfo* meshInfo, class FBXLoader& loader);
private:
void CreateVertexBuffer(const vector<Vertex>& buffer);
void CreateIndexBuffer(const vector<uint32>& buffer);
void CreateBonesAndAnimations(class FBXLoader& loader);
Matrix GetMatrix(FbxAMatrix& matrix);
public:
uint32 GetSubsetCount() { return static_cast<uint32>(_vecIndexInfo.size()); }
const vector<BoneInfo>* GetBones() { return &_bones; }
uint32 GetBoneCount() { return static_cast<uint32>(_bones.size()); }
const vector<AnimClipInfo>* GetAnimClip() { return &_animClips; }
bool IsAnimMesh() { return !_animClips.empty(); }
shared_ptr<StructuredBuffer> GetBoneFrameDataBuffer(int32 index = 0) { return _frameBuffer[index]; } // 전체 본 프레임 정보
shared_ptr<StructuredBuffer> GetBoneOffsetBuffer() { return _offsetBuffer; }
private:
ComPtr<ID3D12Resource> _vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW _vertexBufferView = {};
uint32 _vertexCount = 0;
vector<IndexBufferInfo> _vecIndexInfo;
// Animation
vector<AnimClipInfo> _animClips;
vector<BoneInfo> _bones;
shared_ptr<StructuredBuffer> _offsetBuffer; // 각 뼈의 offset 행렬
vector<shared_ptr<StructuredBuffer>> _frameBuffer; // 전체 본 프레임 정보
};
Mesh.cpp
더보기
#include "pch.h"
#include "Mesh.h"
#include "Engine.h"
#include "Material.h"
#include "InstancingBuffer.h"
#include "FBXLoader.h"
#include "StructuredBuffer.h"
Mesh::Mesh() : Object(OBJECT_TYPE::MESH)
{
}
Mesh::~Mesh()
{
}
void Mesh::Create(const vector<Vertex>& vertexBuffer, const vector<uint32>& indexBuffer)
{
CreateVertexBuffer(vertexBuffer);
CreateIndexBuffer(indexBuffer);
}
void Mesh::Render(uint32 instanceCount, uint32 idx)
{
GRAPHICS_CMD_LIST->IASetVertexBuffers(0, 1, &_vertexBufferView); // Slot: (0~15)
GRAPHICS_CMD_LIST->IASetIndexBuffer(&_vecIndexInfo[idx].bufferView);
GEngine->GetGraphicsDescHeap()->CommitTable();
GRAPHICS_CMD_LIST->DrawIndexedInstanced(_vecIndexInfo[idx].count, instanceCount, 0, 0, 0);
}
void Mesh::Render(shared_ptr<InstancingBuffer>& buffer, uint32 idx)
{
D3D12_VERTEX_BUFFER_VIEW bufferViews[] = { _vertexBufferView, buffer->GetBufferView() };
GRAPHICS_CMD_LIST->IASetVertexBuffers(0, 2, bufferViews);
GRAPHICS_CMD_LIST->IASetIndexBuffer(&_vecIndexInfo[idx].bufferView);
GEngine->GetGraphicsDescHeap()->CommitTable();
GRAPHICS_CMD_LIST->DrawIndexedInstanced(_vecIndexInfo[idx].count, buffer->GetCount(), 0, 0, 0);
}
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;
}
void Mesh::CreateVertexBuffer(const vector<Vertex>& buffer)
{
_vertexCount = static_cast<uint32>(buffer.size());
uint32 bufferSize = _vertexCount * sizeof(Vertex);
D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
DEVICE->CreateCommittedResource(
&heapProperty,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&_vertexBuffer));
// Copy the triangle data to the vertex buffer.
void* vertexDataBuffer = nullptr;
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
_vertexBuffer->Map(0, &readRange, &vertexDataBuffer);
::memcpy(vertexDataBuffer, &buffer[0], bufferSize);
_vertexBuffer->Unmap(0, nullptr);
// Initialize the vertex buffer view.
_vertexBufferView.BufferLocation = _vertexBuffer->GetGPUVirtualAddress();
_vertexBufferView.StrideInBytes = sizeof(Vertex); // 정점 1개 크기
_vertexBufferView.SizeInBytes = bufferSize; // 버퍼의 크기
}
void Mesh::CreateIndexBuffer(const vector<uint32>& buffer)
{
uint32 indexCount = static_cast<uint32>(buffer.size());
uint32 bufferSize = indexCount * sizeof(uint32);
D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
ComPtr<ID3D12Resource> indexBuffer;
DEVICE->CreateCommittedResource(
&heapProperty,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&indexBuffer));
void* indexDataBuffer = nullptr;
CD3DX12_RANGE readRange(0, 0);
indexBuffer->Map(0, &readRange, &indexDataBuffer);
::memcpy(indexDataBuffer, &buffer[0], bufferSize);
indexBuffer->Unmap(0, nullptr);
D3D12_INDEX_BUFFER_VIEW indexBufferView;
indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();
indexBufferView.Format = DXGI_FORMAT_R32_UINT;
indexBufferView.SizeInBytes = bufferSize;
IndexBufferInfo info =
{
indexBuffer,
indexBufferView,
DXGI_FORMAT_R32_UINT,
indexCount
};
_vecIndexInfo.push_back(info);
}
void Mesh::CreateBonesAndAnimations(class FBXLoader& loader)
{
#pragma region AnimClip
uint32 frameCount = 0;
vector<shared_ptr<FbxAnimClipInfo>>& animClips = loader.GetAnimClip();
for (shared_ptr<FbxAnimClipInfo>& ac : animClips)
{
AnimClipInfo info = {};
info.animName = ac->name;
info.duration = ac->endTime.GetSecondDouble() - ac->startTime.GetSecondDouble();
int32 startFrame = static_cast<int32>(ac->startTime.GetFrameCount(ac->mode));
int32 endFrame = static_cast<int32>(ac->endTime.GetFrameCount(ac->mode));
info.frameCount = endFrame - startFrame;
info.keyFrames.resize(ac->keyFrames.size());
const int32 boneCount = static_cast<int32>(ac->keyFrames.size());
for (int32 b = 0; b < boneCount; b++)
{
auto& vec = ac->keyFrames[b];
const int32 size = static_cast<int32>(vec.size());
frameCount = max(frameCount, static_cast<uint32>(size));
info.keyFrames[b].resize(size);
for (int32 f = 0; f < size; f++)
{
FbxKeyFrameInfo& kf = vec[f];
// FBX에서 파싱한 정보들로 채워준다
KeyFrameInfo& kfInfo = info.keyFrames[b][f];
kfInfo.time = kf.time;
kfInfo.frame = static_cast<int32>(size);
kfInfo.scale.x = static_cast<float>(kf.matTransform.GetS().mData[0]);
kfInfo.scale.y = static_cast<float>(kf.matTransform.GetS().mData[1]);
kfInfo.scale.z = static_cast<float>(kf.matTransform.GetS().mData[2]);
kfInfo.rotation.x = static_cast<float>(kf.matTransform.GetQ().mData[0]);
kfInfo.rotation.y = static_cast<float>(kf.matTransform.GetQ().mData[1]);
kfInfo.rotation.z = static_cast<float>(kf.matTransform.GetQ().mData[2]);
kfInfo.rotation.w = static_cast<float>(kf.matTransform.GetQ().mData[3]);
kfInfo.translate.x = static_cast<float>(kf.matTransform.GetT().mData[0]);
kfInfo.translate.y = static_cast<float>(kf.matTransform.GetT().mData[1]);
kfInfo.translate.z = static_cast<float>(kf.matTransform.GetT().mData[2]);
}
}
_animClips.push_back(info);
}
#pragma endregion
#pragma region Bones
vector<shared_ptr<FbxBoneInfo>>& bones = loader.GetBones();
for (shared_ptr<FbxBoneInfo>& bone : bones)
{
BoneInfo boneInfo = {};
boneInfo.parentIdx = bone->parentIndex;
boneInfo.matOffset = GetMatrix(bone->matOffset);
boneInfo.boneName = bone->boneName;
_bones.push_back(boneInfo);
}
#pragma endregion
#pragma region SkinData
if (IsAnimMesh())
{
// BoneOffet 행렬
const int32 boneCount = static_cast<int32>(_bones.size());
vector<Matrix> offsetVec(boneCount);
for (size_t b = 0; b < boneCount; b++)
offsetVec[b] = _bones[b].matOffset;
// OffsetMatrix StructuredBuffer 세팅
_offsetBuffer = make_shared<StructuredBuffer>();
_offsetBuffer->Init(sizeof(Matrix), static_cast<uint32>(offsetVec.size()), offsetVec.data());
const int32 animCount = static_cast<int32>(_animClips.size());
for (int32 i = 0; i < animCount; i++)
{
AnimClipInfo& animClip = _animClips[i];
// 애니메이션 프레임 정보
vector<AnimFrameParams> frameParams;
frameParams.resize(_bones.size() * animClip.frameCount);
for (int32 b = 0; b < boneCount; b++)
{
const int32 keyFrameCount = static_cast<int32>(animClip.keyFrames[b].size());
for (int32 f = 0; f < keyFrameCount; f++)
{
int32 idx = static_cast<int32>(boneCount * f + b);
frameParams[idx] = AnimFrameParams
{
Vec4(animClip.keyFrames[b][f].scale),
animClip.keyFrames[b][f].rotation, // Quaternion
Vec4(animClip.keyFrames[b][f].translate)
};
}
}
// StructuredBuffer 세팅
_frameBuffer.push_back(make_shared<StructuredBuffer>());
_frameBuffer.back()->Init(sizeof(AnimFrameParams), static_cast<uint32>(frameParams.size()), frameParams.data());
}
}
#pragma endregion
}
Matrix Mesh::GetMatrix(FbxAMatrix& matrix)
{
Matrix mat;
for (int32 y = 0; y < 4; ++y)
for (int32 x = 0; x < 4; ++x)
mat.m[y][x] = static_cast<float>(matrix.Get(y, x));
return mat;
}
Animator Shader
더보기
#ifndef _ANIMATION_FX_
#define _ANIMATION_FX_
#include "params.fx"
#include "utils.fx"
#include "math.fx"
struct AnimFrameParams
{
float4 scale;
float4 rotation;
float4 translation;
};
StructuredBuffer<AnimFrameParams> g_bone_frame : register(t8);
StructuredBuffer<matrix> g_offset : register(t9);
RWStructuredBuffer<matrix> g_final : register(u0);
// ComputeAnimation
// g_int_0 : BoneCount
// g_int_1 : CurrentFrame
// g_int_2 : NextFrame
// g_float_0 : Ratio
[numthreads(256, 1, 1)]
void CS_Main(int3 threadIdx : SV_DispatchThreadID)
{
if (g_int_0 <= threadIdx.x)
return;
int boneCount = g_int_0;
int currentFrame = g_int_1;
int nextFrame = g_int_2;
float ratio = g_float_0;
uint idx = (boneCount * currentFrame) + threadIdx.x;
uint nextIdx = (boneCount * nextFrame) + threadIdx.x;
float4 quaternionZero = float4(0.f, 0.f, 0.f, 1.f);
float4 scale = lerp(g_bone_frame[idx].scale, g_bone_frame[nextIdx].scale, ratio);
float4 rotation = QuaternionSlerp(g_bone_frame[idx].rotation, g_bone_frame[nextIdx].rotation, ratio);
float4 translation = lerp(g_bone_frame[idx].translation, g_bone_frame[nextIdx].translation, ratio);
matrix matBone = MatrixAffineTransformation(scale, quaternionZero, rotation, translation);
g_final[threadIdx.x] = mul(g_offset[threadIdx.x], matBone);
}
#endif
Animator.h
더보기
#pragma once
#include "Component.h"
#include "Mesh.h"
class Material;
class StructuredBuffer;
class Mesh;
class Animator : public Component
{
public:
Animator();
virtual ~Animator();
public:
void SetBones(const vector<BoneInfo>* bones) { _bones = bones; }
void SetAnimClip(const vector<AnimClipInfo>* animClips);
void PushData();
int32 GetAnimCount() { return static_cast<uint32>(_animClips->size()); }
int32 GetCurrentClipIndex() { return _clipIndex; }
void Play(uint32 idx);
public:
virtual void FinalUpdate() override;
private:
const vector<BoneInfo>* _bones;
const vector<AnimClipInfo>* _animClips;
float _updateTime = 0.f;
int32 _clipIndex = 0;
int32 _frame = 0;
int32 _nextFrame = 0;
float _frameRatio = 0;
shared_ptr<Material> _computeMaterial;
shared_ptr<StructuredBuffer> _boneFinalMatrix; // 특정 프레임의 최종 행렬
bool _boneFinalUpdated = false;
};
Animator.cpp
더보기
#include "pch.h"
#include "Animator.h"
#include "Timer.h"
#include "Resources.h"
#include "Material.h"
#include "Mesh.h"
#include "MeshRenderer.h"
#include "StructuredBuffer.h"
Animator::Animator() : Component(COMPONENT_TYPE::ANIMATOR)
{
_computeMaterial = GET_SINGLE(Resources)->Get<Material>(L"ComputeAnimation");
_boneFinalMatrix = make_shared<StructuredBuffer>();
}
Animator::~Animator()
{
}
void Animator::FinalUpdate()
{
_updateTime += DELTA_TIME;
const AnimClipInfo& animClip = _animClips->at(_clipIndex);
if (_updateTime >= animClip.duration)
_updateTime = 0.f;
const int32 ratio = static_cast<int32>(animClip.frameCount / animClip.duration);
_frame = static_cast<int32>(_updateTime * ratio);
_frame = min(_frame, animClip.frameCount - 1);
_nextFrame = min(_frame + 1, animClip.frameCount - 1);
_frameRatio = static_cast<float>(_frame - _frame);
}
void Animator::SetAnimClip(const vector<AnimClipInfo>* animClips)
{
_animClips = animClips;
}
void Animator::PushData()
{
uint32 boneCount = static_cast<uint32>(_bones->size());
if (_boneFinalMatrix->GetElementCount() < boneCount)
_boneFinalMatrix->Init(sizeof(Matrix), boneCount);
// Compute Shader
shared_ptr<Mesh> mesh = GetGameObject()->GetMeshRenderer()->GetMesh();
mesh->GetBoneFrameDataBuffer(_clipIndex)->PushComputeSRVData(SRV_REGISTER::t8);
mesh->GetBoneOffsetBuffer()->PushComputeSRVData(SRV_REGISTER::t9);
_boneFinalMatrix->PushComputeUAVData(UAV_REGISTER::u0);
_computeMaterial->SetInt(0, boneCount);
_computeMaterial->SetInt(1, _frame);
_computeMaterial->SetInt(2, _nextFrame);
_computeMaterial->SetFloat(0, _frameRatio);
uint32 groupCount = (boneCount / 256) + 1;
_computeMaterial->Dispatch(groupCount, 1, 1);
// Graphics Shader
_boneFinalMatrix->PushGraphicsData(SRV_REGISTER::t7);
}
void Animator::Play(uint32 idx)
{
assert(idx < _animClips->size());
_clipIndex = idx;
_updateTime = 0.f;
}