约 18 分钟技术学习游戏引擎

Games104学习笔记(9)

GAMES104:游戏引擎的动画技术基础(上)

第 8 讲(上)|主题:Animation System — Basics of Animation Technology
视频时长:约 1 小时 14 分 25 秒
说明:本文根据本地视频课件与讲解脉络整理,不是逐字稿。

一句话理解

游戏动画的核心任务,是把随时间变化的姿态稳定、可控且高效地施加到角色或物体上。对于 3D 角色,最常用的方案是用具有层级关系的骨架描述姿态,用蒙皮权重把骨架运动传递给网格顶点;而为了可靠地表示和组合旋转,引擎通常会从直观但有局限的欧拉角,转向四元数等表示。

本讲学习目标

  • 了解影视与游戏中常见的动画生成方式,以及实时游戏动画面对的额外约束。
  • 区分序列帧、Live2D、刚体层级、逐顶点、骨骼蒙皮与物理动画的基本思路。
  • 理解骨架、关节、骨骼、Bind Pose、Pose、蒙皮权重与动画数据之间的关系。
  • 掌握局部空间、模型空间、世界空间在骨骼动画中的职责。
  • 理解欧拉角的旋转顺序与万向节死锁问题。
  • 建立四元数用于表示、组合和施加三维旋转的基础直觉。

1. 为什么游戏动画不是“播放一段动作”

人对运动变化非常敏感。从早期视觉暂留装置,到手绘动画、定格动画、动作捕捉,动画技术一直在解决同一个问题:如何让静态画面在连续播放时形成可信的运动。

电影可以离线完成每一帧的制作与渲染;游戏则必须在运行时根据玩家输入和世界状态生成结果。因此,游戏动画除了“好看”,还要处理以下问题:

  • 交互性:角色随时可能转向、停止、被击中、切换武器或改变目标。
  • 实时性:动画系统必须在有限帧预算内完成采样、混合、姿态计算与蒙皮。
  • 复杂状态组合:移动、瞄准、上半身攻击、表情和受击可能同时存在。
  • 环境响应:脚部要接触地面,手要握住物体,身体可能受碰撞或物理影响。
  • 观感连续性:状态切换不能突兀,动作节奏和重心需要维持可信。

因此,动画系统应被理解为一个把“输入状态、动作资产与角色结构”转换为“当前帧姿态”的运行时系统,而不仅是播放器。

2. 动画技术的整体分类

课件中的基本动画技术可按驱动对象分为二维、三维和物理驱动三类。

类型 主要被驱动的数据 典型特点 常见用途
2D 序列帧 图片帧 直接、易理解,但帧数增加会占用资源 像素角色、特效、UI 动画
2D 变形动画 图片网格与控制点 用少量图像产生连续变形 Live2D 角色、立绘
刚体层级动画 独立刚体部件的层级变换 部件之间没有连续形变 机械、积木式角色、早期角色
逐顶点动画 每帧顶点位置 表达自由,但数据量大 特定变形、布料或缓存动画
骨骼蒙皮动画 骨架变换与顶点权重 角色动画的主流方案 人形、动物与大多数可动角色
物理动画 物理约束和动力学状态 响应真实交互,但可控性更难 布料、布娃娃、碰撞反应

这些方案不是互斥的。实际角色常以骨骼动画为主,在局部叠加物理、IK、表情或程序化修正。

3. 二维动画:帧序列与图像变形

3.1 Sprite Animation

Sprite Animation 将动画的每个状态保存为一张或多张图像,按既定顺序播放。精灵图通常把多个帧打包在同一张纹理中,以减少资源切换。

它的基本模型很直接:

当前显示帧由播放时间、帧率与循环规则共同决定。

优点是结果可完全由美术控制,风格稳定;代价是每个新姿态都需要额外图像数据。若动作持续时间更长、方向更多或分辨率更高,资源和制作量会快速增长。

循环播放需要保证首尾姿态与节奏连贯,否则在切换回第一帧时会出现跳变。序列帧适合离散、可预先绘制的动作,但不擅长根据运行时输入连续改变姿态。

3.2 Live2D:以控制点驱动二维变形

Live2D 的思路不是为每一帧都准备一张完整图片,而是将二维图像拆分为可变形部分,并通过网格控制点改变局部形状。

可以把它理解为:

  1. 为头发、脸部、衣物等图层建立可变形网格。
  2. 用参数或控制点描述转头、眨眼、呼吸等变化。
  3. 对网格顶点做插值与变换,让原始图像产生连续形变。

这降低了重复绘制大量中间帧的需求,也使少数参数能够驱动多种视觉变化。但二维图像中原本不可见的部分并不会自动出现,较大视角变化仍受原始素材与分层方式限制。

4. 三维动画的几种思路

4.1 刚体层级动画

刚体层级动画将角色拆成彼此独立的硬部件,再用父子层级连接。例如上臂带动前臂,前臂带动手部。每个部件只做刚体变换,不发生自身弯曲。

其优势是结构直观、计算简单;缺点是连接处容易显得割裂。对人体、布料等需要连续形变的对象,仅靠独立刚体部件通常不够自然。

4.2 逐顶点动画

逐顶点动画直接记录或计算网格顶点在各时刻的位置。它能表达非常自由的形变,例如旗帜起伏、液体扰动或预烘焙的复杂模拟。

问题在于顶点数量通常远多于骨骼数量。若每一帧都保存或传输完整顶点位置,内存、带宽和插值成本都会显著增加。它适合特定效果,不是通用角色动作的首选表示。

4.3 骨骼蒙皮动画

骨骼蒙皮动画将“控制结构”和“显示网格”分离:

  • 骨架提供少量具有父子关系的关节。
  • 动画数据描述关节随时间变化的局部变换。
  • 网格顶点绑定到一个或多个关节,并记录权重。
  • 当前骨架姿态通过权重影响每个顶点,形成连续变形。

它用相对较少的关节变换控制大量顶点,在数据规模、动作复用和表现力之间取得了很好的平衡,因此成为角色动画的主流基础。

4.4 物理动画

物理驱动动画依据质量、碰撞、约束、速度和外力计算结果。布娃娃(Ragdoll)能让肢体在受力后自然摆动;布料也可以由物理或近似物理方法生成。

物理的优势是对环境和外力反应自然,挑战则是可控性:纯物理结果未必符合角色意图、镜头需求或游戏手感。工程中常把物理作为骨骼动作的补充,而不是完全替代动画资产。

5. 骨骼蒙皮动画的组成

5.1 从网格到可动角色

将一个静态网格变为可动角色,通常需要以下几类数据:

  1. Mesh:角色表面,由顶点、三角形、法线、UV 等组成。
  2. Skeleton:用于控制姿态的关节层级。
  3. Bind Pose:网格绑定骨架时的参考姿态。
  4. Skinning Weights:每个顶点受哪些关节影响,以及各自影响比例。
  5. Animation:随时间变化的关节局部平移、旋转和缩放数据。

动画系统每帧先求出当前骨架姿态,再把姿态应用于网格。这里的关键是:网格顶点不需要独立保存每一帧的位置;它们由较少的关节变换间接控制。

5.2 Joint、Bone 与层级

Joint(关节) 是骨架中的变换节点,通常保存局部平移、旋转和缩放。Bone(骨骼) 常用来指两个关节之间的连接或其可视化表示。程序实现中,真正参与层级变换的通常是 Joint。

骨架是一棵树:

  • 根关节定义整个角色的基础参考。
  • 子关节的变换相对于父关节定义。
  • 父关节变化会传递给所有后代。

例如,旋转肩关节会带动肘、腕和手指;这正是层级表示比逐部件独立存储更紧凑、更符合身体结构的原因。

5.3 人形与非人形骨架

人形骨架的关节数量取决于精度要求:基础角色可只保留躯干和四肢的主要节点,高质量角色还会加入手指、面部和辅助关节。动物或怪物并不一定遵循人形结构,但仍可用树状骨架表示躯干、四肢、尾巴、翅膀等控制关系。

骨架设计首先服务于动作需求,而不是追求关节越多越好:

  • 关节过少会限制弯曲和形变质量。
  • 关节过多会增加动画制作、运行时计算和蒙皮调试成本。
  • 关键是让关节位置与角色主要转动、拉伸和受力区域相匹配。

5.4 Bind Pose 与当前 Pose

Bind Pose 是网格与骨架建立绑定关系时的参考姿态,常见为 T Pose (现在多用A Pose) 或其他便于蒙皮的中性姿态。它回答“顶点原本相对于骨架在哪里”。

Pose 则是某一时刻所有关节的变换集合。走路、挥手、下蹲,本质上都是不同的 Pose;动画播放是在时间轴上采样一系列 Pose,并在相邻状态之间平滑过渡。

可以把它们区分为:

概念 描述的问题
Bind Pose 网格在绑定参考状态下与骨架的关系
Current Pose 当前帧关节实际处于什么姿态
Skinning 如何将当前姿态相对 Bind Pose 的变化传递给顶点

5.5 蒙皮权重

一个顶点往往由多个关节共同影响。例如手肘附近的顶点会同时受到上臂与前臂影响。权重 (w_i) 代表第 (i) 个关节对该顶点的影响比例,通常满足:

$\sum_i w_i = 1$

线性混合蒙皮(Linear Blend Skinning, LBS)的直观形式为:

$v’ = \sum_i w_i M_i v$

其中 (v) 是绑定姿态下的顶点,(M_i) 是当前骨骼相对绑定姿态的变换,(v’) 是变形后的顶点位置。

这种方法高效且易于 GPU 并行,但在关节大幅扭转时可能出现体积塌陷或“糖纸包装”式伪影。这里需要先建立一个正确的基础认识:蒙皮不是把顶点硬塞到某根骨头上,而是按权重混合多个关节的相对运动。

5.6 绑定到角色之外的物体

骨架也可以驱动武器、装备和其他附属物。例如把剑绑定到手部关节,就能使它继承手部的世界变换。对于马匹、骑手或多部件机械体,也可以通过明确的绑定点和层级关系传递运动。

这类绑定的本质是复用已有的坐标变换链,而不是为每个附属物单独制作完整动画。

6. 坐标空间:动画计算为何离不开“相对谁”

骨骼动画中的变换必须说明所在空间。课件强调了局部空间、模型空间与世界空间的差别。

6.1 Local Space

关节在局部空间中的变换相对于父关节定义。比如手腕的局部旋转是“相对前臂旋转多少”,而不是直接相对世界坐标旋转。

局部空间让动画数据具有可组合性:如果角色整体移动,或肩膀朝向改变,手腕仍可保留自己的局部动作。

6.2 Model Space 与 World Space

  • Model Space:相对于角色模型根节点的空间。骨架的层级变换累积后,通常可得到各关节在模型空间中的位置和朝向。
  • World Space:相对于游戏场景世界原点的空间。角色整体位移、朝向和缩放会把模型空间中的结果进一步转换到世界空间。

对一条父子关节链,概念上可写为:

$M_{\mathrm{global}}(\mathrm{child}) = M_{\mathrm{global}}(\mathrm{parent}),M_{\mathrm{local}}(\mathrm{child})$

根节点的全局变换再与角色的模型/世界变换组合。明确空间是动画调试的重要前提:很多“骨头方向不对”“挂点偏移”的问题,根源都是把不同空间的矩阵或向量混在一起使用。

7. 旋转表示:从欧拉角开始

动画中的关节变换包含平移、旋转和缩放,其中旋转最容易产生表示与插值问题。

7.1 二维旋转

二维中,绕原点旋转角度 θ 可用矩阵表示:

$R(\theta) = \begin{bmatrix}\cos\theta & -\sin\theta \ \sin\theta & \cos\theta\end{bmatrix}$

它将一个二维向量映射到旋转后的方向。二维旋转只有一个平面和一个旋转轴,因此表示相对直接。

7.2 三维中的基础旋转矩阵

三维空间可绕 (x)、(y)、(z) 轴分别旋转,并由对应的旋转矩阵表示。将多个基础旋转相乘,就能得到更复杂的姿态。

欧拉角用三个角度描述这种组合,例如 yaw、pitch、roll。它直观、便于在编辑器中查看和输入,因此很常见;但它的含义依赖于轴顺序和坐标约定。

7.3 旋转顺序不可交换

三维旋转一般不满足交换律:

$R_xR_y \ne R_yR_x$

也就是说,先绕 (x) 轴再绕 (y) 轴,通常与反过来操作得到的结果不同。使用欧拉角时,必须明确旋转顺序,例如 XYZ、ZYX,以及旋转是相对局部轴还是世界轴。

这不是实现细节,而是数据语义的一部分:如果导入器、动画工具和运行时对顺序理解不同,角色姿态会明显错误。

7.4 Gimbal Lock(万向节死锁)

欧拉角的一个经典问题是万向节死锁。当某个中间旋转使两个旋转轴重合时,原本独立的自由度会丢失。此时:

  • 某些方向无法独立旋转。
  • 同一姿态可能对应多组欧拉角。
  • 从一个角度平滑插值到另一个角度,路径可能不符合直觉。

这不表示欧拉角完全不能使用;它仍适合界面输入、简单控制和某些局部场景。问题在于,当系统需要稳定组合、插值和连续表示任意三维姿态时,欧拉角并不是最可靠的内部表示。

8. 四元数:更适合三维姿态的旋转工具

8.1 从复数到四元数

二维旋转可以借助复数理解:单位复数的乘法对应平面内旋转。四元数将这一思路推广到三维,通常写作:

$q = a + bi + cj + dk$

其中 (a) 是实部,((b, c, d)) 是虚部向量部分。四元数满足特定的乘法规则,例如:

$i^2 = j^2 = k^2 = ijk = -1$

对于动画工程,重要的不在于背诵全部代数历史,而在于认识到:单位四元数可以紧凑地表示三维旋转,并能通过乘法完成旋转组合。

8.2 基本运算

对四元数 (q = (w, x, y, z)):

  • 模长:|q| = √(w² + x² + y² + z²)。
  • 共轭:q* = (w, -x, -y, -z)。
  • 单位四元数:模长为 1;旋转计算中通常应保持归一化。
  • 逆:对单位四元数,q⁻¹ = q*。
  • 乘法:用于按顺序组合旋转,且通常也不满足交换律。

如果运算累积导致单位四元数偏离单位长度,应重新归一化;否则后续旋转可能逐渐产生数值误差。

8.3 用四元数旋转向量

将三维向量 (v) 写成纯虚四元数 ((0, v_x, v_y, v_z)),用单位旋转四元数 (q) 作用后得到:

$v’ = qvq^{-1}$

这给出了四元数“如何旋转一个向量”的核心形式。它避免了直接手工拼接多个欧拉角,同时仍对应明确的空间旋转。

8.4 轴角形式

若旋转轴是单位向量 \hat{u} = (u_x, u_y, u_z)),旋转角度为 θ,则对应的单位四元数可写成:

$q = \left(\cos\frac{\theta}{2},,u_x\sin\frac{\theta}{2},,u_y\sin\frac{\theta}{2},,u_z\sin\frac{\theta}{2}\right)$

轴角形式的直觉很清楚:围绕哪根轴旋转,以及旋转多少。四元数将这两个信息编码为四个分量,便于在运行时组合与插值。

8.5 四元数解决了什么,没解决什么

四元数避免了欧拉角的万向节死锁,也更适合作为动画姿态的连续内部表示。但它不意味着动画问题从此消失:

  • 四元数有双重表示:(q) 与 −(q) 表示同一个旋转,插值时需要保证方向一致。
  • 四元数必须保持归一化。
  • 旋转表示正确,不等于动作本身自然;动作混合、脚底接触、约束和物理响应仍需额外系统处理。

9. 关键概念对照

概念 作用 容易混淆之处
Skeleton 关节层级与控制结构 不等同于可见网格
Joint 保存变换的节点 通常是程序中真正计算的层级节点
Bone 两个关节之间的连接/可视化 不必等于单独的数据对象
Bind Pose 绑定时的参考姿态 不等于当前播放姿态
Pose 某一时刻所有关节的变换集合 一段动画包含许多 Pose
Skinning 用关节变换影响网格顶点 不等于只把顶点绑定到单根骨头
Local Transform 相对父关节的变换 不能直接当成世界空间位置
Euler Angle 三个按顺序组合的旋转角 顺序不同,结果不同;可能死锁
Quaternion 表示三维旋转的四元数 旋转用单位四元数,注意归一化与符号

10. 本讲的工程主线

本讲的内容可以收束为一条数据流:

动画时间 → 采样各关节局部变换 → 沿骨架层级累积姿态 → 得到相对 Bind Pose 的关节变换 → 按顶点权重蒙皮 → 输出当前帧角色网格

其中,旋转表示贯穿整条链路。欧拉角足够直观,适合编辑与展示;四元数更适合在运行时做连续旋转、组合和后续插值。理解这条链路后,再学习动画混合、状态机、动画图、IK、重定向和压缩时,才能看清它们分别修改的是“动作来源”“关节姿态”还是“最终网格结果”。

小结

  • 游戏动画需要在交互、实时性、复杂状态组合与视觉连续性之间平衡。
  • 2D 序列帧直接可靠,Live2D 用控制点与网格变形扩展二维表现。
  • 骨骼蒙皮以少量关节控制大量顶点,是通用角色动画的基础方案。
  • 骨架是关节层级;Bind Pose 说明绑定参考,Pose 表示当前时刻的姿态。
  • 局部空间让父子关节关系可组合,模型空间与世界空间负责把结果放入角色和场景。
  • 欧拉角直观但依赖旋转顺序,且可能发生万向节死锁。
  • 单位四元数能够稳定表示和组合三维旋转,是动画系统的重要数学基础。

参考资料