概述 动画状态机
底层动画管道直接供游戏代码使用会有所不便。所以在底层动画管道和角色之间通常会引入一个软件层。此软件层通常实现为动画状态机(animation state machine, ASM)。
1.动画状态(Animation State)
状态机中每个状态代表一个任意复杂的动画片段混合。
- 在扁平加权平均架构中,每个状态代表一组动画片段和一组相对权重。
- 在混合树架构中,每个状态代表一个预先定义的混合树,该混合树可能很复杂也可能仅对应一个全身动画。
数据驱动:
状态和混合树可以是硬编码的,但多数游戏引擎会提供数据驱动(data-driven)的方式定义动画状态。
数据驱动的目标是让用户能创建 、移除、修改、微调动画状态,并能很快地看到修改后的结果。(即快速迭代)
动画状态编辑方式:
- 有些引擎直接把动画状态以简单语法写于文本文件。
- 有些引擎提供图形化编辑器。
无论哪种编辑方式,都必须让使用者能快速地修改和看到修改后的结果。
预定义混合树节点类型:
仅由原子节点构成的混合树很快会变得巨大笨重。为方便起见,许多游戏引擎会预定义一些自定义的复合节点类型。
例子:
Jason:神秘海域引擎的动画状态分为简单状态(一个动画片段)和复杂状态(含有LERP或者加法混合所组成的树)。
Jason:虚幻3的动画系统提供了图形用户界面。UE3的动画混合树有一个AnimTree根节点,此节点有三个输入:动画(animation)、变形(morph)、骨骼控制(skel control)。其中动画输入可以连接至任意复杂的混合树。变形输入用于连接变形目标动画。骨骼控制输入用于各种后期处理例如IK等。
Cre:Unity动画状态机节点可以是单个动画、混合树、子状态机。
Cre:Unity动画状态机的混合树定义了很多混合类型(1D、2D等)。
2.过渡(Transition)
多数引擎提供数据驱动的方式指定如何处理过渡。
过渡种类:
- 跳跃。(旧动画的最终姿势完全匹配新动画的初始姿势)
- 淡入淡出。
- 过渡状态。(过渡状态本身就是一个完整的状态,可以由任意复杂的混合树构成)
过渡参数:
- 来源和目标状态。
- 过渡类型。(跳跃、淡入淡出、过渡状态)
- 持续时间。
- 缓动曲线类型EaseType。
- 过度窗口。
过渡的实现:
一个正方形矩阵可以描述各状态间所有任何可能的过渡,称为过渡矩阵。
Cre:过渡矩阵的实现有很多方法。(一般会使用数据驱动的方式方便修改扩展)
3.状态层
由于一个时刻只能设置一个状态,要实现不同部位的独立操控,解决方案之一就是引入状态层(state layer)概念。
在分层状态机架构中,底层(base-layer)总是产生全身的骨骼姿势,而其上的层可以产生全身、分部骨骼、加法姿势。
Cre:Unity的状态层可以设置Override和Additive两种混合模式。还可以设置Weight、IK、Mask、Sync等。
4.控制参数
通常需要把所有的混合因子、播放速率、以及其他控制参数和谐地结合在一起,并以某种方式暴露给代码供代码操控。
扁平加权平均架构:
在扁平加权平均架构中,每个动画片段都有一个混合权重和播放速率,高层角色代码需要使用名称取得片段,再调整其混合因子。这样的话大部分控制混合的责任都转移给了高层角色控制系统,这种细粒度的方式很复杂繁重,代码难以理解。
混合树架构:
相比扁平加权平均方式,混合树可以更方便地封装复杂的角色动作,但混合树的控制参数深埋在树之内,需要在树中找到合适的节点并控制其参数。有以下几种解决方案。
- 节点搜寻:有些引擎提供搜寻混合节点的方式供高层代码使用,控制代码可以简单地在树中搜寻特定名字的节点。
- 命名变量:有些引擎可以为个别控制参数命名,控制代码便可以用名字查找控制参数并调整其值。
- 控制结构:有些引擎使用简单的数据结构如浮点数组或者Cstruct,以存储整个角色的控制参数。混合树中的节点连接至某些控制参数,链接方式可以用硬编码使用struct成员,或者是用名字或者索引查找参数。
5.约束
约束(constraint)广泛存在于动画系统中。
依附:
几乎所有引擎都支持把物体依附至另一物体上。依附通常是一个父子级关系,这种特别的子关节通常称为依附点(attach point),这些关节经过标记之后可以使动画混合管道忽略它们。有时候通常需要在父关节和子关节之间加入一个偏移(offset)。
JASON:Maya中依附点可当作一般关节或者定位器(locator)建模然而大多数引擎能更方便地定义依附点。
Cre:Unity把骨骼关节映射到Hierarchy中的GameObject,新增的子游戏对象可作为依附点。
跨物体对准:
CRE:使得多个物体在播放一系列物体间互动的动画之前,需要先把所有物体对齐。
- 🔸参考定位器:
为数个动画加入一个共同的参考点,导出这些动画时存储参考定位器的Transform,这些Transform表示为相对每个物体的局部物体空间。之后要播放这几个动画时。动画引擎能找到这几个动画的参考定位器的Transform,然后变换对应的物体,使得这些参考定位器在世界空间重合。
手部IK:
经过依附连接两个物体时,有时依然会出现一些问题。例如右手拿枪,左手扶着枪托时,当用Lerp混合方式使得角色拿武器瞄准不同方向时,左手可能不再和枪托对齐。解决方法之一就是使用IK修正左手的位置。
IK系统的API形式通常是对某个关节链开关IK,再指定目标,IK通常是在底层动画管道中计算的,这种设计令IK计算可以在合适的时机执行–在计算中间局部及全局骨骼姿势之后,但在计算最终矩阵调色盘之前,即后期处理阶段。
有些动画引擎容许预先定义IK链。例如我们可以为左臂右臂左腿右腿各定义一个IK链。
IK通常较少开关,但世界空间的目标位置必须实时更新,因此底层动画管道必须提供更新作用中IK目标点的机制。(Cre:可以自动更新或者手动调用)
CRE:Unity的IK就是预定义的IK链的例子,可以分别开启手、脚、头的IK并设置权重。
Jason:IK适用于关节和目标已经相当接近,仅做出细微修正,如果关节的实际位置和目标位置过远,IK的表现就会不理想。
脚部IK:
角色走路动画有时会出现滑步(foot sliding)现象。最常见的解决方法是动作提取和脚部IK。
- 动作提取:
运动周期动画中,角色的局部空间维持不动,角色随着向前移动远离背后的原点。如果循环播放,角色向前步行完一个周期后会跳回第一帧的位置,这在游戏中是不允许的。
要正确播放,需要消除角色向前的移动,即把骨骼根节点的平移设置为0,会得到原地行走的动画。此时我们需要每帧令角色向前移动合适的距离来适应角色的脚步,可以使用动作提取。动作提取即提取根节点移动数据,把数据应用至角色的局部空间原点,使得局部空间原点随着步行而移动。
(一个周期移动总距离除以周期能计算平均移速,但是角色步行时向前移动速率并非常数。)
我们预先把动画数据存储为一个特别的提取动作通道。
- 脚部IK:
动作提取只适合角色直线移动,对于非直线移动和不平坦地表上的移动,需要使用脚部IK修正滑步。
基本理念是分析动画去找出每只脚全部和地面接触的时间区间。在脚步接触地面时,记下其世界空间位置,之后在该脚仍然贴地的帧里面,用IK去调整该腿的姿势令其依然固定于正确位置。
实际上要实现较好的脚部IK是很有挑战性的,需要很多迭代及微调。而且还有很多动作并不能单靠IK实现。
Jason:需要权衡角色动画的美观和角色的操控性。
其他类型的约束:
- 注视(LookAt):
注视可以使用眼睛注视,也可以加入头或者上半身。
注视约束可以使用IK、程序式关节偏移、加法混合实现。
- 掩体对准(CoverRegistration):
角色在掩体时要和掩护物完美对齐,通常使用参考定位器实现的。
角色进入或者离开掩体时,通常需要动画混合。
- 通行协助:
令角色探索上下左右的障碍物或者通过障碍物。通常的做法是提供自定义动画,并加入参考定位器对准要通过的障碍物。
(END)