形状补间是 Animate 里最容易失控的功能:两个看起来差不多的形状,中间几帧却会翻卷、自交、撕成两半,然后再莫名其妙地合回去。加几个形状提示有时管用,有时把情况变得更糟。要稳定地用它,得先知道补间器在中间帧到底在算什么——它算的不是”形状”,而是顶点之间的对应关系,而这个对应关系本身是有歧义的。
形状补间插的是轮廓,不是对象
动作补间(Motion Tween)作用在一个对象上:一个元件实例,插值它的变换矩阵和色彩效果。对象自始至终是同一个,所以不存在”谁对应谁”的问题。
形状补间不一样。它作用在未组合的原始矢量上——填充和描边。起始帧是一组轮廓,结束帧是另一组轮廓,两组轮廓的顶点数量、顺序、甚至数量本身都可能完全不同。补间器必须先决定:起始轮廓上的哪一个点,在结束轮廓上变成哪一个点。这一步没有唯一正确答案。
这也是为什么把元件或组放进形状补间会失败——它们不是原始矢量。Animate 要求先分离(Break Apart)成形状,补间器才有轮廓可以匹配。
歧义从哪里来
设起始轮廓是一个闭合多边形,重采样成 N 个点 A = [a_0, a_1, ..., a_{N-1}],结束轮廓同样重采样成 N 个点 B = [b_0, b_1, ..., b_{N-1}]。中间帧只是线性插值:
p_i(t) = (1 - t) · a_i + t · b_{(i + k) mod N}
关键在那个 k——起点偏移。闭合轮廓没有”第一个点”这种客观概念,把 B 的起点旋转一格,得到的中间帧就完全不同。N 个点就有 N 种候选对应,再加上遍历方向(顺时针还是逆时针)又翻一倍,一共 2N 种。
补间器会挑一个代价最小的。常用的代价函数就是对应点距离平方和:
cost(k) = Σ_i ‖ a_i - b_{(i + k) mod N} ‖²
选 k* = argmin_k cost(k)
这个启发式在两个形状”大致同姿态”时很准。一旦起止形状差别大,或者存在多个几乎等价的极小值,它就可能选中一个几何上合法、视觉上荒谬的对应——中间帧于是开始翻卷。所谓”爆炸”,本质是选错了 k,或者更糟,是形状数量对不上导致的拓扑跳变。
拓扑变化是没法靠插值解决的
还有一类失败和 k 无关:起始帧 1 个闭合轮廓,结束帧 2 个。或者起始帧实心,结束帧带一个洞(两条轮廓,外圈加内圈)。
线性插值无法表达”一条轮廓分裂成两条”。补间器只能硬凑:把某段轮廓压缩到零长度,或者把两个形状强行配对。结果就是中间帧出现细丝、尖刺,或者某个部分突然从另一个位置长出来。
判断方法很简单:数一数起止帧各有几条独立闭合轮廓(含洞)。数量不等,就不要指望单个形状补间能做好,应该拆成多段补间,在中间插入一个过渡形状,让每一段内部的轮廓数保持一致。
形状提示是在给 k 加约束
形状提示(Shape Hint)的作用,正是把上面那个自动选出来的对应关系,改成人工指定。放一对提示 a,就等于告诉补间器:”起始轮廓上这个点,必须对应结束轮廓上那个点。”这把搜索空间从 2N 种缩小到满足约束的子集。
由此可以推出几条使用规则,它们不是玄学,而是约束求解的直接后果:
- 必须成对且都吸附在轮廓上。没吸附的提示不构成约束,等于没放。Animate 用颜色区分是否生效(起始帧上生效后变黄、结束帧上变绿,具体配色随版本变化)。
- 顺序必须一致。提示在起止轮廓上的环绕顺序要相同。如果起始帧上按顺时针是 a→b→c,结束帧上却是 a→c→b,约束之间互相矛盾,求解结果通常比不加提示更差。
- 先加少量,分布均匀。三四个均匀分布的提示,比十几个挤在一处更有效——后者只约束了轮廓的一小段,剩下的部分照样自由翻卷。
- 提示解决不了拓扑问题。轮廓数量不等时,再多提示也没用,因为约束的前提是两边都存在可对应的点。
同一个问题在现代工具链里换了名字
SVG 路径动画会撞上完全相同的数学。CSS 和 SMIL 的路径插值要求首尾路径的命令数量和类型完全一致:M L L Z 只能补到 M L L Z,补不到 M L L L Z。浏览器不做对应关系搜索,直接拒绝。
形变库(flubber 之类)做的事,就是把 Animate 内部那套流程显式化:
1. 把两条路径都重采样成相同点数 N(沿弧长均匀取样)
2. 遍历起点偏移 k,计算 cost(k)
3. 取 argmin,必要时再试反向遍历
4. 输出一对点数相同、起点已对齐的路径
5. 逐点线性插值
拓扑不同时,这类库的做法也和拆分补间同构:把一条路径拆成多条,或者插入零长度的退化段来”制造”缺失的轮廓。换句话说,形状提示和现代路径形变库解决的是同一个对应问题,只是一个靠人工标注,一个靠采样加搜索。
知道这一点有个实际好处:如果一个形变在 Animate 里怎么调都不稳定,那它导出成 SVG 路径动画通常也不会稳定,因为难点在形状本身的对应歧义,不在工具。
什么时候该换成网格变形
形状补间适合轮廓本身要改变的情况:水滴分离、烟雾扩散、图标之间的形变。它不适合形状不变、只是被推挤的情况——比如角色脸颊的挤压拉伸、布料摆动、手臂弯曲。
后者应该用网格变形:把图形绑定到一张三角网格上,动画的是网格顶点位置,轮廓的拓扑自始至终不变,因此不存在对应歧义,也就不会爆炸。Animate 的骨骼工具、各类 2D 骨骼运行时的 FFD,以及绘图软件里的液化/操控变形,走的都是这条路。
一个可操作的判据:如果你能在起止两个形状之间指出”同一个点跑到了哪里”,说明拓扑一致、只是被移动了,那就用网格变形;如果连你自己都说不清某个尖角在结束形状里对应哪里,那补间器更说不清,形状提示也只是在替你猜。
验证方法
想确认上面关于 k 的说法,可以做一个最小实验:画一个正方形,补间到一个旋转 45 度的同尺寸正方形。因为正方形有四重对称,cost(k) 存在多个接近的极小值,你会看到补间选择在不同版本或不同顶点数下并不稳定——有时走最短路径旋转,有时整个翻转。
接着在四个角各放一对形状提示,强制指定对应关系,中间帧立刻变成干净的旋转。这个实验能在一分钟内做完,而且它同时演示了歧义的来源和提示的作用机制。