5-动画的混合
在看完GAMES104动画系统那一部分后,我也想写一个简单的动画系统,又要开一个坑了!本文将简要介绍如何实现动画混合,包括:
- 简单线性混合,即让绑定姿态和任一动画剪辑序列帧混合。
- CrossFading过渡混合,即混合两个动画序列间的过渡。
- 附加混合,将一个额外动画序列附加到已经混合好的动画中。
动画混合综述
可以让绑定姿态和任一剪辑序列混合,也能让不同剪辑序列混合,也能将混合限制在角色模型的具体部位。
动画剪辑序列的淡入淡出
这是动画混合的最简单形式,对两个序列同一时间关键帧进行插值混合,其中缩放和平移是LERP,旋转是SLERP。通过插值可以决定两个序列帧节点的混合权重,它决定当前帧采用哪个序列的姿势多一点。
CrossFading
这是两动画序列切换间的过渡混合,混合权重决定当前帧完全采用哪个序列的动画。
附加混合
当上述混合都做完后,可以通过附加混合的方式给混合好的动画额外添加一些动作。我们需要给模型的骨骼的不同部分创建遮罩,以区分哪些部位应该被附加额外动画,哪些部位保持原样。例如给正在奔跑中的角色右胳膊做遮罩,让他边跑步边挥手/扔手雷等。
此外,更复杂的混合可能是胳膊、手和头的各种移动,面部表情、嘴部讲话等,都需要细细打磨。
简单线性混合
就是让绑定姿态的SRT变换和一个动画序列的SRT变换进行线性混合。
首先要拓展Bone类,让我们获取到动画序列当前的SRT变换:
glm::vec3 m_curTranslate;
glm::vec3 m_curScale;
glm::quat m_curOrientation;
接下来给Animator
类添加线性混合参数:
// 动画混合 - 简单线性混合
bool m_enableBlending = true;
float m_blendFactor = 1.0f;
然后就能和绑定姿态的SRT进行线性混合了,由于我用的是Assimp库,绑定姿态的SRT变换已经被计算为矩阵了,需要使用glm::interpolate()
进行矩阵插值或用glm::decompose()
拆分绑定姿态变换再插值。这里推荐后者,因为前者忽略了缩放变换的插值。可能有不对的结果:
// 前者
#define GLM_ENABLE_EXPERIMENTAL
#include "glm/gtx/matrix_interpolation.hpp"
nodeTransform = glm::interpolate(node->transformation, bone->GetLocalTransform(), m_blendFactor);
// 后者
#define GLM_ENABLE_EXPERIMENTAL
#include "glm/gtx/matrix_decompose.hpp"
glm::quat orientation;
glm::vec3 scale;
glm::vec3 translation;
glm::vec3 skew;
glm::vec4 perspective;
if (glm::decompose(node->transformation, scale, orientation, translation, skew, perspective))
{
glm::quat interOrientation = glm::slerp(orientation, bone->GetCurOrientation(), m_blendFactor);
glm::vec3 interTranslate = glm::mix(translation, bone->GetCurTranslate(), m_blendFactor);
glm::vec3 interScale = glm::mix(scale, bone->GetCurScale(), m_blendFactor);
nodeTransform = glm::translate(glm::mat4(1.0f), interTranslate) *
glm::toMat4(interOrientation) *
glm::scale(glm::mat4(1.0f), interScale);
}
结果如下:
参考资料
- 《C++ Game Animation Programming》