跳到主要内容
3D Playground
Three.js 核心第 7 / 13 节

动画:缓动与关键帧

进阶WebGL

两种动画思路

代码驱动:每帧根据时间计算属性值,例如 ball.position.x = f(t)。灵活、适合程序化运动。关键帧驱动:只描述几个时刻的值,中间由引擎插值,适合设计好的动作,也是 glTF 模型动画的格式。本示例右边的小球是前者,左边的方块是后者。

时间从哪来

THREE.Timer 替代了已废弃的 Clock。每帧调用 timer.update(time) 后:

  • timer.getDelta():距上一帧的秒数,用于“每秒移动多少”的增量运动;
  • timer.getElapsed():从开始到现在的总秒数,用于周期运动。

用时间而不是帧数做动画,在 60Hz 和 144Hz 的屏幕上速度才一致。

缓动(Easing)

先把时间归一化成 0–1 的进度 t,再用缓动函数改变它的“节奏”,最后用 lerp(start, end, e) 得到位置:

js
const e = easeOutBack(t);
ball.position.x = THREE.MathUtils.lerp(x0, x1, e);

linear 匀速;easeInOutCubic 慢起慢停;easeOutBack 先冲过头再回来;elastic 像弹簧;bounce 像落地弹跳。五条轨道并排,同一时刻出发、同一时刻到达,只是过程不同。

关键帧系统的四层结构

  1. KeyframeTrack:一个属性路径 + 时间数组 + 值数组。VectorKeyframeTrack(".position", times, values),值数组是扁平的(每个关键帧 3 个数)。旋转用 QuaternionKeyframeTrack,颜色用 ColorKeyframeTrack,路径还可以深入子属性,如 ".material.color"。
  2. AnimationClip:把多条轨道打包成一个有名字、有时长的片段。
  3. AnimationMixer:绑定到一个根对象,负责按名字找到属性并写入插值结果。
  4. AnimationAction:mixer.clipAction(clip) 返回的播放控制器,可以 play()、暂停、设置循环模式 setLoop、调速,或用 crossFadeTo 在两个动作之间平滑过渡。

每帧必须调用 mixer.update(delta),否则动画不会前进。

四元数插值总是走最短路径,所以一整圈旋转要至少拆成三段,每段小于 180°。

核心 API

THREE.TimerTHREE.MathUtils.lerpTHREE.VectorKeyframeTrackTHREE.QuaternionKeyframeTrackTHREE.ColorKeyframeTrackTHREE.AnimationClipTHREE.AnimationMixerAnimationAction.crossFadeToTHREE.LoopPingPong

动手试试

  1. 1自己写一个 easeInQuad:t => t * t,加入 easings 对象,看看新轨道。
  2. 2把 timeScale 调成负数,让关键帧动画倒着播放。
  3. 3给 hop 片段加一条 NumberKeyframeTrack(".material.emissiveIntensity", ...),让方块落地时闪光。
  4. 4把 loop 设为 once,观察 clampWhenFinished 如何让方块停在最后一帧。
运行中