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

Games104学习笔记(10)

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

第 8 讲(下)|主题:Animation System — Basics of Animation Technology
视频时长:约 52 分 11 秒
说明:本文根据本地视频课件与讲解脉络整理,不是逐字稿;内容承接第 8 讲(上)。

一句话理解

骨骼动画的运行时本质是一次数据变换链:从动画片段按时间采样各关节的局部姿态,插值得到当前 Pose;再沿骨架层级计算关节的模型空间矩阵;最后以“当前姿态相对 Bind Pose 的变化”构造蒙皮矩阵,并由 GPU 按权重变形网格。为了让资产能在有限内存和带宽下播放,系统还要处理旋转插值、关键帧曲线、量化压缩和导出数据的一致性。

本讲学习目标

  • 说明一个关节 Pose 通常由哪些局部变换组成,以及为什么常用仿射矩阵表示。
  • 理解局部空间姿态、模型空间姿态与前向运动学的关系。
  • 推导单关节与多关节线性混合蒙皮的基本形式。
  • 区分 Skinning Matrix、Skinning Matrix Palette 与角色的整体世界变换。
  • 比较 LERP、NLERP 与 SLERP 在四元数旋转插值上的性质和取舍。
  • 理解动画轨道、关键帧与样条插值如何描述连续动作。
  • 了解浮点量化、四元数量化、视觉误差度量与动画资产导出的工程意义。

1. 从“一个姿态”到可计算的数据

上半讲建立了骨架、Bind Pose、蒙皮和四元数的基本概念;下半讲关注的是:引擎拿到一段动画后,究竟怎样把它变成当前帧的网格顶点。

对于骨架中的一个关节,某时刻的 Pose 通常可写为局部平移、局部旋转和局部缩放:

$\operatorname{JointPose}_j(t) = {T_j(t), R_j(t), S_j(t)}$

这里的“局部”至关重要:除根关节外,子关节的变换是相对父关节定义的。动画片段无需在每一帧存储所有关节的世界坐标,只需保存这种层级化、可组合的局部数据。

课件也展示了面部关节、机械结构等例子,说明骨架关节不只服务于人体肢体;只要某个对象存在可层级控制的姿态,它都可以用类似的数据模型描述。

2. 用仿射矩阵统一关节变换

2.1 平移、旋转与缩放

在齐次坐标中,平移、旋转和缩放可组合为一个 4×4 仿射矩阵。矩阵的确切乘法顺序取决于引擎采用行向量还是列向量、局部轴还是世界轴等约定;常见概念形式为:

$M_{\mathrm{local}} = TRS$

或在另一套向量约定下写成逆序。重点不是机械记住字母排列,而是保证导入、动画计算、渲染着色器和工具链使用同一套约定。

仿射矩阵把“一个关节如何变换空间中的点”统一起来:

$p’ = Mp$

其中位置使用齐次坐标 (w = 1),方向或法线在实际计算时要遵守相应的向量与逆转置规则。对于骨骼动画,关节矩阵最主要的职责是将顶点从一个姿态相关的空间映射到另一个空间。

2.2 为什么不直接存整张矩阵

运行时可以使用矩阵进行组合,但动画资产常将平移、旋转、缩放拆开存储:

  • 平移与缩放是三维向量,适合线性或样条插值。
  • 旋转适合以单位四元数保存和插值。
  • 分量分离后便于压缩、跳过未变化的通道和做动画混合。

因此,TRS 常是动画数据的表达形式,矩阵则是关节层级传播和蒙皮计算中的高效工作形式。

3. Local Pose、Model Pose 与前向运动学

3.1 两类姿态空间

同一帧骨架可以用不同空间描述:

表示 每个关节相对于谁 主要用途
Local Pose 相对父关节 动画片段存储、采样、混合与复用
Model Pose 相对角色根节点 蒙皮矩阵构造、调试骨架整体姿态
World Pose 相对游戏世界 挂点、碰撞、IK 目标、渲染与场景交互

Local Pose 保留了关节结构:手腕动作不必关心角色站在世界的什么位置。Model Pose 则是把父子链逐级累积后的结果。角色再通过自身的世界变换被放到场景中。

3.2 Forward Kinematics

由父节点推导子节点全局变换的过程称为前向运动学(Forward Kinematics, FK)。若 (p) 是父关节、(j) 是子关节:

$M_{\mathrm{model},j} = M_{\mathrm{model},p},M_{\mathrm{local},j}$

根关节以角色模型空间为起点。按照骨架的拓扑顺序遍历一次,就可以得到全部关节的 Model Pose。

这解释了为什么关节层级是动画系统的核心数据结构:父节点发生变化时,后代都会自动继承;动画只需指定局部变化,就能得到整条手臂、脊柱或尾巴的整体姿态。

3.3 为什么动画混合通常在 Local Space 进行

两个动作 Pose 的混合通常在 Local Space 完成,再做 FK。这样做有两个原因:

  • 局部变换的语义稳定,仍然是“手腕相对前臂”“前臂相对上臂”的动作。
  • 如果直接混合已经累积出的模型空间关节,父子约束可能不再自然;之后再反推局部姿态也会增加复杂度。

从工程角度看,动画系统的典型顺序是“Local Pose 采样与混合 → FK → Model Pose”,而不是先把每个片段展开到世界空间再随意插值。

4. 从 Bind Pose 推导 Skinning Matrix

4.1 单关节蒙皮的思路

设顶点 (v) 在 Bind Pose 下处于角色的模型空间,某关节在绑定姿态的模型空间矩阵为 (M_{bind,j}),当前动画时刻的模型空间矩阵为 (M_{pose,j}(t))。

要让顶点跟随关节,不能只直接乘当前关节矩阵;还需要先消除它在 Bind Pose 时已经具有的关节变换。概念步骤是:

  1. 用绑定矩阵的逆,将顶点带到该关节的绑定局部空间。
  2. 用当前关节矩阵,将它带回当前 Pose 下的模型空间。

于是单关节的蒙皮矩阵为:

$K_j(t) = M_{\mathrm{pose},j}(t),M_{\mathrm{bind},j}^{-1}$

顶点变形结果为:

$v’(t) = K_j(t)v$

这个“当前姿态乘以绑定姿态的逆”是蒙皮实现的核心。它表达的不是绝对姿态,而是从绑定参考到当前姿态的相对变化。

4.2 Bind Pose 为何不可省略

若省略 (M_bind,j^{-1}),顶点会把绑定时的关节位移、朝向又额外施加一次,导致网格偏移或扭曲。Bind Pose 因此不只是美术制作时的一张中性动作图,而是运行时将网格与骨架对齐所必需的参考数据。

常见错误包括:

  • 绑定矩阵来自不同骨架版本或不同坐标系。
  • 根节点变换被重复乘入或完全漏掉。
  • 导入时矩阵行列主序、单位或坐标轴约定不一致。
  • 使用了局部关节矩阵,却按模型空间蒙皮公式计算。

这些错误的表象可能都是“模型飞了”或“某几根骨头方向不对”,但根因通常是空间与参考姿态不一致。

5. 多关节影响:线性混合蒙皮

5.1 Weighted Skinning Blend

真实网格的一个顶点通常受多个关节影响。以肘部为例,靠近关节的顶点既应继承上臂的运动,也应平滑过渡到前臂的运动。

线性混合蒙皮(Linear Blend Skinning, LBS)使用加权和:

$v’(t) = \sum_i w_i K_i(t)v$

其中:

  • (K_i(t)) 是第 (i) 个影响关节的 Skinning Matrix。
  • (w_i) 是该关节对顶点的蒙皮权重。
  • 通常要求 Σ_i w_i = 1。

每个顶点还会保存影响它的关节索引。运行时不需要遍历整副骨架,只读取该顶点绑定的少数几个关节及其权重。

5.2 顶点权重是艺术与工程的交界面

权重不仅决定“顶点被谁带动”,还决定关节处的视觉体积和弯曲质量:

  • 权重过硬,关节边界会出现折痕或断层。
  • 权重分布不当,弯曲时会拉扯不该移动的区域。
  • 多关节大角度扭转时,LBS 可能出现体积塌陷或糖纸包装伪影。

所以蒙皮质量既依赖运行时公式,也依赖资产制作中的骨架布局、权重绘制和必要的人工修正。引擎提供的是可计算的变形框架,不能自动替代全部角色美术判断。

5.3 Skinning Matrix Palette

对同一个角色网格而言,当前帧所有关节的 (K_j(t)) 可整理为一组矩阵,即 Skinning Matrix Palette。典型做法是:

  1. CPU 或计算任务采样动画并做 FK,得到当前 Model Pose。
  2. 为每个关节计算 M_pose · M_bind⁻¹。
  3. 将矩阵数组上传到 GPU 的常量/结构化缓冲区或其他可访问存储。
  4. 顶点着色器按顶点关节索引读取对应矩阵,完成加权蒙皮。

Palette 把“每帧每关节算一次”的结果复用给大量顶点,避免重复计算层级变换。

5.4 角色整体变换与调色板的分工

Skinning Matrix 通常负责把顶点从绑定的模型空间变到当前 Pose 的模型空间。角色在场景中的平移、旋转、缩放则由额外的 Model-to-World 矩阵处理:

$v_{\mathrm{world}} = M_{\mathrm{world}},v_{\mathrm{skinned,model}}$

把两者分开有利于实例化与资源复用:同一套动画和网格可被放到不同世界位置,而不必为每个角色复制一份骨骼动画数据。

6. Pose 之间如何连续过渡

动画片段由离散关键姿态构成,但播放时间是连续的。因此系统必须在两个相邻 Pose 之间插值得到任意时刻的局部 TRS 数据。

6.1 平移与缩放的线性插值

对向量 (a) 与 (b),插值参数 (t ∈ [0, 1]),线性插值为:

$\operatorname{Lerp}(a,b,t) = (1-t)a + tb$

平移和缩放常可直接使用 LERP。它计算简单、结果可预测;如果希望运动经过关键帧时更加平滑,可改用样条曲线。

6.2 四元数不能只做普通 LERP

旋转四元数应该保持单位长度。直接对两个单位四元数做 LERP 后,一般会落到单位超球面内部,失去严格的单位旋转性质。因此需要归一化或采用沿球面插值的方法。

6.3 NLERP:先线性插值,再归一化

归一化线性插值(Normalized LERP, NLERP)可写为:

$\operatorname{Nlerp}(q_0,q_1,t) = \operatorname{normalize}((1-t)q_0+tq_1)$

优点:

  • 计算成本低。
  • 实现简单,数值上通常足够稳定。
  • 当两种姿态差异较小时,视觉效果接近球面插值。

局限:

  • 旋转角速度并不恒定。
  • 两个姿态相差较大时,轨迹偏离真正的球面最短路径更明显。

在实时游戏中,NLERP 往往是性能与质量之间有吸引力的折中方案。

6.4 SLERP:沿单位超球面插值

球面线性插值(Spherical LERP, SLERP)在单位四元数所在的超球面上沿弧线插值。它具有更符合几何直觉的性质:在旋转路径上保持恒定角速度。

概念公式为:

$\operatorname{Slerp}(q_0,q_1,t) = \frac{\sin((1-t)\theta)}{\sin\theta}q_0 + \frac{\sin(t\theta)}{\sin\theta}q_1$

其中 θ 是两个单位四元数夹角相关的角度。

相较 NLERP,SLERP 的代价更高,需要三角函数计算;当两四元数非常接近时,sin θ 也会使数值计算不够理想。实际实现常在小角度区间退化为 NLERP 或 LERP 后归一化。

6.5 四元数符号一致性

单位四元数 (q) 与 −(q) 表示同一个空间旋转,但在插值时它们位于超球面的相对两侧。若不先检查点积并统一符号,插值可能沿很长的弧线旋转。

常用规则是:若 dot(q₀, q₁) < 0,先将 (q₁) 取反,再进行 NLERP 或 SLERP。这样通常可选择较短的旋转路径。

7. 动画片段、轨道与关键帧

7.1 Animation Clip

Animation Clip 是一段有长度的动画数据。它将每个关节的属性随时间变化记录为曲线或离散采样值,而不是逐帧保存整个角色网格。

动画片段常把一个关节的通道拆为:

  • Translation Track:平移变化。
  • Rotation Track:旋转变化。
  • Scale Track:缩放变化。

不同通道的采样密度可以不同。例如一个关节可能旋转频繁而几乎不平移;分轨存储能避免为不变数据浪费空间。

7.2 Keyframe

关键帧(Keyframe)记录某个时刻的明确属性值。两个关键帧之间由插值规则补出连续值:

$\operatorname{Value}(t) = \operatorname{Interpolate}(\operatorname{Key}i,\operatorname{Key}{i+1},t)$

关键帧的意义是把“制作人员关心的姿态变化”保留下来,而将中间状态交给曲线或运行时插值生成。它既减少数据量,也允许动作节奏在局部加速、减速或停顿。

7.3 Catmull–Rom Spline

对位置等向量通道,Catmull–Rom 样条可以在相邻关键点之间产生比直线更平滑的路径。它利用前后多个控制点计算当前段的曲线,因此位置和速度的过渡通常更自然。

但平滑曲线并不总是更好:它可能产生过冲,导致对象经过不希望越过的位置。对于脚底接触、武器定位、碰撞边界等约束较强的通道,需要谨慎选择切线、限制曲线或使用更受控的插值。

8. 简化的动画运行时管线

课件给出的运行时流程可概括为:

Clip + Time → 采样与插值 → Local Current Pose → FK → Model Current Pose → Skinning Matrix Palette → GPU Skinning → 变形后的 Mesh Vertices

将它拆开看:

  1. Extract:根据动画片段和播放时间找到相邻关键帧或采样点。
  2. Interpolate:对每个关节的 TRS 通道求当前局部变换。
  3. Forward Kinematics:沿层级组合局部矩阵,得到模型空间关节姿态。
  4. Calculate Skinning Matrix:每个关节与 Bind Pose 的逆组合,形成 Palette。
  5. Skinning:GPU 对每个顶点读取关节索引、权重和矩阵,输出当前帧位置。

这条流程明确了 CPU 与 GPU 的典型分工:前者更适合处理结构化的动画状态和关节层级,后者更适合对海量顶点执行相同的加权变形。具体引擎可以调整任务位置,但数据依赖关系不会消失。

9. 动画压缩:只为看得见的误差付费

动画数据包含大量随时间采样的浮点数。角色关节多、片段长、动作种类多时,未压缩的数据会显著增加包体、内存、加载和缓存压力。压缩的目标不是追求数学上逐位一致,而是在可接受的视觉误差内减少数据量。

9.1 浮点量化

若一个数值只在已知范围 ([min, max]) 内变化,可以将 32 位浮点数线性映射到较少位数的无符号整数:

$q = \operatorname{round}\left(\frac{x-\min}{\max-\min}(2^n-1)\right)$

解码时再映射回原范围。位数 (n) 越少,存储越省,但量化台阶和最大误差越大。平移、缩放等分量适合根据各轨道的实际取值范围单独量化,而非使用全局统一范围。

9.2 四元数量化

单位四元数四个分量满足长度约束,因此并非四个分量都需要以同等精度独立存储。课件展示了利用“省略一个分量并结合符号/索引重建”的思路:存储若干分量与必要标记,由单位长度约束恢复剩余分量。

这种方法把四元数的几何约束转化为压缩机会,但必须保证:

  • 编码与解码使用一致的分量选择和符号规则。
  • 重建后重新归一化或控制数值误差。
  • 压缩前后的姿态误差在目标角色与镜头距离下可接受。

9.3 误差不是平均分配的

不同关节对视觉质量的敏感度不同:面部、手指、手持物和镜头近处的关节,往往比远处躯干或被遮挡部位更容易暴露误差。课件以特写部位与骨架示例说明,动画压缩应考虑关节的重要程度,而不是只追求每个通道相同的数值误差。

一个实用的判断视角是屏幕空间误差:

  • 同样的角度误差,离镜头越远,画面上的偏移越小。
  • 同样的顶点偏移,出现在脸部或手部通常比出现在衣摆更显眼。
  • 误差会沿关节链累积,父关节的微小偏差可能放大到末端。

因此,压缩策略可以为关键关节保留更高精度,为不敏感轨道使用更激进的量化或更稀疏的关键帧。

9.4 In-Place Correction

压缩或重采样后,关节的累计误差可能使原本应稳定接触的位置发生漂移。In-Place Correction 的思路是在重要约束处补偿这种误差,例如保持脚底、手部或特定锚点与预期位置一致。

它体现出一个工程事实:压缩并不是一次性把数值缩短,而是“压缩—测量视觉/功能误差—对关键约束补偿”的过程。

该技术并非完美。

10. 资产制作与导出

10.1 自动蒙皮不等于最终蒙皮

角色制作工具可以自动生成初始权重,但关节弯曲处、衣物、肩膀、髋部等区域常需要手工调整。权重绘制与网格形状调整用于解决:

  • 动作时网格互相穿插或出现明显夹缝。
  • 关节处体积塌陷、拉伸过度或不符合角色轮廓。
  • 动画姿态与建模阶段的静态外观差异过大。

这说明运行时蒙皮公式正确,只是基础条件;最终质量还依赖网格拓扑、骨架放置、权重分布和美术修型的协同。

10.2 导出时必须带走什么

课件以 FBX 等交换格式为例,指出导出并不只是“导出模型”。一个可在引擎中重建动画的资产至少需要匹配的:

  • 网格与顶点属性。
  • 骨架层级、父子关系和 Bind Pose。
  • 顶点关节索引与权重。
  • 动画片段、轨道、关键帧及其时间单位。
  • 坐标系、单位、矩阵/四元数约定等元数据。

只要其中任一部分的约定在导出与导入两侧不同,就可能造成比例异常、旋转错误、蒙皮偏移或动画速度不对。因此,动画导入问题首先应按“数据契约是否一致”排查,而不是只在渲染端寻找错误。

11. 容易混淆的概念

概念 正确理解
Local Pose 与 Model Pose 前者相对父关节,后者是父子变换累积后的结果
Current Joint Matrix 与 Skinning Matrix 当前关节矩阵描述姿态;Skinning Matrix 还包含 Bind Pose 的逆
Bone Matrix Palette 与 World Matrix Palette 处理骨架相对绑定姿态的形变;World Matrix 放置整个角色
LERP 与 NLERP NLERP 是 LERP 后归一化,适合四元数的低成本近似
NLERP 与 SLERP 前者角速度不恒定但便宜;后者路径更精确、恒定角速度但计算更贵
数据误差与视觉误差 相同数值误差在不同关节、距离和镜头下的可见程度不同
自动权重与最终角色质量 自动绑定提供起点,关键区域仍可能需要人工修型与权重调整

小结

  • 一段骨骼动画通常保存关节局部 TRS;运行时将其组合成矩阵并通过 FK 得到模型空间姿态。
  • Skinning Matrix 的核心是 当前关节矩阵 · Bind Pose 关节矩阵的逆,它表达从参考姿态到当前姿态的变化。
  • 多关节顶点通过权重混合多个 Skinning Matrix;矩阵调色板让每个关节结果能被大量顶点复用。
  • 平移和缩放可直接插值,四元数旋转通常使用 NLERP 或 SLERP,并要处理归一化与符号一致性。
  • 动画片段由分轨关键帧组成;样条可增加平滑性,但也可能带来过冲。
  • 动画压缩要围绕视觉重要性和约束设计,而不是让所有关节使用相同精度。
  • 最终动画质量来自运行时算法、资产制作和导入导出约定三者的一致配合。

参考资料