Blending skeletal animations question

Started by
0 comments, last by congard 3 years, 10 months ago

Hi, I'm working on blending skeletal animations in my project. And I finally got confused in my own code...

I have 2 arrays of glm::mat4, which represents lhs and rhs animation's bones. So, I need to blend these 2 arrays of bones in the third one.

In the very beginning I wrote next naive approach:

inline mat4 blendBones(const mat4 &lhs, const mat4 &rhs, const float factor) {
    quat lhsQuat = quat_cast(lhs);
    quat rhsQuat = quat_cast(rhs);
    quat finalQuat = slerp(lhsQuat, rhsQuat, factor);

    vec4 lhsTransform  = lhs[3];
    vec4 rhsTransform = rhs[3];
    vec4 finalTransform = mix(lhsTransform, rhsTransform, factor);

    mat4 result = mat4_cast(finalQuat);
    result[3] = finalTransform;

    return result;
}

[...]

for (uint i = 0; i < lhs.size(); i++)
    bones[i] = blendBones(lhs[i], rhs[i], factor);

And It worked, but with some issues, of course:

Well, I decided that its because I don't take into account parent bone transformation. I updated my algorithm, but it produced very strange results. So… I just removed taking into account parent bone transformation, and… It starts work!

But why? Code:

void AnimationBlender::readNodeHierarchy(const Node &amp;node, const glm::mat4 &amp;parentTransform) {
    auto &amp;name = node.name;
    uint index = m_shape->bonesStorage.getIndex(name);

    mat4 transform = parentTransform;

    if (index != BonesStorage::BoneNotFound) {
        mat4 &amp;boneMatrix = m_shape->bonesStorage[index].boneMatrix;
        mat4 inverseBoneMatrix = inverse(boneMatrix);
        mat4 inverseGlobalInverseTransform = inverse(m_shape->globalInverseTransform);

        mat4 lhsTransform = m_shape->animations[m_lhsAnim].bones[index] * inverseBoneMatrix; // (*)
        mat4 rhsTransform = m_shape->animations[m_rhsAnim].bones[index] * inverseBoneMatrix; // (*)

        transform = blendBones(lhsTransform, rhsTransform, m_factor); // (**)

        bones[index] = transform * boneMatrix; // (***)
    }

    for (const auto &amp; child : node.childs)
        readNodeHierarchy(child, mat4());
}

That's how the bone was calculated:

[…]
nodeTransformation = translation * rotation * scaling;
glm::mat4 globalTransformation = parentTransform * nodeTransformation * node.transformation;

uint index = m_shape->bonesStorage.getIndex(nodeName);
if (index != BonesStorage::BoneNotFound) {
    auto &amp;bone = m_shape->bonesStorage.bones[index];
    auto &amp;dstBone = m_shape->animations[m_animationIndex].bones[index];
    dstBone = m_shape->globalInverseTransform * globalTransformation * bone.boneMatrix;
}

(*), (***): Am I should multiply it on `inverseGlobalInverseTransform` and then on `m_shape->globalInverseTransform`? I.e

mat4 lhsTransform = inverseGlobalInverseTransform * m_shape->animations[m_lhsAnim].bones[index] * inverseBoneMatrix;
mat4 rhsTransform = inverseGlobalInverseTransform * m_shape->animations[m_rhsAnim].bones[index] * inverseBoneMatrix;

transform = blendBones(lhsTransform, rhsTransform, m_factor);

bones[index] = m_shape->globalInverseTransform * transform * boneMatrix;

(**): (most likely the most stupid question) why not multiply by `parentTransform`? I.e

transform = parentPransform * blendBones(lhsTransform, rhsTransform, m_factor);

Most likely I have such stupid questions because I forgot what data I work with since the code that has to be upgraded is quite old...

EDIT: all-in-one function:

mat4 AnimationBlender::blendBones(uint index) const {
    const mat4 &lhs = m_shape->animations[m_lhsAnim].bones[index];
    const mat4 &rhs = m_shape->animations[m_rhsAnim].bones[index];

    const mat4 &boneMatrix = m_shape->bonesStorage[index].boneMatrix;
    mat4 inverseBoneMatrix = inverse(boneMatrix);
    mat4 inverseGlobalInverseTransform = inverse(m_shape->globalInverseTransform);

    // extracting bone transformation
    mat4 lhsTransform = inverseGlobalInverseTransform * lhs * inverseBoneMatrix;
    mat4 rhsTransform = inverseGlobalInverseTransform * rhs * inverseBoneMatrix;

    // blending lhsTransform and rhsTransform
    quat lhsQuat = quat_cast(lhsTransform);
    quat rhsQuat = quat_cast(rhsTransform);
    quat finalQuat = slerp(lhsQuat, rhsQuat, m_factor);

    vec4 lhsTranslate = lhsTransform[3];
    vec4 rhsTranslate = rhsTransform[3];
    vec4 finalTranslate = mix(lhsTranslate, rhsTranslate, m_factor);

    mat4 transform = mat4_cast(finalQuat);
    transform[3] = finalTranslate;

    // forming result
    return m_shape->globalInverseTransform * transform * boneMatrix;
}

This topic is closed to new replies.

Advertisement