
글에 사용된 모든 그림과 내용은 직접 작성한 것입니다.
[유튜브 영상]
[깃허브 보러가기]
https://github.com/Chang-Jin-Lee/D3D11-AliceTutorial/tree/main/23_Rigid_Animation
[풀리퀘 보러가기]
https://github.com/Chang-Jin-Lee/D3D11-AliceTutorial/pull/47
D3D11에서 assimp로 로드한 FBX에 본이 없는 경우, 있는 경우 각각의 애니메이션을 실행하는 방법을 정리하기 위함
이번 글에서는 이전에 다룬 글에서 분기처리합니다. Skinned, Rigid를 각각 예외처리를 합니다.
3D 모델이 완벽하게 그려졌고, Skinned 가중치 적용 애니메이션을 구현했다는 걸 전제로 진행합니다.
아래의 아티클에서 이전 내용을 복습할 수 있습니다
https://velog.io/@whoamicj/DX11-fbx-Animation
이전 Skinned 3D 모델의 애니메이션을 그리는 예제에서 로직을 수정합니다
bool FbxManager::Load(ID3D11Device* device, const std::wstring& pathW)
{
Release();
m_->Importer = new Impl::AssimpImporterHolder();
// Importer properties for speed/robustness
m_->Importer->importer.SetPropertyInteger(AI_CONFIG_PP_LBW_MAX_WEIGHTS, 4);
std::string pathA = Utf8FromWString(pathW);
const aiScene* scene = m_->Importer->importer.ReadFile(pathA,
aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_ImproveCacheLocality |
aiProcess_GenSmoothNormals | aiProcess_CalcTangentSpace | aiProcess_ConvertToLeftHanded |
aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph | aiProcess_LimitBoneWeights);
if (!scene || !scene->HasMeshes()) return false;
m_->SceneMutable = const_cast<aiScene*>(scene);
// Save global inverse of root (row-vector mapping)
{
aiMatrix4x4 I = scene->mRootNode->mTransformation;
I.Inverse();
DirectX::XMFLOAT4X4 gi;
gi._11 = (float)I.a1; gi._12 = (float)I.a2; gi._13 = (float)I.a3; gi._14 = (float)I.a4;
gi._21 = (float)I.b1; gi._22 = (float)I.b2; gi._23 = (float)I.b3; gi._24 = (float)I.b4;
gi._31 = (float)I.c1; gi._32 = (float)I.c2; gi._33 = (float)I.c3; gi._34 = (float)I.c4;
gi._41 = (float)I.d1; gi._42 = (float)I.d2; gi._43 = (float)I.d3; gi._44 = (float)I.d4;
m_->GlobalInverse = gi;
}
std::wstring baseDir = pathW;
size_t slash = baseDir.find_last_of(L"/\\");
baseDir = (slash == std::wstring::npos) ? L"" : baseDir.substr(0, slash + 1);
if (!LoadMaterials(device, scene, baseDir)) return false;
if (!BuildMeshBuffers(device, scene)) return false;
BuildSkeletonAndNodeIndex(scene);
CollectBonesAndOffsets(scene);
// Rigid 애니메이션. Bone이 없음 → 가중치 누적/정규화 스킵
if (m_->BoneNames.empty() && scene->mNumAnimations > 0)
{
// Rigid: 스켈레톤 노드로 가짜 본 구성 후, 각 정점에 소유 노드 1웨이트만 부여
m_CurrentType = AnimationType::Rigid;
BuildRigidBonesFromSkeleton();
BuildRigidWeightsFromOwners();
if (m_->pVB) { ApplyInfluencesToVB(device); }
InitAnimationMetadata(scene);
return true;
}
// Skinned 애니메이션. 본이 있음. → 가중치 누적/정규화 수행
if (!m_->BoneNames.empty())
{
std::vector<size_t> baseVertex; BuildBaseVertexTable(scene, baseVertex);
AccumulateVertexWeights(scene, baseVertex);
NormalizeInfluencesAndFlag();
if (m_->pVB) { ApplyInfluencesToVB(device); }
InitAnimationMetadata(scene);
m_CurrentType = AnimationType::Skinned;
return true;
}
// Static Mesh 본도, 애니메이션도 없음.
InitAnimationMetadata(scene);
m_CurrentType = AnimationType::None;
return true;
}





void FbxManager::BuildRigidBonesFromSkeleton()
{
if (!m_->BoneNames.empty()) return;
m_->BoneNames.clear();
m_->BoneOffset.clear();
m_->BoneIndexOfName.clear();
m_->BoneNames.reserve(m_->Skeleton.size());
m_->BoneOffset.reserve(m_->Skeleton.size());
for (size_t i = 0; i < m_->Skeleton.size(); ++i)
{
const auto& sn = m_->Skeleton[i];
m_->BoneIndexOfName[sn.name] = (int)i;
m_->BoneNames.push_back(sn.name);
DirectX::XMFLOAT4X4 I{ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
m_->BoneOffset.push_back(I);
}
}

void FbxManager::CollectBonesAndOffsets(const aiScene* scene)
{
m_->HasSkinning = false;
m_->BoneNames.clear();
m_->BoneOffset.clear();
m_->BoneIndexOfName.clear();
for (unsigned mi = 0; mi < scene->mNumMeshes; ++mi)
{
const aiMesh* mesh = scene->mMeshes[mi];
for (unsigned bi = 0; bi < mesh->mNumBones; ++bi)
{
const aiBone* b = mesh->mBones[bi];
std::string name = b->mName.C_Str();
std::wstring nameW = WStringFromUtf8(name);
if (m_->BoneIndexOfName.find(name) == m_->BoneIndexOfName.end())
{
int newIndex = (int)m_->BoneNames.size();
m_->BoneIndexOfName[name] = newIndex;
m_->BoneNames.push_back(name);
DirectX::XMFLOAT4X4 off;
off._11 = (float)b->mOffsetMatrix.a1; off._12 = (float)b->mOffsetMatrix.a2; off._13 = (float)b->mOffsetMatrix.a3; off._14 = (float)b->mOffsetMatrix.a4;
off._21 = (float)b->mOffsetMatrix.b1; off._22 = (float)b->mOffsetMatrix.b2; off._23 = (float)b->mOffsetMatrix.b3; off._24 = (float)b->mOffsetMatrix.b4;
off._31 = (float)b->mOffsetMatrix.c1; off._32 = (float)b->mOffsetMatrix.c2; off._33 = (float)b->mOffsetMatrix.c3; off._34 = (float)b->mOffsetMatrix.c4;
off._41 = (float)b->mOffsetMatrix.d1; off._42 = (float)b->mOffsetMatrix.d2; off._43 = (float)b->mOffsetMatrix.d3; off._44 = (float)b->mOffsetMatrix.d4;
m_->BoneOffset.push_back(off);
auto itNode = m_->NodeIndexOfName.find(name);
if (itNode != m_->NodeIndexOfName.end()) {
m_->Skeleton[itNode->second].isBone = true;
if (m_->Skeleton[itNode->second].nameW.empty()) m_->Skeleton[itNode->second].nameW = nameW;
}
}
}
}
}
| Animation - Skinned | Animation - Rigid |
|---|---|
![]() | ![]() |
| Animation - Static Mesh |
|---|
![]() |