Reshaping t, line by line
Step 1 — see the problem. Lerp advances by the same amount for every equal slice
of t: its rate is dead flat, since
\frac{d}{dt}\!\left[(1-t)a + tb\right] = b - a is a
constant. The
motion never accelerates and never slows — that abrupt start and stop is what looks mechanical.
Step 2 — the idea of easing. Keep the lerp; pass it an
eased parameter instead of the raw one. An easing function
e maps [0, 1] \to [0, 1] with
e(0) = 0 and e(1) = 1 (same endpoints, so the
animation still starts at a and ends at b), but
bends the journey in between:
\text{value} = \operatorname{lerp}\big(a,\, b,\, e(t)\big).
Step 3 — ease-in (start slow). The simplest bend is to square
t:
e_{\text{in}}(t) = t^2.
Near t = 0, t^2 is tiny, so the value barely
moves at first and then races to catch up — it accelerates in. Good for things gathering
momentum (a falling weight, a charging attack).
Step 4 — ease-out (end slow). Mirror ease-in by flipping the input and output:
e_{\text{out}}(t) = 1 - (1 - t)^2.
This screams off the line and decelerates into the finish — the natural feel for a UI
element settling into place. (Ease-in-out simply uses e_{\text{in}} for
the first half and e_{\text{out}} for the second.)
Step 5 — smoothstep: slow at both ends in one formula. The animator's favourite is
a single cubic that eases in and out:
s(t) = 3t^2 - 2t^3.
Check the endpoints: s(0) = 0 and
s(1) = 3 - 2 = 1. Good.
Step 6 — why it feels smooth: zero slope at the ends. Differentiate:
s'(t) = 6t - 6t^2 = 6t(1 - t).
At the boundaries this slope vanishes: s'(0) = 0 and
s'(1) = 6(1)(0) = 0. Slope is speed, so the motion starts from a dead
stop and glides to a dead stop — no velocity jump at either end. That is precisely the
"slow-in / slow-out" the eye reads as natural. In the middle s'\big(\tfrac12\big) = 6 \cdot \tfrac12 \cdot \tfrac12 = \tfrac32,
a touch faster than a lerp's constant 1 — it makes up the time it spent
easing.
Step 7 — wire it in. Nothing about lerp changes; you just feed it
s(t):
\text{value} = \operatorname{lerp}\big(a,\, b,\, s(t)\big) = a + (b - a)\,(3t^2 - 2t^3).
One line turns a robotic slide into a polished, weighty move.
Easing reshapes the interpolation parameter before the blend:
-
An easing function e : [0,1] \to [0,1] keeps the
endpoints (e(0) = 0, e(1) = 1) but bends the
path between; you feed e(t) into lerp:
\operatorname{lerp}(a, b, e(t)).
-
Ease-in t^2 starts slow; ease-out
1 - (1 - t)^2 ends slow.
-
Smoothstep s(t) = 3t^2 - 2t^3 eases in and out
in one cubic, with s(0) = 0, s(1) = 1.
-
Its slope vanishes at both ends — s'(t) = 6t(1 - t) gives
s'(0) = s'(1) = 0 — so motion accelerates smoothly in and decelerates
smoothly out (no velocity jump).
Disney's animators codified the "12 basic principles of animation" in the 1930s, and one of them
is slow in and slow out: real objects take time to accelerate and decelerate, so
drawings should crowd their frames near the extremes of a move and spread them out through the
middle. Smoothstep 3t^2 - 2t^3 is exactly that principle written as a
formula — its vanishing end-slopes are the bunched frames at the start and finish, its
steeper middle is the spread-out frames in between. Decades of hand-drawn craft, captured
in a one-line cubic that every UI toolkit now ships under names like ease-in-out. Need
an even silkier stop? The quintic smootherstep
6t^5 - 15t^4 + 10t^3 zeroes the second derivative at the ends
too, killing the jerk as well as the velocity jump.