把角色绑好骨骼、刷完权重之后,会遇到一批用骨骼怎么都做不出来的效果:裙摆末端的抖动、脸颊被挤压时的细微鼓起、角色转身时手臂从身后绕到身前。这些不是权重没刷好,而是线性混合蒙皮这个模型本身表达不了。现代 2D 骨骼运行时(Spine、DragonBones、Live2D 这一类)之所以比”骨骼 + 蒙皮”复杂得多,正是因为它们在蒙皮之外又叠了好几层独立的形变与排序机制。
骨骼能表达什么,不能表达什么
回顾一下 线性混合蒙皮的核心式子:网格顶点 v 的最终位置,是它绑定的若干骨骼变换的加权和。
v' = Σ_i w_i · M_i · B_i⁻¹ · v Σ_i w_i = 1
其中 B_i⁻¹ 是逆绑定矩阵,M_i 是骨骼当前世界矩阵。这个式子的表达能力被死死限制住了:顶点位置只能是骨骼变换的线性组合。骨骼数量有限、每根骨骼只有平移旋转缩放,因此凡是无法由这组变换线性张成的形变,蒙皮都做不出来。
典型做不出来的三类:局部的、非刚性的细节形变(鼓起、褶皱);不改变任何骨骼姿态却要改变外观的变化(比如纯粹的口型切换);以及根本不属于”形变”的东西——绘制顺序。
网格形变把顶点偏移本身做成动画
第一层补充叫网格形变,Spine 里叫 Deform,早期叫 FFD(自由变形)。它的做法很直接:在蒙皮之前,先给绑定姿态下的顶点加一个可动画的偏移量 Δv。
v' = Σ_i w_i · M_i · B_i⁻¹ · (v + Δv(t))
Δv(t) 是逐顶点、逐关键帧存储的位移,和骨骼完全独立。于是同一套骨骼姿态下,你可以额外雕出布料的褶皱、脸颊的挤压、武器的弯折。
要注意偏移是加在绑定空间而不是世界空间的——先偏移,再走蒙皮。这个顺序决定了雕出来的褶皱会随骨骼一起旋转,而不是固定在屏幕某个方向上。实现或导出时把顺序弄反,表现就是”角色一转身,褶皱还朝着原来的方向”。
代价是数据量:Δv 是每顶点每关键帧一组,网格越密、形变关键帧越多,数据增长越快。所以实践中网格只在需要细节形变的部件上加密(脸、布料),刚性部件(武器、护甲)保持低面数甚至不用网格。
绘制顺序是一条独立的动画轨道
角色转身时手臂要从身后移到身前。骨骼可以把手臂移过去,但决定”画在谁前面”的是渲染顺序,它不在任何矩阵里。
现代运行时把绘制顺序做成一条可关键帧的轨道:每个关键帧存一个槽位的排列(permutation)。播放时按当前帧的排列决定渲染次序。它天然是离散的——两个关键帧之间不能插值,因为”排在第 3 位和第 5 位之间”没有意义。
由此得到一条实践规则:绘制顺序的切换应该安排在视觉上被遮挡或运动最快的那一帧。因为切换必然是突变,观众只有在画面稳定时才会注意到”手臂突然跳到前面”。这也是为什么转身动作通常要在中间加一个手臂完全挡住身体、或者身体侧面最窄的过渡帧。
约束是”骨骼之外再算一遍”的机制
约束(Constraint)在骨骼层级求值完之后运行,用来改写某些骨骼的最终变换。常见三类,它们解决的问题各不相同:
IK 约束把一条骨链的末端对齐到目标点,也就是 双骨骼反向运动学那篇里解的问题。运行时版本通常多两个参数:mix 控制 IK 结果与原始 FK 姿态的混合比例,让动画可以在 IK 和 FK 之间平滑过渡;softness 在接近伸直时提前减速,避免膝盖或肘部在极限处”啪”地锁死。
变换约束让一根骨骼部分地复制另一根骨骼的变换:
b.rotation = lerp(b.rotation, source.rotation + offset, mix)
它是做跟随和延迟的标准手段。把 mix 设成 0.3、再给几帧延迟,头发和披风就会滞后于身体运动——这比给每根头发骨骼手动 K 帧省得多,而且节奏天然一致。
路径约束把一串骨骼绑到一条样条上,按弧长参数分布。履带、链条、沿轨道移动的物体用它。实现上要先把样条按弧长重参数化,否则骨骼在曲率大的地方会挤在一起——这和把贝塞尔曲线当作匀速运动轨迹时遇到的问题是同一个。
Live2D 走的是另一条路:参数驱动形变器
Spine 一类工具的核心对象是骨骼;Live2D 的核心对象是参数和形变器,思路差别很大。
模型定义一组参数,例如 ParamAngleX(头部左右转)取值 −30 到 30。美术为参数的若干取值点分别雕出关键形态(keyform):−30 时的网格顶点位置、0 时的、30 时的。运行时给定参数值 p,在相邻两个关键形态之间插值:
p 落在 [p_0, p_1] 之间时
u = (p - p_0) / (p_1 - p_0)
vertex = (1 - u) · vertex@p_0 + u · vertex@p_1
这就是”伪 3D”头部转动的真相:没有任何三维几何,只是美术在几个角度上分别画好、运行时在这些形态之间插值。之所以看起来有立体感,是因为关键形态里已经包含了透视、遮挡关系和五官位置的变化——这些信息是画出来的,不是算出来的。
形变器分两类:旋转形变器(绕枢轴旋转子物体,接近骨骼)和弯曲形变器(一张可变形的网格控制面片,子网格跟着它整体弯曲)。形变器可以嵌套,变换沿层级累积,和元件层级、骨骼层级是同一套矩阵连乘结构。
多个参数同时作用时,形态按参数维度做多线性插值。这也解释了 Live2D 建模的工作量为什么随参数数量指数增长:两个参数各 3 个取值点,理论上需要 9 组关键形态才能完整覆盖。
物理是把参数接到弹簧上
头发、饰品的二次运动一般不手动 K 帧,而是交给物理系统:取一个输入参数(通常是身体或头部角度),驱动一个阻尼弹簧或单摆,把输出写回另一个参数(头发摆动角度)。
a = (k · (target - x) - c · v) / m
v += a · dt
x += v · dt
要点是 dt 必须用真实经过时间,而不是”一帧”。这与 关键帧插值那篇里帧率无关播放的结论一致:一旦掉帧,按帧数积分的弹簧会算出完全不同的振幅。掉帧时还需要对 dt 设上限并分多个子步积分,否则大 dt 会让显式积分发散——表现就是角色卡一下之后头发突然炸开。
常见故障与定位顺序
- 接缝处网格撕裂:两个部件在接缝顶点上的权重不一致。检查同一位置的顶点是否绑定了相同骨骼、权重是否都归一化到 1。
- 形变被骨骼”吃掉”:网格形变加在了世界空间或蒙皮之后。确认顺序是”先加 Δv,再蒙皮”。
- 转身时图层突然跳变:绘制顺序关键帧落在了画面稳定的帧上。把切换移到遮挡最严重的那一帧。
- 关节在伸直时锁死:IK 没有 softness,或目标点距离超出骨链总长。先夹紧目标距离,再调 softness。
- 头发在低帧率下乱飞:物理积分用了帧数而不是真实时间,或者
dt没有上限。
先加哪一层
这些机制不必一次全上。按收益排序,通常是:先把骨骼层级和权重做干净,这是其它一切的基础;接着加绘制顺序轨道,因为转身几乎是必需的;然后用变换约束批量解决跟随和延迟,性价比最高;网格形变留到最后,只加在观众真正会盯着看的部件上——它的数据量和制作时间成本都是最高的。
更基础的骨骼与蒙皮数学,可以回到 2D 动画原理专栏按顺序阅读。