Time, Frames and Sampling
The world moves continuously: a thrown ball traces one unbroken arc, a spinning wheel turns
through every angle in between. A screen cannot. A screen can only show you a rapid slideshow of
still pictures — frames — and trust your eye to stitch them back into motion. Every
animated film, every video game, every physics simulation you have ever seen is really a sequence of
frozen snapshots, taken at regular ticks of a clock, played back fast enough to fool the brain.
That act of freezing continuous motion into evenly-spaced snapshots is sampling, and
it is the hidden foundation under all of computer animation. Get it right and motion looks smooth and
alive. Get it wrong and wheels spin backwards, fast objects stutter, and — in the worst case — your
physics engine detonates a character across the level. This page is about time: how it turns
into frames, how sampling can lie to you, and why the clock a simulation runs on is not the
clock your display runs on.
Frames, frame rate, and time as the independent variable
An animation is a function of one variable: time. Everything else — a character's
position, a joint's angle, a colour — is an output driven by time. Formally the motion of a
channel is a continuous signal x(t), and the timeline you
scrub in any animation tool is just the t-axis of that function.
To display it we chop time into equal slices and take one snapshot per slice. Each snapshot is a
frame; the number of frames shown per second is the frame rate,
measured in frames per second (fps). The time between consecutive frames is simply
the reciprocal:
\Delta t_{\text{frame}} = \frac{1}{\text{fps}}.
At 24 fps a frame lasts 1/24 \approx 41.7 ms; at
60 fps it lasts 16.7 ms. Frame n
is the sample taken at time t_n = n\,\Delta t_{\text{frame}}, so the pictures
you actually see are the values x(t_0), x(t_1), x(t_2), \dots — the signal
read off at the clock ticks.
- 24 fps — the cinema standard since the sound era; its gentle, slightly
stuttery motion is the "film look".
- 30 / 25 fps — broadcast television (NTSC / PAL heritage).
- 60 fps (and up) — interactive games and high-frame-rate video, where crisp,
low-latency motion matters more than a cinematic feel.
Film gets away with 24 fps partly because a real camera's shutter is open for a slice of each frame,
so fast objects smear across the picture — natural motion blur. A game rendering an
instant, razor-sharp snapshot each frame has no such smear, which is one reason games want higher
frame rates to look smooth.
Worked example: how far per frame?
Take an object gliding at a steady v = 10 m/s. How far does it travel
between two frames? That distance is the per-frame spacing — the jump the object makes from
one snapshot to the next:
d_{\text{frame}} = v \cdot \Delta t_{\text{frame}} = \frac{v}{\text{fps}}.
- At 24 fps: d = 10 / 24 \approx 0.417 m per frame — about
42 cm of jump between pictures.
- At 60 fps: d = 10 / 60 \approx 0.167 m per frame — about
17 cm.
Same physical speed, but the higher frame rate takes smaller steps, so each jump is gentler
and the motion reads as smoother. The total distance over a second is identical
(10 m either way) — frame rate changes the granularity of the
sampling, not the underlying motion.
Now a spin. A wheel turning at f = 20 revolutions per second, sampled at
24 fps, advances
20/24 \approx 0.833 of a turn each frame — that is 300^\circ
of rotation between snapshots. The eye cannot tell 300^\circ forward from
60^\circ backward: the wheel appears to crawl slowly in reverse.
This is not a rendering glitch — it is sampling telling a lie, and it has a precise cause.
Temporal aliasing: when sampling lies
If a motion repeats or moves too fast for the frame rate to keep up, the samples can trace out a
different, slower motion that was never really there. This false motion is
temporal aliasing. Its everyday faces are judder and
strobing (a fast pan breaking into visible stutter) and the famous
wagon-wheel effect — spoked wheels in old Westerns spinning backwards or standing
still while the stagecoach races forward.
The governing rule is the same one that governs sampling any signal: the
Nyquist limit.
To capture a motion honestly you must sample it more than twice per cycle.
- A cyclic motion of frequency f is faithfully captured only if the
frame rate satisfies \text{fps} > 2f.
- Below that, the samples alias: the motion appears at a lower (possibly negative) frequency and
the true speed is lost.
- For a 20-rev/s wheel you would need
\text{fps} > 40 to see it turn the right way — 24
fps is far too slow, so it aliases.
The fix is the film camera's trick: motion blur. Instead of an instantaneous
snapshot, integrate the motion over the frame's exposure window, smearing a fast object into a streak.
Blur acts as a low-pass filter before sampling — it removes the high-frequency detail that
would otherwise alias, trading crisp-but-lying frames for soft-but-honest ones. It is why a spinning
wheel with proper motion blur reads as a believable blur rather than a backward crawl.
Because mains lighting samples too. A fluorescent or LED lamp on AC flickers at twice the
mains frequency (100 or 120 Hz), briefly
lighting the wheel in strobe-like pulses. If the wheel advances almost a whole spoke-spacing
between flashes, each pulse catches it a hair short of where the last spoke was — and your eye
reconstructs a slow backward creep. Same aliasing maths, no film required: any pulsed
sampling of a periodic motion can do it. Under steady daylight (continuous illumination) the effect
vanishes, because there is nothing doing the sampling.
See it alias
Below, the faint curve is the true continuous motion — a sine wave
x(t)=\sin(2\pi f t) at a fixed frequency of
f = 3 Hz (three full cycles a second). The bold dots are the samples your
chosen frame rate actually captures, joined by straight lines — this is what the animation would
look like. Drag the frame-rate slider down and watch the sampled motion peel away from the
truth: below the Nyquist rate of 2f = 6 fps the dots trace a slow, wrong,
aliased wave with the wrong frequency — even sliding backwards. Push the rate high
and the samples hug the real curve.
Notice there is a threshold, not a gradual fade: cross below roughly 6 fps
and the reconstructed motion suddenly reports the wrong frequency entirely. That cliff is the
Nyquist limit made visible. No amount of clever playback recovers the lost cycles — the information
simply was not sampled.
Display frame rate is not the simulation timestep
Here is the distinction that separates people who ship stable animation from people who chase mystery
bugs. There are two clocks:
- The display frame rate — how often you draw a picture on screen
(\Delta t_{\text{frame}} = 1/\text{fps}). This is what the viewer sees.
- The simulation timestep \Delta t_{\text{sim}} — how
often the physics or motion is advanced by one integration step. This is how the world is
computed.
These need not be equal, and usually should not be. A physics engine might take several small
sub-steps per rendered frame: to draw at 60 fps while
integrating at a rock-steady 240 Hz, you run 4
simulation steps of \Delta t_{\text{sim}} = 1/240 s for every frame you
show. Smaller \Delta t_{\text{sim}} means more accurate, more stable
integration — a fast projectile no longer tunnels straight through a wall between snapshots because
the simulation checked several positions in between.
This is why the professional pattern is a fixed timestep with an accumulator: bank
the real time that has elapsed, then spend it in fixed-size chunks of
\Delta t_{\text{sim}}, no matter how fast or slow the display is rendering.
let accumulator = 0;
const dt = 1 / 240; // fixed simulation step — never changes
function frame(realElapsed: number): void {
accumulator += realElapsed; // bank however long the last frame took
while (accumulator >= dt) {
stepPhysics(dt); // advance the world in fixed chunks
accumulator -= dt;
}
render(); // draw whenever the display is ready
}
Fixed vs variable timestep — why physics demands fixed
The tempting shortcut is a variable timestep: measure how long the last frame took
and advance the simulation by exactly that. It keeps motion looking real-time on any hardware, and for
purely kinematic motion (moving something at a known velocity) it is fine —
x \mathrel{+}= v\,\Delta t is correct for any \Delta t.
Physics is a different animal. Numerical integrators (Euler, Verlet, and friends) are
only stable when the step is small enough; feed a stiff spring or a fast collision a large, wobbly
\Delta t and the numbers overshoot, feed back on themselves, and diverge to
infinity. A variable step also makes the simulation non-deterministic: run the same
scene twice on two machines and you get two different results, which wrecks replays, networked games
and reproducible tests. A fixed \Delta t_{\text{sim}} keeps the integrator
in its stable range and makes every run bit-identical.
The single most common beginner mistake in game physics is stepping the simulation with the
display frame time — literally passing the measured frame duration straight into the
integrator:
// DON'T: physics step = whatever the last render took
function frame(realElapsed: number): void {
stepPhysics(realElapsed); // dt is now at the mercy of the frame rate
render();
}
Now the timestep is hostage to performance. When the machine is fast, frames are short and physics
is stable. But the instant the frame rate drops — a heavy scene, a background download, a
debugger pause — \Delta t balloons, the integrator overshoots, and stiff
forces feed back on themselves: characters launch through walls, ragdolls fling into orbit, the sim
"explodes". Even without a crash, gameplay literally runs at different speeds on different hardware.
The fix is the accumulator loop above: step physics at a fixed
\Delta t_{\text{sim}} and let only the rendering ride
the variable frame rate. Never couple the two.
Interpolation: reading between the samples
Once motion lives as samples, you often need a value between two of them — for slow-motion,
for retiming a shot, or (with the accumulator above) to draw the world at a display time that falls
partway through a simulation step. You interpolate. Given samples
x_n and x_{n+1} a fraction
\alpha \in [0,1] of the way from one to the next, the simplest estimate is
linear:
x(\alpha) = (1-\alpha)\,x_n + \alpha\,x_{n+1}.
Want a 4\times slow-motion clip? Generate three interpolated in-between
frames for every pair of original frames. Retiming — speeding a shot up or slowing it down — is the
same idea: resample the motion at new, non-integer frame times and interpolate to fill the gaps. Good
slow-motion tools use smarter, motion-aware interpolation than plain linear, but the principle is
identical: the continuous signal was only ever sampled, so any in-between value is a
reconstruction, and how well it looks depends on how good that reconstruction is — which
loops us right back to Nyquist. If the original sampling aliased, no interpolation can invent the
motion that was never captured.
Because you are asking the reconstruction to do work the sampling never supported. A clip shot at
30 fps has one snapshot every 33 ms; stretch it
8\times and you must invent seven frames between every real pair. Linear
interpolation just cross-fades, so a fast-moving object smears or ghosts. That is exactly why sports
and nature filmmakers shoot at 240, 1000 or
more fps up front: they oversample the motion so that when it is played back slowly, every
"slow" frame is a real sample, not a guess. Capture the cycles you will need — you cannot add them
back later.