最近我一直在尝试将 assimp 实现到 Frank Luna 的基本 dx12 引擎中,作为我学习的一部分。我在让矩阵数学正常工作时遇到了真正的麻烦,并且遇到了一些死胡同。
这是我用来从骨架和动画中获取转换并传递给 GPU 的主要函数。
首先,这里有一个 github 链接供任何喜欢直接查看源代码的人使用:repo
void Skeleton::GetTransforms(float timePos, aiNode* node, aiAnimation* animation, aiMatrix4x4& parentTransform, const aiMatrix4x4& globalInverseTransform, std::vector<DirectX::XMFLOAT4X4>& transforms)
{
std::string nodeName(node->mName.data);
aiMatrix4x4 nodeTransform = node->mTransformation;
const aiNodeAnim* nodeAnim = FindNodeAnim(animation, nodeName);
if (nodeAnim)
{
aiVector3D scaling;
aiQuaternion rotation;
aiVector3D translate;
CalcInterpolatedScaling(scaling, timePos, nodeAnim);
CalcInterpolatedRotation(rotation, timePos, nodeAnim);
CalcInterpolatedPosition(translate, timePos, nodeAnim);
nodeTransform = CreateAffineMatrix(scaling, rotation, translate);
}
aiMatrix4x4 globalTransform = parentTransform * nodeTransform;
if (this->bones.find(nodeName) != this->bones.end())
{
int boneIndex = this->bones[nodeName].index;
aiMatrix4x4 finalTransform = globalInverseTransform * globalTransform * this->bones[nodeName].offsetMatrix;
aiMatConvert(finalTransform, transforms[boneIndex]);
}
for (UINT i = 0u; i < node->mNumChildren; i++)
{
GetTransforms(timePos, node->mChildren[i], animation, globalTransform, globalInverseTransform, transforms);
}
}
此代码与我在其他几个线程中发现的代码非常吻合:
不幸的是,这没有产生正确的结果,目前这产生了一个奇怪的结果,如下所示:
如果我通过注释掉禁用动画
nodeTransform = CreateAffineMatrix(scaling, rotation, translate);
结果如我所料,人物坐在TPose:
我一直无法弄清楚我的代码库是如何工作不正常的,这些是一些我已经尝试解决问题的方法:
一个可能有用的观察,我注意到这个问题不会发生在面向世界空间的关节上。所以基本上他们没有一个独特的方向。这让我觉得
globalInverseTransform * globalTransform * this->bones[nodeName].offsetMatrix
可能无法正常工作。
我正在使用此功能采购 globalInverseTransform:
struct Animation
{
Skeleton* skeleton;
aiNode* rootNode;
aiAnimation* animation;
std::vector<DirectX::XMFLOAT4X4> transforms;
float TimePos = 0.0f;
bool Loop = true;
float Speed = 1.0f;
void UpdateSkinnedAnimation(float dt)
{
dt *= Speed;
TimePos += dt;
if (Loop)
{
float duration = animation->mDuration;
if (TimePos > duration)
{
TimePos = 0.0;
}
}
skeleton->GetTransforms(TimePos, rootNode, animation, aiMatrix4x4(), rootNode->mTransformation.Inverse(), transforms);
}
};
rootNode 指针在网格加载阶段设置,数据存储为堆内存,因此在运行时不会丢失其范围。
其余的骨架相关功能如下所示,
非常感谢!这真的让我难过了一个星期,不确定下一步该看哪种方式,一些帮助将不胜感激! :)
float clamp(const float& f)
{
return (f < 0.0f) ? 0.0f : ((f > 1.0f) ? 1.0f : f);
}
void aiMatConvert(const aiMatrix4x4& aiMatrix, DirectX::XMFLOAT4X4& dXMatrix)
{
DirectX::XMMATRIX meshToBoneTransform = DirectX::XMMATRIX(aiMatrix.a1, aiMatrix.a2,
aiMatrix.a3, aiMatrix.a4, aiMatrix.b1, aiMatrix.b2,
aiMatrix.b3, aiMatrix.b4, aiMatrix.c1, aiMatrix.c2,
aiMatrix.c3, aiMatrix.c4, aiMatrix.d1, aiMatrix.d2,
aiMatrix.d3, aiMatrix.d4);
DirectX::XMStoreFloat4x4(&dXMatrix, meshToBoneTransform);
}
const aiNodeAnim* Skeleton::FindNodeAnim(const aiAnimation* animation, const std::string& nodeName)
{
for (unsigned int i = 0; i < animation->mNumChannels; ++i)
{
const aiNodeAnim* nodeAnim = animation->mChannels[i];
if (std::string(nodeAnim->mNodeName.data) == nodeName)
{
return nodeAnim;
}
}
return nullptr;
}
int Skeleton::FindPositionKey(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
for (int i = 0; i < pNodeAnim->mNumPositionKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
return i;
}
}
return 0;
}
int Skeleton::FindRotationKey(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
for (int i = 0; i < pNodeAnim->mNumRotationKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
return i;
}
}
return 0;
}
int Skeleton::FindScalingKey(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
for (int i = 0; i < pNodeAnim->mNumScalingKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
return i;
}
}
return 0;
}
void Skeleton::CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumPositionKeys == 1) {
Out = pNodeAnim->mPositionKeys[0].mValue;
return;
}
int PositionIndex = FindPositionKey(AnimationTime, pNodeAnim);
int NextPositionIndex = (PositionIndex + 1);
float DeltaTime = (float)(pNodeAnim->mPositionKeys[NextPositionIndex].mTime - pNodeAnim->mPositionKeys[PositionIndex].mTime);
float Factor = clamp((AnimationTime - (float)pNodeAnim->mPositionKeys[PositionIndex].mTime) / DeltaTime);
const aiVector3D& Start = pNodeAnim->mPositionKeys[PositionIndex].mValue;
const aiVector3D& End = pNodeAnim->mPositionKeys[NextPositionIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
void Skeleton::CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumRotationKeys == 1) {
Out = pNodeAnim->mRotationKeys[0].mValue;
return;
}
int RotationIndex = FindRotationKey(AnimationTime, pNodeAnim);
int NextRotationIndex = (RotationIndex + 1);
float DeltaTime = (float)(pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime);
float Factor = clamp((AnimationTime - (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime);
const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
Out = Out.Normalize();
}
void Skeleton::CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumScalingKeys == 1) {
Out = pNodeAnim->mScalingKeys[0].mValue;
return;
}
int ScalingIndex = FindScalingKey(AnimationTime, pNodeAnim);
int NextScalingIndex = (ScalingIndex + 1);
float DeltaTime = (float)(pNodeAnim->mScalingKeys[NextScalingIndex].mTime - pNodeAnim->mScalingKeys[ScalingIndex].mTime);
float Factor = clamp((AnimationTime - (float)pNodeAnim->mScalingKeys[ScalingIndex].mTime) / DeltaTime);
const aiVector3D& Start = pNodeAnim->mScalingKeys[ScalingIndex].mValue;
const aiVector3D& End = pNodeAnim->mScalingKeys[NextScalingIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
aiMatrix4x4 CreateAffineMatrix(const aiVector3D& scaling, const aiQuaternion& rotation, const aiVector3D& translate)
{
aiMatrix4x4 scalingMatrix;
aiMatrix4x4 translationMatrix;
aiMatrix4x4::Scaling(scaling, scalingMatrix);
aiMatrix4x4::Translation(translate, translationMatrix);
aiMatrix4x4 rotationMatrix = aiMatrix4x4(rotation.GetMatrix());
return scalingMatrix * rotationMatrix * translationMatrix;
}
void Mesh::ReadSkeleton(const aiScene* scene, Skeleton* mSkeleton)
{
unsigned int numMesh = scene->mNumMeshes;
aiMesh** meshList = scene->mMeshes;
int boneCount = 0;
for (UINT x = 0; x < numMesh; ++x)
{
for (UINT i = 0; i < meshList[x]->mNumBones; ++i)
{
std::string boneName(meshList[x]->mBones[i]->mName.C_Str());
bool exists = mSkeleton->bones.find(boneName) != mSkeleton->bones.end();
if (!exists)
{
aiMatrix4x4& offsetMatrix = meshList[x]->mBones[i]->mOffsetMatrix;
Joint joint(boneName, boneCount, offsetMatrix);
mSkeleton->bones[boneName] = joint;
boneCount += 1;
}
}
}
}
void Mesh::ReadAnimations(const aiScene* scene, std::unordered_map<std::string, aiAnimation*> animations)
{
unsigned int numAnim = scene->mNumAnimations;
for (UINT x = 0; x < numAnim; ++x)
{
animations[scene->mAnimations[x]->mName.C_Str()] = scene->mAnimations[x];
}
}