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》