Assimp 动画骨骼变换

Posted

技术标签:

【中文标题】Assimp 动画骨骼变换【英文标题】:Assimp animation bone transformation 【发布时间】:2017-01-30 15:30:45 【问题描述】:

最近我在做骨骼动画导入,所以我用一些 IK 技术制作了一个类似于 3d minecraft 的模型来测试 Assimp 动画导入。输出格式为 COLLADA(*.dae),我使用的工具是 Blender。在编程方面,我的环境是 opengl/glm/assimp。我认为这些信息对于我的问题来说已经足够了。一件事,模型的动画,我只是记录了 7 个 unmove 关键帧来测试 assimp 动画。

首先,我猜我的变换除了局部变换部分是正确的,所以让函数只返回glm::mat4(1.0f),结果显示绑定姿势(不确定)模型。 (见下图)

其次,将glm::mat4(1.0f)的值转回bone->localTransform = transform * scaling * glm::mat4(1.0f);,然后模型变形。 (见下图)

在搅拌机中测试图像和模型: (bone->localTransform = glm::mat4(1.0f) * scaling * rotate;:这张图片在地下:()

这里的代码:

void MeshModel::UpdateAnimations(float time, std::vector<Bone*>& bones)

    for each (Bone* bone in bones)
    
        glm::mat4 rotate = GetInterpolateRotation(time, bone->rotationKeys);
        glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);
        glm::mat4 scaling = GetInterpolateScaling(time, bone->scalingKeys);
        //bone->localTransform = transform * scaling * glm::mat4(1.0f);
        //bone->localTransform = glm::mat4(1.0f) * scaling * rotate;
        //bone->localTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f));
        bone->localTransform = glm::mat4(1.0f);
    


void MeshModel::UpdateBone(Bone * bone)

    glm::mat4 parentTransform = bone->getParentTransform();
    bone->nodeTransform = parentTransform
        * bone->transform  // assimp_node->mTransformation
        * bone->localTransform; // T S R matrix

    bone->finalTransform = globalInverse
        * bone->nodeTransform 
        * bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix

    for (int i = 0; i < (int)bone->children.size(); i++) 
        UpdateBone(bone->children[i]);
    


glm::mat4 Bone::getParentTransform()

    if (this->parent != nullptr)
        return parent->nodeTransform;
    else
        return glm::mat4(1.0f);


glm::mat4 MeshModel::GetInterpolateRotation(float time, std::vector<BoneKey>& keys)

    // we need at least two values to interpolate...
    if ((int)keys.size() == 0) 
        return glm::mat4(1.0f);
    
    if ((int)keys.size() == 1) 
        return glm::mat4_cast(keys[0].rotation);
    

    int rotationIndex = FindBestTimeIndex(time, keys);
    int nextRotationIndex = (rotationIndex + 1);
    assert(nextRotationIndex < (int)keys.size());
    float DeltaTime = (float)(keys[nextRotationIndex].time - keys[rotationIndex].time);
    float Factor = (time - (float)keys[rotationIndex].time) / DeltaTime;
    if (Factor < 0.0f)
        Factor = 0.0f;
    if (Factor > 1.0f)
        Factor = 1.0f;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const glm::quat& startRotationQ = keys[rotationIndex].rotation;
    const glm::quat& endRotationQ = keys[nextRotationIndex].rotation;
    glm::quat interpolateQ = glm::lerp(endRotationQ, startRotationQ, Factor);
    interpolateQ = glm::normalize(interpolateQ);
    return glm::mat4_cast(interpolateQ);


glm::mat4 MeshModel::GetInterpolateTransform(float time, std::vector<BoneKey>& keys)

    // we need at least two values to interpolate...
    if ((int)keys.size() == 0) 
        return glm::mat4(1.0f);
    
    if ((int)keys.size() == 1) 
        return glm::translate(glm::mat4(1.0f), keys[0].vector);
    

    int translateIndex = FindBestTimeIndex(time, keys);
    int nextTranslateIndex = (translateIndex + 1);
    assert(nextTranslateIndex < (int)keys.size());
    float DeltaTime = (float)(keys[nextTranslateIndex].time - keys[translateIndex].time);
    float Factor = (time - (float)keys[translateIndex].time) / DeltaTime;
    if (Factor < 0.0f)
        Factor = 0.0f;
    if (Factor > 1.0f)
        Factor = 1.0f;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const glm::vec3& startTranslate = keys[translateIndex].vector;
    const glm::vec3& endTrabslate = keys[nextTranslateIndex].vector;
    glm::vec3 delta = endTrabslate - startTranslate;
    glm::vec3 resultVec = startTranslate + delta * Factor;
    return glm::translate(glm::mat4(1.0f), resultVec);

代码思路引用自Matrix calculations for gpu skinning和Skeletal Animation With Assimp。

总的来说,我把assimp中的所有信息都提取到MeshModel中并保存到骨骼结构中,所以我认为这些信息还可以吗?

最后一件事,我的顶点着色器代码:

#version 330 core 
#define MAX_BONES_PER_VERTEX 4

in vec3 position;
in vec2 texCoord;
in vec3 normal;
in ivec4 boneID;
in vec4 boneWeight;

const int MAX_BONES = 100;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 boneTransform[MAX_BONES];

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out float Visibility;

const float density = 0.007f;
const float gradient = 1.5f;

void main()

    mat4 boneTransformation = boneTransform[boneID[0]] * boneWeight[0];
    boneTransformation += boneTransform[boneID[1]] * boneWeight[1];
    boneTransformation += boneTransform[boneID[2]] * boneWeight[2];
    boneTransformation += boneTransform[boneID[3]] * boneWeight[3];


    vec3 usingPosition = (boneTransformation * vec4(position, 1.0)).xyz;
    vec3 usingNormal = (boneTransformation * vec4(normal, 1.0)).xyz;

    vec4 viewPos = view * model * vec4(usingPosition, 1.0);
    gl_Position =  projection * viewPos;
    FragPos = vec3(model * vec4(usingPosition, 1.0f));
    Normal = mat3(transpose(inverse(model))) * usingNormal;
    TexCoords = texCoord;
    float distance = length(viewPos.xyz);
    Visibility = exp(-pow(distance * density, gradient));
    Visibility = clamp(Visibility, 0.0f, 1.0f);

如果我上面的问题,缺少代码或描述模糊,请告诉我,谢谢!

编辑:(1)

另外,我的骨骼信息是这样的(代码获取部分):

for (int i = 0; i < (int)nodeAnim->mNumPositionKeys; i++)

    BoneKey key;
    key.time = nodeAnim->mPositionKeys[i].mTime;
    aiVector3D vec = nodeAnim->mPositionKeys[i].mValue;
    key.vector = glm::vec3(vec.x, vec.y, vec.z);
    currentBone->transformKeys.push_back(key);

有一些变换向量,所以我上面的代码glm::mat4 transform = GetInterpolateTransform(time, bone-&gt;transformKeys);,绝对可以从中得到相同的值。我不确定我制作了一个 nomove 关键帧动画,它提供的变换值是否为真(当然它有 7 个关键帧)。

这样的关键帧内容(在头骨上调试): 7 个不同的关键帧,相同的向量值。

编辑:(2)

如果你想测试我的 dae 文件,我把它放在jsfiddle,来拿吧:)。另一件事,在 Unity 中我的文件可以正常工作,所以我认为可能不是我的本地转换出现问题,似乎问题可能是其他问题,如 parentTransform 或 bone->transform...等?我还添加了所有骨骼的局部变换矩阵,但无法弄清楚为什么 COLLADA 包含这些值用于我的移动动画...

【问题讨论】:

【参考方案1】:

经过大量测试,终于发现问题出在UpdateBone()部分。

在我指出我的问题之前,我需要说一下矩阵乘法的系列让我感到困惑,但是当我找到解决方案时,它只是让我完全(可能只有90%)意识到所有的矩阵在做什么。

问题来自文章Matrix calculations for gpu skinning。我认为答案代码是绝对正确的,不要再考虑应该使用矩阵了。因此,滥用矩阵会严重地将我的看法转移到局部变换矩阵中。回到我的问题部分中的结果图像是当我将本地变换矩阵更改为返回glm::mat4(1.0f) 时的绑定姿势。

所以问题是为什么改变了绑定姿势?我认为问题一定是骨骼空间中的局部变换,但我错了。在我给出答案之前,先看看下面的代码:

void MeshModel::UpdateBone(Bone * bone)

    glm::mat4 parentTransform = bone->getParentTransform();
    bone->nodeTransform = parentTransform
        * bone->transform  // assimp_node->mTransformation
        * bone->localTransform; // T S R matrix

    bone->finalTransform = globalInverse
        * bone->nodeTransform 
        * bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix

    for (int i = 0; i < (int)bone->children.size(); i++) 
        UpdateBone(bone->children[i]);
    

我做出如下改变:

void MeshModel::UpdateBone(Bone * bone)

    glm::mat4 parentTransform = bone->getParentTransform();
    if (boneName == "Scene" || boneName == "Armature")
    
        bone->nodeTransform = parentTransform
            * bone->transform // when isn't bone node, using assimp_node->mTransformation
            * bone->localTransform;  //this is your T * R matrix
    
    else
    
        bone->nodeTransform = parentTransform // This retrieve the transformation one level above in the tree
            * bone->localTransform;  //this is your T * R matrix
    

    bone->finalTransform = globalInverse // scene->mRootNode->mTransformation 
        * bone->nodeTransform  //defined above
        * bone->inverseBindPoseMatrix;  // ai_mesh->mBones[i]->mOffsetMatrix

    for (int i = 0; i < (int)bone->children.size(); i++) 
        UpdateBone(bone->children[i]);
    
   

我不知道assimp_node-&gt;mTransformation 之前给了我什么,只有 assimp 文档中的“相对于节点父级的转换”的描述。对于一些测试,我发现mTransformation 是绑定姿势矩阵,如果我在骨骼节点上使用它们,则当前节点相对于父节点。让我给一张在头骨上捕获矩阵的图片。

左边部分是transform,它是从assimp_node-&gt;mTransformation获取的。右边部分是我的unmove动画的localTransform,它是由nodeAnim-&gt;mPositionKeysnodeAnim-&gt;mRotationKeys中的键计算得出的和nodeAnim-&gt;mScalingKeys

回顾我所做的,我做了一个绑定姿势变换两次,所以我的问题部分中的图像看起来只是单独的,但不是意大利面条:)

最后,让我展示一下我在 unmove 动画测试和正确的动画结果之前所做的事情。

(对于大家,如果我的概念有错误,请指出我!谢谢。)

【讨论】:

那个皮肤的腰带上有精灵球吗?

以上是关于Assimp 动画骨骼变换的主要内容,如果未能解决你的问题,请参考以下文章

使用 ASSIMP 的骨骼动画

如何使用 assimp 在 C++ 中旋转蒙皮模型的骨骼?

Skeleton with Assimp 骨骼动画解析

DirectX 12 中 Assimp 的骨骼动画错误

CSharpGL(50)使用Assimp加载骨骼动画

一步步学OpenGL(38) -《Assimp库实现骨骼蒙皮动画》