关键帧与插值:姿态是"补"出来的
全课骨架:动画 = 每一帧先把骨架的姿态算出来,再把顶点按权重「挂」到骨头上。 「读姿态」那一半是第 1 课的蒙皮;「写姿态」前面给了两条路 —— 第 2 课的 IK 是解出来的、第 3 课的弹簧骨是演出来的。 可真实项目里占比最大的是第三条,也是最朴素的一条:姿态是动画师摆的。他只在少数几个时刻摆好姿势, 中间那几百帧全是补出来的。这一课就讲"补"。 保真度:真机可证(画面与四张表都由本页自己的采样器算出;对照物是三条闭式: 线性插值 p(u) = (1−u)a + u·b、保持段的过冲 b + (b−a)/8、缓动的时间推进率 6u(1−u) —— 读者能拿笔复核)。
本页的原理与数值表照常可读(画面只是把同一个数画出来)。
(脚本启动中…)
定点观察:先点「4.3 换成 Catmull-Rom」→ 看右上那条曲线在"保持"那一段(t = 1…2)鼓起来多高,以及表③ 的「过冲」一列 → 关键帧明明写着 60°,曲线却冲到 67.5° —— 而且它会「在关键帧处折一下」这件事同时消失了(表② 的跳变从 60 变成 0)。 平滑与过冲是同一枚硬币的两面。
本帧的读数
(脚本启动中…)
4.1 段 · 两个姿态之间:先把时间归一化,再线性插值
本段只增加一样东西:时间 t(画面里是两条通道曲线 + 手臂;关键帧成对出现,其余控件与各段一致)。 保真度:真机可证。上一课结束时你已经能"算出一个姿态"了,可关键帧给的是几张照片 —— t = 0 一张、t = 3 一张,中间什么都没有。播放器每一帧要问的是"现在这一刻该是什么姿态", 所以第一件事是把时间归一化:这一帧落在哪两个关键帧之间、走到这一段的多大比例 u。
| u | 闭式 (1−u)·0 + u·60 (°) | 实测 θ₀ (°) | 差 (°) |
|---|
算式:把"这一段里还要走多远"归一化成 u = (t − t₀)/(t₁ − t₀),然后对每一条通道各做一次线性插值
p(u) = (1−u)·a + u·b。拿 u = 0.5 验算:0.5×0 + 0.5×60 = 30.000° ——
表① 那一行逐位对上(差 0,是浮点意义的 0)。
注意 u 与通道无关:手臂有 θ₀、θ₁ 两条通道,可"走到这一段的多大比例"只有一份。
所以真实实现里是先算 u、再逐通道插值(一条时间轴、多条曲线),而不是给每条通道各算一遍时间 ——
后者在通道数一多就会因为浮点路径不同而对不齐,关键帧处出现极小的错位。
4.2 段 · 一串关键帧:逐段补出来,会在关键帧处折一下
本段只增加一样东西:关键帧数(2 → 4)(插值方法仍是线性,其余控件一致)。 保真度:真机可证。一段真实动作不止两个姿势:抬起来、停一下、放下来 —— 至少三四个。 于是"逐段线性插值"就变成一条折线:每一段各自是直的,可接缝处的方向是断的。
| 方法 / 位置 | 左斜率 | 右斜率 | 跳变 |
|---|
算式:斜率用数值微分量(在关键帧两侧各取 t ∓ 5e−4 采样,再除以 2h),
这样"这条曲线顺不顺"就不必用眼睛判了。
线性插值下两侧斜率就是前后两段的斜率:θ₀ 在 t = 1 处左 = (60−0)/1 = 60、右 = (60−60)/1 = 0
⇒ 跳变 60 °/s;t = 2 处对称,跳变也是 60。
★ 这张表里最容易被忽略的一行是最后一行:Catmull-Rom 的 C1 只在关键帧等间距时成立。
它的切线 m = (v[i+1] − v[i−1]) / 2 是按段内参数定义的,换回真实时间轴要除以该段的时长 ——
所以把间距从 1/1/1 改成 1/1/2,t = 2 处就变成左 −30、右 −15,跳变 15 °/s,不再是 0。
4.3 段 · 让折角消失:切线由邻居说了算(代价是过冲)
本段只增加一样东西:插值方法(线性 → Catmull-Rom)(关键帧仍是 4 条,其余控件一致)。 保真度:真机可证。折角的根源是"每段只顾自己":它只知道自己的两端,不知道旁边那两段往哪走。 补法非常直接:让关键帧处的切线由它的左右邻居决定 —— 把相邻两点的差取一半(m = (v₊ − v₋)/2), 塞进 Hermite 基函数。这样每一段的两端斜率就自动与邻居接上。
| 配置 | 关键帧值域 (°) | 曲线极值 (°) | 过冲 (°) | 闭式 (b−a)/8 (°) |
|---|
算式:考虑"抬起来 → 停住 → 放下"里那个保持段(两条关键帧的值相同,记作 b;外侧邻居记作 a)。
这一段的两个切线是 m₀ = (b − a)/2、m₁ = (a − b)/2(把邻居差取一半),代进 Hermite:
p(s) = b + (b−a)/2 · s(1−s),它是开口向下的抛物线,峰值在 s = 0.5,等于 b + (b−a)/8。
拿 θ₀ 验算:a = 0、b = 60 ⇒ 峰值 = 60 + 60/8 = 67.5°,过冲 = 60/8 = 7.5° ——
表③ 那一行逐位对上。θ₁ 同理:a = −40、b = 0 ⇒ 峰值 0 + 40/8 = 5°。
线性插值则永远不离开关键帧值域(过冲恒为 0)—— 它不是更好,它只是"没有形状"。
这条式子解释了那个很常见的疑问:为什么停在关键帧上还会抖一下?
因为关键帧只规定"经过这里",不规定"怎么经过";两侧邻居把切线撑开了,曲线在中间自己鼓起来。
所以"停在 60°"这句话在关键帧数据里根本不存在 —— 想要真的停住,得再加一条关键帧、或者用钳制切线。
4.4 段 · 快慢 ≠ 路径:缓动改的是时间,不是姿态
本段只增加一样东西:时间重映射(缓动开关)(关键帧 4 条、方法仍是 Catmull-Rom,其余控件一致)。 保真度:真机可证。前三段改的都是"走过哪些姿态"。还有一种非常常见、却常被混起来的改动: 姿态一个不改,只改"什么时候走到"。做法是把段内参数 u 先过一遍缓动函数(这里用 smoothstep s = u²(3−2u)), 再拿 s 去采样曲线。
| u | 闭式 6u(1−u) | 实测 dτ/dt | 该点姿态与线性之差 (°) | 是否同一姿态 |
|---|
算式:缓动把段内参数换成 s = ease(u),于是"剪辑时间" τ = tᵢ + s·Δt,
时间推进率 dτ/dt = ds/du。对 smoothstep 求导得 ds/du = 6u(1−u) ——
它在两端为 0(起步与收尾都慢)、在 u = 0.5 处取峰值 1.5。表④ 的「实测」是对页面自己的 τ 做数值微分得到的,
三行分别是 1.125 / 1.500 / 1.125,与闭式逐位一致。
而"姿态"那一列只在 u = 0.5 处为 0:因为 smoothstep 关于中点对称,s(0.5) = 0.5 = u,
两条路恰好在那里重合;其余位置 s ≠ u,走过的姿态自然不同 ——
但"走过的姿态集合"是同一个(都是同一条曲线上的点,只是到达的时刻不同)。这就是"快慢 ≠ 路径"的可验形式。
★ 一处如实的代价:缓动是逐段施加的,所以在每个关键帧上推进率都落到 0 ——
动作会在每个关键帧"刹一下车"。真实工具通常用整条剪辑的时间重映射、或自动切线来避免它;
本页保留逐段缓动,正是为了让你看见这个副作用。
课末:姿态从哪来 —— 三条路都齐了
把这一课与前三课并起来看,骨架那句话的两半都落到实处了:
读姿态(第 1 课蒙皮)、写姿态有三条路 ——
解出来的(第 2 课 IK:给目标,解角度,有可达性、有迭代收敛)、
演出来的(第 3 课弹簧骨:给规则,让姿态自己长出来,只有稳定态)、
补出来的(本课关键帧:给几张照片,中间插值出来)。
三者的选用也互补:手要抓住栏杆、脚要踩住地面 —— 硬要求,用 IK;
头发、尾巴、挂坠 —— 只要"看起来对",用弹簧骨;
角色的主体动作 —— 那是表演,只能由人摆关键帧。真实角色的每一帧常常三条路都用上:
关键帧给主体姿态、IK 修脚部落地、弹簧骨补头发。
如果只记一句话:关键帧只规定"经过哪里",不规定"怎么经过" —— 中间那段全是插值,而插值是有性格的。
本课题到此四课收口(蒙皮 → IK → 弹簧骨 → 关键帧与插值);附录里留了三样下一层的:
切线与钳制(怎样既平滑又不过冲)、姿态混合与状态机(两个动作之间怎么过渡)、以及根运动与脚步对齐。