2D 动画关键帧插值:线性、Smoothstep、贝塞尔与帧率无关播放
2D 动画关键帧插值:线性、Smoothstep、贝塞尔与帧率无关播放
站内搜索
直接问 AI

2D 动画关键帧插值:线性、Smoothstep、贝塞尔与帧率无关播放

两个关键帧只定义“起点”和“终点”,并没有定义中间每一帧怎样运动。角色看起来机械、漂浮或突然停住,通常不是骨骼角度错了,而是时间插值函数没有表达重量、预备动作和减速。

一、把时间归一化

关键帧发生在 t0t1,当前时间为 t。先计算归一化进度:

u = clamp((t - t0) / (t1 - t0), 0, 1)

位置、角度或缩放都可以写成:

x(t) = x0 + (x1 - x0) f(u)

f(u) 决定节奏。它必须至少满足 f(0)=0f(1)=1;是否需要零速度、超调或弹性,则由动作设计决定。

二、线性插值为什么显得机械

线性插值 f(u)=u 的导数恒为 1,所以速度瞬间从 0 跳到常数,再瞬间回到 0。数学位置连续,但速度不连续。镜头匀速平移可能需要它,角色抬手、转头和起跳通常不应该默认线性。

三、Smoothstep 的速度证明

常用缓入缓出函数为:

f(u) = 3u^2 - 2u^3
f'(u) = 6u - 6u^2 = 6u(1-u)

因为 f'(0)=0f'(1)=0,动作在两端自然停住;中点 u=0.5 的归一化速度达到 1.5。这说明缓入缓出并不是整体更慢,而是把两端的时间让给启动和制动,中段必须更快。

四、三次贝塞尔与控制手柄

动画软件的曲线编辑器常使用三次贝塞尔:

B(u) = (1-u)^3 P0
     + 3(1-u)^2 u P1
     + 3(1-u)u^2 P2
     + u^3 P3

当曲线描述“时间到进度”的映射时,P1P2 控制起止斜率。手柄越平,起步或停下越慢。回弹曲线允许中间值短暂超过 1,但应谨慎用于关节角,避免穿模。

时间 · 曲线

关键帧、插值与缓动

比较线性、缓入缓出和三次贝塞尔曲线对速度与节奏的影响。

五、帧率无关的播放

不要假设每帧固定经过 16.67 ms。浏览器、游戏引擎或导出器在负载变化时会丢帧。应根据真实时间差累计:

elapsed += currentTime - previousTime
u = elapsed / duration
value = lerp(start, end, easing(u))

这样 60 Hz、120 Hz 和偶尔丢帧的设备在相同真实时间点仍会到达相同姿态。固定步长更适合物理模拟;视觉插值可以在渲染时根据时间采样。

六、多个通道如何保持协调

位置、旋转、缩放和透明度不一定共享一条曲线。起跳时身体位置可以先慢后快,手臂预备动作可以提前,阴影缩放可以稍微滞后。真正自然的动作常由相位差构成,而不是所有通道同时从 0 走到 1。

但父子骨骼的时间采样必须来自同一时间轴。先求每根骨骼当前局部值,再按层级执行正向运动学,最后用蒙皮权重变形顶点。

七、动画审查清单

  • 关键动作是否在明确帧点到达,而不是凭感觉拖动曲线?
  • 速度是否在需要停住的位置趋近 0?
  • 回弹是否服务于材质和重量,而不是所有动作都套同一预设?
  • 低帧率下是否仍在相同时间完成动作?
  • 父子骨骼、遮罩和附件是否共享时间基准?

八、用采样表检查曲线

把缓动曲线在固定进度上采样,可以快速发现端点错误、非单调和意外超调。线性与 smoothstep 的对比如下:

u线性 f(u)Smoothstep f(u)Smoothstep f'(u)解释
0000从静止开始
0.250.250.156251.125位置落后,但速度正在上升
0.50.50.51.5通过中点时速度最大
0.750.750.843751.125位置领先,但速度正在下降
1110在终点停止

自定义贝塞尔或回弹函数至少要检查 f(0)f(1)、是否单调,以及允许的最小/最大值。位置通道通常不希望出现负进度;缩放和关节角允许超调时,也应给出明确范围,避免曲线编辑器中的一个手柄把角色翻转或穿模。

两段动画接在一起时,smoothstep 还不够平滑

前面证明了 smoothstep 的一阶导在两端为零,所以动作会自然停住。但如果两段动画要连续播放——比如角色抬手之后紧接着挥手——只保证一阶导为零是不够的。

看它的二阶导:

f(u)   = 3u^2 - 2u^3
f'(u)  = 6u - 6u^2
f''(u) = 6 - 12u

f''(0) = 6      f''(1) = -6

二阶导在两端不为零,而且符号相反。二阶导对应的是加速度,所以在两段动画的接缝处,加速度会从 -6 突变到 +6。速度是连续的(都是 0),加速度不连续。

人眼对加速度突变是敏感的,尤其在物体较大或运动幅度较大时。表现为接缝处一个说不清楚的「顿挫」——动作没有停,但有一下轻微的生硬感。很多人会去调曲线手柄试图消除它,而问题不在手柄。

需要二阶连续时用五次的版本,通常叫 smootherstep:

g(u)    = 6u^5 - 15u^4 + 10u^3
g'(u)   = 30u^4 - 60u^3 + 30u^2 = 30u^2(1-u)^2
g''(u)  = 120u^3 - 180u^2 + 60u

g'(0) = g'(1) = 0        g''(0) = g''(1) = 0

一阶和二阶导在两端都归零,接缝处加速度连续。代价是中点速度从 1.5 升到 1.875——同样的时长里,中段必须走得更快才能补回两端让出的时间。所以 smootherstep 并不是「更平滑所以更好」,它把更多的时间预算挪给了启动和制动,动作的中段会显得更快。

选择依据很直接:单独播放的动作用 smoothstep 就够了;需要和前后段无缝衔接的用 smootherstep。不必全站统一。

「按真实时间采样」会把二拍节奏抹平

前面说过要按真实时间差累计,而不是假设每帧固定 16.67 毫秒。这条对物理和过渡是对的,但用在手绘节奏上会出问题,值得单独说清楚。

传统动画常用「二拍」——24 fps 下每张画面占两帧,实际只有 12 个不同的画面。这不是省工,而是一种风格选择:画面之间的跳变本身构成了动作的清脆感。很多风格化的 2D 作品刻意保留这一点。

如果按真实时间连续采样缓动曲线,得到的是每帧都不同的平滑序列——二拍的节奏被抹平了。动作变得顺滑,但也失去了原本的锐度,看起来「太数字化」。

解决办法是把量化放在时间上,而不是放在数值上:

STEP = 1.0 / 12.0                      # 期望的画面频率
t_quantized = floor(elapsed / STEP) * STEP
u = t_quantized / duration
value = lerp(start, end, easing(u))

这样无论渲染帧率是 60 还是 144,姿态每秒只更新 12 次,而且更新发生在相同的时间点上。和「直接按渲染帧计数」的区别在于:这里的节奏由时间决定,掉帧不会让动作变慢,只会跳过一次更新。

关键是这个量化必须能逐通道开关。角色主体用二拍,镜头推移和渐变通常要保持连续——如果把整个场景一起量化,镜头会看起来在卡顿。这也是为什么节奏应该是通道的属性,而不是播放器的全局设置。

多个关键帧之间的样条会过冲,脚会穿过地面

上面讨论的都是两个关键帧之间。一旦有三个以上的关键帧,情况就变了:常用的做法不是逐段独立缓动,而是拟合一条经过所有关键帧的样条,让整条曲线光滑。这时候会出现一个新问题。

Catmull-Rom 这类插值样条会用相邻关键帧来估计每个点的切线。如果某个关键帧恰好是局部极值——比如脚落地那一帧的 y 坐标是整段里的最低点——估计出来的切线会让曲线在这一点冲过极值再回来

具体表现是:动画师明明把落地帧的 y 设成了地面高度,播放时脚却先陷进地面几个像素再弹回来。检查关键帧的数值全是对的,因为错的是关键帧之间的曲线。

这不是实现 bug,是插值样条的固有性质:它保证经过所有控制点,但不保证不越过它们围成的范围。

修法是使用保单调的三次插值。思路是先按普通方法算出每个点的切线,再逐点检查:如果这个点是局部极值,就把切线强制设为 0;如果相邻两段的斜率符号相同但切线过大,就按比例缩小它。经典的做法是 Fritsch–Carlson 的限制条件。

# 相邻两段的割线斜率
d0 = (y[i]   - y[i-1]) / (x[i]   - x[i-1])
d1 = (y[i+1] - y[i])   / (x[i+1] - x[i])

if d0 * d1 <= 0:
    m[i] = 0.0                      # 局部极值,切线归零
else:
    m[i] = min(abs(m[i]), 3 * abs(d0), 3 * abs(d1)) * sign(d0)

代价是曲线在这些点上不再是二阶连续的,光滑度略有损失。这个取舍在动画里几乎总是值得的:观众注意不到样条在某个点的二阶导有个小拐折,但一定会注意到脚陷进地板。

要注意这个处理只应该用在不允许越界的通道上——位置、透明度、缩放。关节角度通常反而需要过冲,因为那正是回弹感的来源。把单调限制无差别地套到所有通道上,会让整套动画失去弹性。

九、角度插值与帧率测试

角度不能直接把数值差当作最短路径。例如从 350° 到 10°,普通线性插值会倒转 340°,而正确的最短角差是 +20°:

delta = ((target - start + 180) mod 360) - 180
angle = start + delta * f(u)

验收时记录 0、200、400、600、800 ms 五个时间点的姿态,在 60 Hz、120 Hz 和人工跳过每第五帧三种条件下比较。基于真实时间采样的结果应在这些时间点一致;如果结果随帧数变化,代码仍把“帧”误当成“时间”。

十、把四篇原理连成完整管线

完整顺序是:分层资产确定部件和锚点,骨骼矩阵传播姿态,IK 从目标反求角度,蒙皮消除关节裂缝,关键帧曲线控制时间。回到2D 动画原理总目录可以按顺序复查全部演示。

发表回复

向下探索