GAMES104课程笔记09-Advanced Animation Technology
这个系列是GAMES104-现代游戏引擎:从入门到实践(GAMES 104: Modern Game Engine-Theory and Practice)的同步课程笔记。本课程会介绍现代游戏引擎所涉及的系统架构、技术点以及引擎系统相关的知识。本节课主要介绍动画系统中的一些前沿技术。
在上一节课中我们介绍了游戏引擎中动画系统的基本内容。基于上一讲的内容已经可以实现一些简单的动画了,而现代3A游戏会在这些内容的基础上添加更多的细节来实现更加真实的动画效果。
Animation Blending
基于DCC流程我们可以获得不同的动画资源,然而这些资源往往是相互独立的,在实际游戏中我们还需要将不同类型的动画混合起来以实现更加自然的运动效果。以角色的跑动为例,人体的跑动包含低速的步行以及高速的跑动两个部分的动画。将两个动画混合起来就可以实现自然的变速效果。

要实现动画的混合非常简单,我们只需要对骨骼的姿态进行插值即可。但和上节课介绍的关键帧插值不同,在进行动画混合时不仅需要考虑同一组动画关键帧之间的插值,还要考虑不同组动画不同关键帧的插值。

计算插值权重也比不是很难,我们可以按照当前角色运动的速度来选择两个相邻动作的关键帧。两个动作的权重即为对速度进行线性插值的权重。

在进行动画混合时还要考虑时间线上对齐的问题。要使插值后的动作更加自然,我们需要动画师在动画建模时将每个动画都按照无限循环播放的形式进行设计。同时,两组进行混合的动画最好要保证角色具有相同的肢体运动频率。这样可以保证混合后的动画更加自然,不会出现角色滑行的结果。

Blend Space
2D Blend Space
上面介绍的动画混合技术主要针对的是角色一维运动,我们可以基于同样的思想把角色在平面上的运动动画进行混合,进而获得角色不同方向上的连续动画。

对骨骼动画进行插值的方法也很简单,只要根据角色运动的状态在两个方向上分别进行线性插值即可。

有时动画师会为角色的平面运动建立多个不同的预设clip,在这种情况下直接进行插值是比较困难的。为了解决这种问题我们可以利用Delaunay三角化的方式对平面进行划分,在需要对动作进行插值时首先选择动作所在的三角形,然后再利用重心坐标进行插值即可。

Skeleton Masked Blending
除了平面运动外,有些角色的动作可能只依赖于一部分骨骼。以鼓掌动作为例,角色在鼓掌时只涉及上半身的骨骼运动而与下半身无关,因此我们可以把上半身鼓掌的动作融合到其它下半身动作中。

要进行这样的处理也十分简单,我们也有设置一个mask来标记混合动画时需要考虑角色的哪些骨骼。这样就可以单独录制不同的动作然后根据需要组合出新的动作。


Additive Blending
角色动作另一种常见的情况是动作本身只与关节相对姿态有关,而与绝对姿态无关。以点头动画为例,角色在点头时可以有不同的朝向,但在录制动画时只需要一套统一的动画。要处理这种情况则需要在保存动画时指明这个clip是只依赖于相对位姿的动画,在实际进行动画混合时再根据角色当前的姿态叠加上相对位姿。


使用这种additive blending技术时需要额外注意角色的姿态,尤其要避免角色关节的姿态超过最大值的情况。


Action State Machine (ASM)
在很多时候角色的动作不能直接通过对动画进行插值来获得。以跳跃为例,实际上角色的跳跃动作可以划分为起跳、浮空以及落地三个不同状态。不同的状态之间存在着一定的依赖关系,这会使得我们可能无法通过直接插值的方式来计算角色当前的动作。

实际上我们可以把角色的动作和状态视为图上的节点,这样角色的运动就等价于在不同节点之间进行游走。这种模型称为动作状态机(action state machine, ASM)。

当玩家发出指令后,角色根据指令和预设的状态转移条件来判断是否需要转移到其它的状态。当确定需要发生转移时就可以根据动画师的设计来切换角色的动画。

在切换角色动画时根据切换的方法可以分为smooth transition和frozen transition两种。进行smooth transition时,我们需要使用前面介绍过的动画混合插值方法逐步从一个动画过渡到另一个动画;而需要进行frozen transition时,我们则会停住角色当前的动画然后根据设置直接切换到下一个动画。这两种动画切换形式在游戏工业中都有广泛的应用。

同时,在进行smooth transition时可以根据动画师的需要设计不同的过渡曲线来表达不同的动画切换效果。

目前,动作状态机技术已经广泛应用于各种游戏引擎中。虚幻引擎中就实现了ASM来运行游戏开发者自定义角色的动画状态和切换条件。

同时在游戏业界还结合了多层次的设计将角色身体的不同部位单独设计成状态机,这样可以实现更加丰富的角色肢体动作。

Animation Blend Tree
在ASM的基础上现代游戏引擎还引入了动画树(animation blend tree)的概念。动画树可以理解为一棵表达树,它可以对不同的动画资源按照指定的计算规则进行混合,从而产生新的动画。

以线性插值为例,我们可以把LERP运算看做是一个节点,它将两个输入的动画资源按照比例混合到一起。基于动画树的结构可以在LERP的基础上定义更加复杂的动画混合插值计算。类似地,前面介绍过的additive blending也可以看做是一种特殊的运算节点,通过该节点可以在原始动作上叠加一个新的动作。


使用动画树来描述动画混合的优势在于它可以基于简单的树状结构描述复杂的动画混合过程,比如说layered ASM就可以看做是不同动画资源在相应层级上进行混合的结果。

同时,动画树还可以看做是一种递归的计算结构。我们可以在游戏引擎中将各种不同的动画生成方法都定义为动画树的节点,在一个计算节点中可以包含很多更复杂的计算过程,甚至是小型的动画树。这样的结构形式会为设计师提供更高的设计自由度,从而实现复杂的动画效果。

虚幻引擎中同样实现了动画树的功能。游戏开发者和设计师可以利用大量的计算节点来自定义角色的不同行为。

动画树的一个核心在于使用控制变量(control parameters)来改变输出动画的结果。一般来说控制变量来源于游戏的玩法系统,当控制变量发生改变时动画树会根据自身定义的规则动态地调整动画混合的比例以及使用的动画资源。

还是以虚幻引擎为例,虚幻引擎中使用variable作为动画树的控制变量调整不同动画之间的混合比例。此外,虚幻引擎中还使用了event作为资源切换的信号,当动画树接收到某个event后会调整自身的动画资源实现角色动画状态的改变。


Inverse Kinematics (IK)
我们目前介绍过的动画系统都是之间利用骨骼和关节的运动学公式来驱动角色的动作,这种方式称为前向运动学(forward kinematics, FK)。在很多情况下我们还需要考虑游戏场景对于角色肢体的约束,并且利用这些约束来求出角色合适的骨骼关节姿态,这种动画技术则称为反向运动学(inverse kinematics, IK)。IK在游戏中有着非常丰富的应用场景,比如说角色在崎岖不平的地面上行走时我们根据地面的起伏来调整前进的动画。

Two Bones IK
IK最简单的情况是只考虑角色两块骨骼的运动,然后利用约束来求解关节的姿态。这种问题的解法其实非常简单,我们可以利用骨骼的大小以及场景约束来构造三角形,然后利用几何关系来求解出所需的姿态。

当然在三维空间中我们基于上述方法实际上会得到无穷多组解,因此在实际计算中还要引入一些额外的约束来保证解的位移性。


Multi-Joint IK
在IK问题中更常见的情况是链式的关节,此时我们需要求解出末端到达指定位置时每个关节对应的姿态。

除此之外,在实际求解前还需要考虑解的存在性。很多时候可能并不存在能够满足需求的关节姿态。

另一方面,角色的不同关节往往还具有不同类型的约束。在进行求解时需要考虑这些约束,否则会出现角色动作过于扭曲的状况。

总体来看,在三维空间中求解带约束的IK问题是非常复杂的,目前还没有通用的算法来进行实时求解。在现代游戏引擎中一般是通过一些启发式的算法来进行近似求解。

CCD
CCD(cyclic coordinate decent)是目前游戏引擎中求解IK问题最主流的算法。它的求解过程非常简单:在每次迭代中按照关节顺序对当前关节进行旋转,使得末端关节指向目标位置。

在标准CCD算法的基础上还可以对迭代过程进行优化,比如设置tolerance region控制每次旋转的角度,或是对接近根部关节的旋转施加额外的约束从而保证根部不会出现过大的旋转。


FABRIK
FABRIK(forward and backward reaching inverse kinematics)也是一种经典的IK求解算法。它的思想是在每一轮迭代中进行一次前向计算以及一次反向计算:前向计算时从末端关节向根部关节移动骨骼,而在反向计算时则从根部出发向末端移动关节。通过不断迭代同样可以计算出合适的姿态。

FABRIK可以很容易地考虑关节约束的问题。当关节的转动角度存在约束时只需要把关节转到约束内垂直与目标的方向即可。

Jacobian Matrix
Jacobian矩阵(Jacobian matrix)是一种基于优化来求解IK问题的算法,它在机器人学中有着非常广泛的应用。关于Jacobian矩阵的细节我们会在后面的物理系统中进行详细的介绍。




Other IK Solutions
除了上面介绍的几种经典算法外,目前游戏业界对于IK问题也提出了一些其它方法进行处理。总体来看IK仍然是非常复杂的问题,在近几年得到了人们越来越多的关注。



同时需要注意的是IK也需要整合到动画管线中作为后处理来调整骨骼和关节的姿态。

Facial Animation
表情动画是动画系统中的重要组成部分,从人体的生理学基础来讲表情是由面部肌肉的运动来进行控制的。

而表情动画的难点在于人的表情变化往往只有非常少的肌肉运动,换句话说不同面部表情之间的差异可能是非常细微的。

FACS
得益于电影工业对面部表情的探索,人们发现只需要面部五官进行组合就可以表现出不同的表情。这种表达表情的方式称为FACS(facial action coding system),它一共包含46组基本单元。


同时由于面部的对称性,实际上在制作表情动画时只需要一半左右的单元就可以表达不同的表情。

Key Pose Blending
基于FACS就可以着手制作不同的表情动画了。在表情动画中我们一般是直接使用面部的网格而不是骨骼关节系统来描述表情,不过动画插值的思想和骨骼动画是一致的。

进行表情混合时需要注意直接对面部进行混合往往会得到错误的结果,这是由于人脸在表达不同表情时一般只会用到一小部分面部肌肉,直接对表情进行混合容易造成整个面部发生运动。要解决这个问题也十分简单,只需要存储一个基本表情然后利用前面介绍过的additive blending技术对表情进行叠加即可。


当然目前也有一些使用骨骼来表达面部表情的实践。使用骨骼动画的优势在于它比较适合表达面部比较大的变形,比如说眼球的转动或是嘴巴张开以及游戏中常见的捏脸系统等等。

UV Texture Facial Animation
除了使用网格或是骨骼外很多游戏也会使用二维纹理图像来实现表情动画,这种方法在很多卡通渲染的游戏中有着大量的应用。

目前在学术界还出现了通过直接对人的面部肌肉进行建模然后实现表情动画的方法。尽管这种方法还没有大规模应用在游戏业界,而且它的计算需求也远高于传统方法,但它却可以实现更加逼真的角色表情。

Animation Retargeting
本节课最后介绍了动画重定向(animation retargeting)技术在游戏行业中的应用。在动画制作中动画师往往只会对同一个动作进行一次建模,而在游戏中设计师则希望可把这个动作应用到各种不同的角色中。这种把一组动作从一个角色迁移到另一个角色的技术称为动画重定向,其中已经绑定好动作的模型称为源角色(source character)而需要施加动画的角色称为目标角色(target character)。


动画重定向最直接的做法是把对应骨骼和关节的动作直接复制过去。当然考虑到源角色和目标角色骨骼之间位置的差异,一般需要对关节姿态进行一定的补偿。

考虑到不同角色之间姿态的差异,在进行重定向时可以只施加关节的相对运动。这种做法会得到更加自然以及符合人直觉的动画效果。

综合上面的内容就得到了动画重定向最基本的算法:处理关节旋转时我们考虑关节的相对旋转,处理骨骼的平移时则需要根据目标角色骨骼的实际长度进行补偿,而对于缩放的情况则直接按照比例进行缩放。这样就可以把源角色的动作迁移到目标角色身上。

在实际工程中人们发现基于上面介绍的算法进行重定向时,由于源角色和目标角色本身骨骼的差异强行迁移动作容易造成目标角色悬空的现象。因此还需要对角色的高度再进行一定的补偿。

如果源角色和目标角色的体型相差很多的话还会出现各种诡异的动画。要处理这种情况还需要利用一些IK的方法来进一步修正。

目前介绍的重定向方法都假定了源角色和目标角色的骨骼有相同的拓扑结构,但在很多情况下两个角色的骨骼是没有办法做到一一对应的,此时就需要一些更复杂的算法来传递动画。

比较直接的处理方法是利用骨骼和关节的对应关系来对关节运动进行插值,这样就可以把原始骨骼的动作重定向到新的骨骼上。实际上在Omniverse上就使用了类似的方法来处理动画重定向的问题。


当然动画重定向仍然是一个相对比较复杂的问题,很多时候即使把源角色的动作迁移到目标角色上仍然会出现很多穿模或者相交的问题。在这种情况下一般还需要动画师对迁移后的动画进行一些微调。

动画重定向的技术除了可以应用在骨骼上,实际上还可以应用在表情动画上。当然在迁移的时候由于不同模型尺度的差异,有时会出现穿模等诡异的问题。此时可以对网格施加一些额外的约束来强行获得正确的动画效果。

