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 &node, const glm::mat4 &parentTransform) {
auto &name = node.name;
uint index = m_shape->bonesStorage.getIndex(name);
mat4 transform = parentTransform;
if (index != BonesStorage::BoneNotFound) {
mat4 &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 & 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 &bone = m_shape->bonesStorage.bones[index];
auto &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;
}