You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在DX12引擎中集成Assimp骨骼动画遇矩阵计算问题求助

Assimp集成DX12引擎骨骼动画矩阵计算问题

作为学习的一部分,我正尝试将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);禁用动画,角色会正常显示为T姿:
禁用动画后的T姿效果

我尚未定位到问题根源,已尝试以下解决方法:

  • 转置最终结果矩阵
  • 转置offsetMatrix
  • 使用.glb和.gltf格式替代fbx
  • 设置SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false)
  • 使用带3-4个关节的简单立方体模型测试

一个可能有用的观察:朝向世界空间的关节未出现该问题,这让我怀疑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];
    }
}

这个问题已经困扰我一周了,不确定下一步该排查哪里,非常感谢您的帮助!


内容的提问来源于stack exchange,提问作者Gregm8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 03:49:55