all lessons / computer_graphics / 13 · animation & motion lesson 13 / 15

Animation & motion — describing change over time动画与运动——描述随时间的变化

Every lesson so far rendered one frozen frame: a fixed camera, fixed geometry, one measurement. But a film or a game is a sequence of frames in which the scene changes. This lesson is about that change. The surprise is how little new machinery we need: we do not re-derive transforms — we reuse the exact T·R·S matrices of lesson 02 and simply let their parameters be functions of time. The whole art is (1) how to describe motion sparsely, with a few authored poses, and (2) how to interpolate between them so the in-betweens look natural rather than robotic. 到目前为止的每一课都渲染了一帧凝固的画面:固定的相机、固定的几何、一次测量。但一部电影或一款游戏是一连串变化着的帧。这一课讲的就是这份变化。令人意外的是我们几乎不需要新机器:我们不重新推导变换——而是复用第 02 课那批一模一样的 T·R·S 矩阵,只是让它们的参数成为时间的函数。整门手艺就在于:(1) 如何稀疏地描述运动——只给几个关键姿态;(2) 如何在它们之间插值,让中间帧看起来自然而不僵硬。

The plan本课计划

Five moves. (1) Add time as a dimension: frames vs simulation time, and the core idea of keyframing — authors set sparse poses, the computer fills the in-betweens, saving memory and buying control. (2) Choose an interpolation curve: linear lerp is cheap but robotic, easing (smoothstep) fixes the velocity jumps, and Catmull–Rom splines pass smoothly through many keys. (3) Handle rotation specially, because it lives on a sphere, not a flat space — Euler angles gimbal-lock, quaternion slerp gives the shortest, constant-speed arc. (4) Animate characters with a bone hierarchy and linear-blend skinning, and meet its candy-wrapper artifact. (5) When hand-authoring every key is impractical, simulate — particles, springs, cloth, rigid bodies — by integrating the physics over small time steps. Then lesson 14 asks what "color" the animated pixels actually are.

五步走。(1) 把时间加成一维:帧 vs 仿真时间,以及关键帧这个核心思想——作者只给稀疏姿态,计算机补出中间帧,既省内存又换来控制力。(2) 选一条插值曲线:线性 lerp 便宜却僵硬,缓动(smoothstep)修掉速度的突跳,Catmull–Rom 样条能平滑地穿过多个关键点。(3) 特殊对待旋转,因为它活在球面上、而非平坦空间——欧拉角会万向锁,四元数 slerp 给出最短、匀速的弧。(4) 用骨骼层级与线性混合蒙皮角色做动画,并见识它的“糖纸”瑕疵。(5) 当逐个手 K 关键帧不现实时,就去仿真——粒子、弹簧、布料、刚体——用小时间步对物理做积分。随后第 14 课会追问:这些动起来的像素,究竟是什么“颜色”。

1 · Time as a dimension: frames, simulation time, and keyframes时间作为一维:帧、仿真时间与关键帧

A still image is a function of two variables: color at each pixel (u, v). Animation adds a third: color at each pixel at each time t. In practice we do not store a continuous t; we sample it into frames — 24 per second for film, 30 or 60 for games. But note two clocks that must not be confused. Frame time is the display grid (frame 0, 1, 2…); simulation time is the physical seconds the scene believes have elapsed. Decoupling them is what lets you render the same one-second motion at 24 or 60 fps, or produce slow-motion by sampling many frames over a short simulated interval.

一张静止图像是两个变量的函数:每个像素 (u, v) 处的颜色。动画再加一维:每个像素在每个时刻 t 处的颜色。实践中我们并不存连续的 t;而是把它采样成一个个——电影每秒 24 帧,游戏 30 或 60 帧。但要注意两个不可混淆的时钟。帧时间是显示网格(第 0、1、2……帧);仿真时间是场景所认为已流逝的物理秒数。把二者解耦,才能让同一段一秒的运动既能以 24 fps 也能以 60 fps 渲染,或者通过在一小段仿真区间上采样很多帧来做出慢动作。

Now the central economy. Suppose a door swings open over 2 seconds at 60 fps — that is 120 frames. You could store the door's rotation angle for all 120 frames. But an artist thinks in poses: "closed at t=0, fully open at t=2." That is two numbers, not 120. Those two authored poses are keyframes; the 118 frames between them are in-betweens (the term is borrowed from hand-drawn animation, where a lead artist drew the keys and assistants "inbetweened"). The computer's job is to interpolate the keyframes to synthesize every in-between.

现在讲那个核心的“经济学”。假设一扇门在 2 秒内、以 60 fps 摆开——那是 120 帧。你可以把门的旋转角度为全部 120 帧各存一份。但美术师是按姿态思考的:“t=0 时关着,t=2 时全开。”那是两个数,不是 120 个。这两个被创作出来的姿态就是关键帧;它们之间的那 118 帧是中间帧(这个词借自手绘动画:主笔画出关键帧,助手负责“补中间”)。计算机的任务就是插值关键帧,合成出每一个中间帧。

Why interpolate · 为何插值
memory: 2 keys ≪ 120 frames内存:2 个关键点 ≪ 120 帧
Why interpolate · 为何插值
control: edit a pose, not 120 samples控制:改一个姿态,而非 120 个采样
Resolution-free · 与帧率无关
same keys → any fps同一批关键点 → 任意帧率

This is the same "describe sparsely, reconstruct densely" idea that runs through the whole course — the rendering equation is an integral we sample (lesson 11), a texture is samples we filter (lesson 06). Here the sparse description is a handful of keys and the dense reconstruction is a smooth curve through them. Everything left in this lesson is about the shape of that curve.

这与贯穿整门课的“稀疏描述、稠密重建”是同一个思想——渲染方程是我们采样的一个积分(第 11 课),纹理是我们滤波的一批采样(第 06 课)。这里,稀疏描述是寥寥几个关键点,稠密重建是穿过它们的一条光滑曲线。这一课余下的全部内容,都是关于那条曲线的形状

2 · Interpolation curves — from robotic lerp to natural easing插值曲线——从僵硬的 lerp 到自然的缓动

Take two key values a and b and a normalized parameter t ∈ [0,1] that walks from the first key to the next. The simplest fill-in is linear interpolation (lerp):

取两个关键值 ab,以及一个从前一关键点走向后一关键点的归一化参数 t ∈ [0,1]。最简单的补法是线性插值(lerp):

lerp(a, b, t) = a + t · (b − a) = (1 − t) · a + t · b

Lerp is correct but it looks wrong for anything physical. Its value is continuous, but its velocity — the derivative — is a constant b − a that appears and vanishes instantly at each key. A ball animated by lerp between two rest poses starts at full speed, travels at a rigid constant rate, and stops dead. Real objects have inertia: they accelerate from rest and decelerate into rest. That mismatch is the "robotic" tell of naive interpolation, and it is a discontinuity in velocity, i.e. a lack of C^1 continuity across the key.

lerp 是正确的,但对任何有物理感的东西看起来都不对。它的值连续,但它的速度——导数——是一个常数 b − a,在每个关键点处瞬间出现又瞬间消失。用 lerp 在两个静止姿态之间做动画的球,一起步就是满速、以僵硬的恒定速率行进、然后戛然而止。真实物体有惯性:它们从静止加速、又减速回到静止。这种不匹配正是朴素插值那股“机器味”的破绽,它是速度上的一个不连续,也就是在关键点处缺少 C^1 连续性。

The fix is easing: replace the linear t with a reparameterization s(t) that still runs 0→1 but starts and/or ends with zero slope. Squaring gives ease-in (s = t^2, slow start); a mirrored square gives ease-out (s = 1−(1−t)^2, slow stop). The workhorse is smoothstep, which eases both ends:

修法是缓动:把线性的 t 换成一个重参数化 s(t),它仍从 0→1,但在起点和/或终点处斜率为零。平方给出缓入s = t^2,起步慢);镜像的平方给出缓出s = 1−(1−t)^2,收尾慢)。主力是 smoothstep,它把端都缓下来:

smoothstep(t) = 3t² − 2t³    ⟹    s(0)=0, s(1)=1, s′(0)=s′(1)=0

Check the ends: s'(t) = 6t − 6t^2 = 6t(1−t), which is zero at both t=0 and t=1 and peaks at the middle. So motion begins imperceptibly, reaches maximum speed at the halfway point, and settles gently — exactly the slow-fast-slow profile the eye reads as natural. Note that easing changes the timing, not the path: the object still visits the same in-between values, just at a rescaled tempo.

检查两端:s'(t) = 6t − 6t^2 = 6t(1−t),在 t=0t=1 处都为零,在中点取到峰值。于是运动无声无息地起步、在中点达到最大速度、再轻柔地停下——正是眼睛读作“自然”的那种慢-快-慢节奏。注意缓动改变的是时序而非路径:物体仍然经过同样的中间值,只是节拍被重新缩放了。

Easing handles two keys. For a whole animation curve with many keys — a bouncing value, a camera path — we want a single curve that passes through every key and has continuous velocity across each. Hermite splines do this by specifying, per key, both a value and a tangent (the velocity there); Catmull–Rom is the friendly special case that computes each tangent automatically from the neighbouring keys — the tangent at key P_i is (P_{i+1} − P_{i-1})/2. The result interpolates every key (unlike a Bézier control polygon, which only touches its endpoints) and is C^1 continuous, so there are no velocity jumps at the joints. This is precisely the math behind the little draggable dots in every animation-curve editor.

缓动处理的是个关键点。而对于有许多关键点的整条动画曲线——一个弹跳的数值、一条相机路径——我们想要一条穿过每个关键点、且在每个关键点处速度都连续的单一曲线。Hermite 样条通过为每个关键点同时指定一个值和一个切线(该处的速度)来做到这点;Catmull–Rom 是那个友好的特例,它从相邻关键点自动算出每条切线——关键点 P_i 处的切线是 (P_{i+1} − P_{i-1})/2。结果插值了每个关键点(不像 Bézier 的控制多边形只碰到端点),并且是 C^1 连续的,所以接缝处没有速度突跳。这正是每一个动画曲线编辑器里那些可拖动小圆点背后的数学。

Continuity, precisely连续性,说精确点

C^0 = the value is continuous (no teleporting). C^1 = the first derivative (velocity) is continuous (no sudden speed changes). C^2 = acceleration is continuous (no sudden force changes — matters for very smooth camera moves). Lerp gives only C^0 at the keys; Catmull–Rom gives C^1; natural cubic and B-splines can give C^2 but then no longer pass exactly through the keys. "Interpolate the keys" and "maximize smoothness" pull against each other — every spline picks a point on that trade-off.

C^0 = 值连续(不会瞬移)。C^1 = 一阶导数(速度)连续(没有突然的速度变化)。C^2 = 加速度连续(没有突然的受力变化——对极其平滑的运镜很重要)。Lerp 只在关键帧处给出 C^0;Catmull–Rom 给出 C^1;自然三次样条与 B 样条能给出 C^2,但代价是不再精确穿过关键帧。"穿过关键帧"与"最大化平滑度"彼此拉扯——每一种样条都在这个取舍上选了一个点。

3 · Rotation is special — gimbal lock and quaternion slerp旋转很特殊——万向锁与四元数 slerp

Positions live in flat space ℝ^3, so lerp-ing them is fine. Rotations do not. The set of all 3D rotations is a curved space (a sphere, essentially), and naively interpolating a rotation's raw numbers takes shortcuts through that space that do not correspond to sensible rotations. Two representations, two failure modes.

位置活在平坦空间 ℝ^3 里,所以对它们做 lerp 没问题。旋转不然。所有三维旋转构成一个弯曲的空间(本质上是一个球面),而朴素地对旋转的原始数值插值,会抄近路穿过那个空间——那些近路并不对应合理的旋转。两种表示,两种失效方式。

Euler angles store rotation as three sequential turns (yaw, pitch, roll). They are intuitive but suffer gimbal lock: at certain orientations two of the three axes align, collapsing three degrees of freedom to two — a whole axis of rotation silently disappears, and interpolating the angles produces lurching, non-uniform motion or gets stuck. It is the same failure that famously nearly aborted an Apollo alignment. Euler angles are a fine input for humans, a poor thing to interpolate.

欧拉角把旋转存成三次顺序转动(偏航、俯仰、翻滚)。它们直观,却会遭遇万向锁:在某些朝向下,三根轴里有两根对齐,把三个自由度塌成两个——整整一个旋转轴无声地消失了,而对角度插值会产生颠簸的、不匀速的运动,甚至卡住。这与那次著名地几乎中止了阿波罗对准的失效是同一回事。欧拉角作为给人用的输入很好,作为被插值的对象则很糟。

Unit quaternions represent a rotation as a point on the unit sphere in 4D — a scalar plus a 3-vector, q = (w, x, y, z) with |q|=1. They have no gimbal lock, compose by multiplication, and — crucially — can be interpolated along the sphere by slerp (spherical linear interpolation):

单位四元数把一个旋转表示为四维单位球面上的一个点——一个标量加一个三维向量,q = (w, x, y, z)|q|=1。它们没有万向锁,靠乘法复合,而且——关键在于——可以用 slerp(球面线性插值)沿着球面插值:

slerp(q₀, q₁, t) = [ sin((1−t)Ω) · q₀ + sin(tΩ) · q₁ ] / sin Ω ,    cos Ω = q₀ · q₁

Here Ω is the angle between the two quaternions on the sphere. Slerp traces the great-circle arc — the shortest path on the sphere — from one orientation to the other, and it does so at constant angular velocity: equal steps in t sweep equal angles. That is why a slerped turn looks perfectly even, with no speed-up in the middle. A cheaper approximation, nlerp, just lerps the four components and renormalizes; it follows the same arc but with slightly non-uniform speed. For most game motion nlerp is indistinguishable and faster; slerp is the reference when even angular speed matters.

这里 Ω 是两个四元数在球面上的夹角。slerp 描出大圆弧——球面上的最短路径——从一个朝向到另一个,并且以匀角速度做到这点:t 上相等的步长扫过相等的角度。这就是为什么一次 slerp 的转动看起来完美均匀、中途不会加速。一个更便宜的近似 nlerp,只是对四个分量做 lerp 再重新归一化;它走同一条弧,但速度略微不匀。对多数游戏运动,nlerp 与 slerp 难以区分且更快;当角速度是否均匀要紧时,slerp 是基准。

Why the sphere matters为什么球面要紧

If you lerp two unit quaternions and forget to renormalize, the result leaves the unit sphere — its length shrinks toward the chord — and a non-unit quaternion applies an unwanted scale as it rotates. Even after renormalizing (that is nlerp), the straight chord reprojected onto the sphere is traversed unevenly: fast near the ends, slow in the middle. Slerp's 1/\sin Ω weighting is exactly the correction that turns that chord into a constant-speed arc. Rotations are not vectors; treating them as such is the single most common animation bug after transform order.

如果你对两个单位四元数做 lerp 而忘了重新归一化,结果会离开单位球——它的长度朝弦收缩——而一个非单位四元数在旋转的同时会施加不想要的缩放。即便归一化之后(那就是 nlerp),被重新投影回球面的那条直弦也是被不均匀地走过的:两端快、中间慢。Slerp 的 1/\sin Ω 权重,正是把那条弦变成一段匀速圆弧的修正。旋转不是向量;把它们当作向量对待,是继变换顺序之后最常见的动画 bug。

4 · Skeletal animation & skinning — one mesh, many bones骨骼动画与蒙皮——一张网格,多根骨头

A character is one continuous skin, but it is driven by a skeleton: a hierarchy of bones, each of which is nothing more exotic than a transform — the T·R·S of lesson 02 — expressed relative to its parent. Rotate the upper-arm bone and the forearm, hand, and fingers all follow, because each child's world transform is the product of its parents' up the chain. We keyframe and slerp the bone rotations (section 3), which is far cheaper and more natural than keyframing every one of thousands of skin vertices.

一个角色是一整块连续的皮肤,但它由一副骨架驱动:一个由骨头组成的层级,每根骨头无非就是一个变换——第 02 课的 T·R·S——以相对其父的形式表达。转动上臂骨,前臂、手、手指都会跟着动,因为每个子节点的世界变换是它沿链而上所有父节点变换的乘积。我们对骨头的旋转做关键帧与 slerp(第 3 节),这远比对成千上万个皮肤顶点逐一 K 帧更便宜、更自然。

But a skin vertex near a joint — the inside of an elbow — cannot belong to just one bone, or the mesh would tear. So each vertex is weighted to several bones, the weights summing to 1, and its animated position is the weighted average of where each influencing bone would put it. This is linear-blend skinning (LBS), also called skeletal subspace deformation:

但关节附近的皮肤顶点——肘部内侧——不能只属于一根骨头,否则网格会撕裂。于是每个顶点被加权到若干根骨头上,权重之和为 1,它动画后的位置是“每根有影响的骨头会把它放到哪”的加权平均。这就是线性混合蒙皮(LBS),也叫骨骼子空间变形:

v′ = Σi wi · Mi · v ,    with   Σi wi = 1

Here v is the rest-pose vertex, M_i is bone i's skinning matrix (how that bone moved from rest to the current pose), and w_i is the vertex's weight for that bone. Because it is just a weighted sum of matrix-times-vector, LBS is dirt cheap and runs in the vertex shader on the GPU — which is exactly why it has powered real-time characters for two decades.

这里 v 是静止姿态下的顶点,M_i 是骨头 i 的蒙皮矩阵(该骨头从静止到当前姿态如何移动),w_i 是该顶点对这根骨头的权重。因为它只是“矩阵乘向量”的加权和,LBS 便宜得要命,能在 GPU 的顶点着色器里跑——这正是它驱动实时角色长达二十年的原因。

The candy-wrapper collapse“糖纸”坍缩

LBS's cheapness has a price. It averages matrices linearly, but rotation does not average linearly (section 3!). When a joint twists — a wrist rotating near 180° — the two bones' matrices point in nearly opposite directions, and their linear average is a near-degenerate matrix that shrinks the mesh. The skin pinches to a thin waist right at the joint: the notorious "candy-wrapper" artifact. The fix respects the sphere: dual-quaternion skinning blends the bones' rotations as quaternions (roughly, a slerp) instead of averaging matrices, preserving volume through twists. It costs a little more but kills the collapse. For faces, a different tool entirely — blend shapes / morph targets — stores several whole-mesh poses (neutral, smile, frown) and lerps between them per vertex, since facial motion is not bone-driven.

LBS 的廉价是有代价的。它线性地平均矩阵,可旋转并不线性地平均(第 3 节!)。当一个关节扭转时——手腕接近 180° 旋转——两根骨骼的矩阵指向几乎相反的方向,它们的线性平均是一个近乎退化的矩阵,会压扁网格。皮肤恰好在关节处收缩成一个细腰:臭名昭著的"糖纸(candy-wrapper)"瑕疵。修法要尊重球面:对偶四元数蒙皮(dual-quaternion skinning)把骨骼的旋转当作四元数来混合(大致是一次 slerp),而不是平均矩阵,从而在扭转中保住体积。它稍贵一点,但消除了塌陷。对脸部则用完全不同的工具——混合形状 / 变形目标(blend shapes / morph targets)——存下若干整网格姿态(中性、微笑、皱眉),逐顶点在它们之间做 lerp,因为面部运动并非由骨骼驱动。

5 · Procedural & physics-based motion — simulate instead of author程序化与基于物理的运动——用仿真代替创作

Keyframing is control, but some motion has too many degrees of freedom to hand-author: a thousand sparks, a flag in wind, a collapsing wall. For these we simulate — we do not describe the motion, we describe the forces and let the motion emerge. Particles carry a position and velocity and are pushed by gravity and drag; a spring-mass system connects masses by springs (Hooke's law) to model cloth or jelly; rigid bodies bounce and stack under collision and friction; cloth is a spring-mass grid with extra constraints. In every case the loop is the same: given the current state and the forces, advance time by a small step dt and update.

关键帧是控制,但有些运动的自由度太多、无法手工创作:上千颗火花、风中的旗、坍塌的墙。对这些我们仿真——我们不描述运动,而是描述、让运动自行涌现。粒子带着位置和速度,被重力与阻力推动;弹簧-质点系统用弹簧(胡克定律)把质点连起来,模拟布料或果冻;刚体在碰撞与摩擦下弹跳、堆叠;布料是一张带额外约束的弹簧-质点网格。每种情形里循环都一样:给定当前状态与受力,把时间推进一小步 dt 并更新。

The heart of simulation is numerical integration: turning acceleration into velocity into position over dt. The simplest, explicit (forward) Euler, just steps along the current derivative — v \mathrel{+}= a\,dt;\; x \mathrel{+}= v\,dt — but it injects energy and explodes if dt is too large relative to the stiffness of the system. Verlet integration stores the previous position instead of an explicit velocity (x_{\text{new}} = 2x - x_{\text{prev}} + a\,dt^2); it is more stable and makes distance constraints trivial to enforce, which is why cloth and rope solvers love it. The universal tension is the timestep trade-off: small dt is stable and accurate but slow (many steps per frame); large dt is fast but risks instability. Stiff springs force small steps — the reason a taut cloth is expensive.

仿真的核心是数值积分:在 dt 内把加速度变成速度、再变成位置。最简单的显式(前向)欧拉只是沿当前导数迈一步——v \mathrel{+}= a\,dt;\; x \mathrel{+}= v\,dt——但它会注入能量,若 dt 相对系统的刚度太大就会爆掉Verlet 积分存的是上一帧位置而非显式速度(x_{\text{new}} = 2x - x_{\text{prev}} + a\,dt^2);它更稳定,也让距离约束极易施加,这正是布料与绳索求解器钟爱它的原因。普遍的矛盾是时间步权衡:小 dt 稳定而精确,但慢(每帧要走很多步);大 dt 快,却有失稳风险。刚硬的弹簧逼你用小步长——这就是绷紧的布料昂贵的缘由。

ApproachYou authorBest forCost / risk
Keyframe + interpolateexact poses at key timeshero motion, cameras, cutscenescheap; labor-intensive to author
Skeletal + LBSbone-rotation keyscharacters, creaturescheap on GPU; candy-wrapper at twists
Procedural / physicsforces & constraintscrowds of particles, cloth, debrisemergent, less control; timestep-bound
方法你创作的是最适合开销 / 风险
关键帧 + 插值关键时刻的精确姿态主角动作、相机、过场便宜;创作耗人力
骨骼 + LBS骨头旋转关键帧角色、生物GPU 上便宜;扭转处有糖纸
程序化 / 物理力与约束成群粒子、布料、碎片涌现、控制力弱;受时间步限制
Interpolation curves: how easing shapes motion插值曲线:缓动如何塑造运动
The graph plots several remap functions f(t) on [0,1]×[0,1]; the active one is bold, the rest faint. A dot marks (t, f(t)). Below, a position track places a ball at the interpolated value so you can feel the tempo: linear moves at constant speed, ease-in-out is slow-fast-slow, ease-in starts creeping, ease-out glides to a stop. Catmull–Rom threads four fixed keys (0, 0.9, 0.2, 1) so the ball overshoots and settles. Pick a curve; drag t.图上画出几个重映射函数 f(t)[0,1]×[0,1] 上;当前曲线加粗,其余变淡。圆点标出 (t, f(t))。下方的位置轨道把小球放在插值后的值处,让你感受节拍:线性匀速,缓入缓出慢-快-慢,缓入起步爬行,缓出滑行到停。Catmull–Rom 穿过四个固定关键点(0、0.9、0.2、1),于是小球会过冲再回稳。选一条曲线;拖动 t
t
eased f(t)
speed f′(t)
tempo
Show the core JS查看核心 JS
// Smoothstep: eases BOTH ends. s(0)=0, s(1)=1, s'(0)=s'(1)=0.
function smoothstep(t){ return t*t*(3 - 2*t); }        // = 3t^2 - 2t^3
function easeIn (t){ return t*t; }                     // slow start
function easeOut(t){ return 1 - (1-t)*(1-t); }         // slow stop

// Catmull-Rom through P0..P3, parameter u in [0,1] on the P1->P2 span.
// Tangents are taken from the neighbours: m1=(P2-P0)/2, m2=(P3-P1)/2.
function catmullRom(P0,P1,P2,P3,u){
  var u2 = u*u, u3 = u2*u;
  return 0.5*( (2*P1)
             + (-P0 + P2)*u
             + (2*P0 - 5*P1 + 4*P2 - P3)*u2
             + (-P0 + 3*P1 - 3*P2 + P3)*u3 );
}
// Instantaneous speed via a finite difference (central).
function speed(f, t){ var h = 1e-3; return (f(t+h) - f(t-h)) / (2*h); }

Scrub the slider under linear and the ball crosses the track at a dead-constant rate — the speed KPI never changes. Switch to ease-in-out: the same slider now inches the ball out slowly, races it through the middle (peak speed KPI), and eases it into the far wall. That is the whole point of section 2 made tactile — same start, same end, same path, utterly different feel, all from reshaping f(t). The Catmull–Rom option adds interior keys so the value overshoots past 0.9 and dips before resolving, showing how a spline threads several targets with continuous velocity.

线性下拖动滑块,小球以纹丝不变的速率穿过轨道——速度 KPI 始终不变。切到缓入缓出:同一个滑块现在让小球缓缓挪出、在中段飞驰(速度 KPI 达到峰值)、再缓入远端的墙。这就把第 2 节的要点变得可触可感——同样的起点、终点、路径,却是截然不同的手感,全都来自重塑 f(t)。Catmull–Rom 选项加入了内部关键点,于是数值会冲过 0.9 再回落才收敛,展示了样条如何以连续的速度穿过多个目标。

Where this points next接下来指向何处

Time is now a first-class dimension of the scene. We can describe motion sparsely with keyframes, interpolate positions with easing curves and splines, interpolate rotations correctly with quaternion slerp, deform characters with skeletal skinning, and, where authoring fails, integrate physics over small steps. But notice something: every value we interpolated in this lesson — a position, an angle, a vertex — was a number in the geometry pipeline. We never asked what a color is, yet we have been blending and outputting colors since lesson 08. Is lerp-ing two RGB values even the right thing to do? Lesson 14, color, HDR & tone mapping, confronts that: light values span an enormous dynamic range, displays do not, and the naive linear blends we have used are subtly wrong in the wrong color space. The animated frames are correct in motion; next we make them correct in color.

时间现在是场景的一等维度。我们能用关键帧稀疏描述运动,用缓动曲线与样条插值位置,用四元数 slerp 正确插值旋转,用骨骼蒙皮变形角色,并在创作力不逮之处对物理做小步积分。但请注意一件事:这一课里我们插值的每一个值——位置、角度、顶点——都是几何流水线里的数。我们从没问过一个颜色什么,可从第 08 课起我们就一直在混合并输出颜色。对两个 RGB 值做 lerp,究竟对不对?第 14 课,色彩、HDR 与色调映射,正面迎战这个问题:光的数值横跨极大的动态范围,显示器却不能,而我们一直用的朴素线性混合,在错误的色彩空间里会微妙地出错。动起来的帧在运动上是对的;接下来我们让它们在色彩上也对。

Takeaway要点

Animation is rendering with time as a third axis, sampled into frames (keep frame time and simulation time distinct). We author motion sparsely as keyframes and interpolate the in-betweens — saving memory and buying editability. Linear lerp is C^0 only: constant velocity that starts and stops abruptly, the "robotic" look. Easing reshapes the time parameter — smoothstep 3t²−2t³ zeroes the slope at both ends for natural accel/decel — and Catmull–Rom splines pass through many keys with C^1 continuity via neighbour-derived tangents. Rotations live on a sphere: Euler angles gimbal-lock, so use unit quaternions and slerp, which follows the shortest great-circle arc at constant angular velocity (nlerp is the cheaper, slightly uneven cousin). Characters use a bone hierarchy + linear-blend skinning v′ = Σ w_i M_i v (weights sum to 1) — cheap, but its linear matrix averaging causes the candy-wrapper collapse at twists, fixed by dual-quaternion skinning. When authoring is impractical, simulate: integrate forces over dt (explicit Euler vs the more stable Verlet), always fighting the timestep stability trade-off.

动画就是把时间作为第三根轴的渲染,采样成一帧帧画面(把帧时间与仿真时间分清)。我们把运动稀疏地写成关键帧、再插值出中间帧——省内存、也换来可编辑性。线性 lerp 只有 C^0:匀速、却突起突停,一副"机器人"的样子。缓动重塑时间参数——smoothstep 3t²−2t³ 把两端的斜率归零,得到自然的加减速——而 Catmull–Rom 样条借由邻点推出的切线,以 C^1 连续性穿过众多关键帧。旋转活在一个球面上:欧拉角会万向锁,所以要用单位四元数与 slerp,它以恒定角速度沿最短的大圆弧前进(nlerp 是更便宜、略不均匀的表亲)。角色用骨骼层级 + 线性混合蒙皮 v′ = Σ w_i M_i v(权重和为 1)——便宜,但它线性平均矩阵的做法会在扭转处造成糖纸塌陷,用对偶四元数蒙皮来修。当手工制作不现实时,就仿真:把力在 dt 上积分(显式 Euler,对比更稳定的 Verlet),并始终与时间步长稳定性取舍作斗争。

Interview prompts面试题