Wishlist on Steam
No.05

Bézier Curves

Bézier curves power smooth camera paths, projectile arcs, UI animations, and spline geometry across countless games. This article breaks down the math from linear interpolation to De Casteljau's algorithm, with an interactive demo where you sculpt the curve in real time.

t = 0.49
Published
Reading time
8 min
Demos
4 to play with
Author
Kieren Hovasapian
Interactive 3D · Drag to orbit · scroll to zoomOpen full screen

When a racing game swings its camera around a hairpin bend, when a platformer's fireball traces an arc, or when a UI panel eases into place with a bounce, Bézier curves are almost certainly doing the work. Named after French engineer Pierre Bézier who popularized them at Renault in the 1960s (though mathematician Paul de Casteljau independently discovered them earlier at Citroën), Bézier curves are among the most useful tools in any game developer's toolkit.

Unlike rigid geometric primitives, Bézier curves offer intuitive, artist-friendly control over smooth paths. Place a handful of control points and the math does the rest, producing curves that feel natural without forcing you to solve systems of equations. Understanding them deeply unlocks an entire category of game mechanics: smooth camera rails, procedural animation, spline-based road networks, projectile trajectories, easing functions, and more.

The Foundation: Linear Interpolation

Every Bézier curve, regardless of degree, reduces to repeated applications of one simple operation: linear interpolation, or LERP. Given two points and and a scalar parameter , the interpolated point is:

At you get . At you get . At you are exactly halfway between them. This is itself a first-degree (linear) Bézier curve. Everything that follows is just this same operation applied recursively to its own intermediate results.

JavaScript
function lerp(a, b, t) {
  return {
    x: a.x + (b.x - a.x) * t,
    y: a.y + (b.y - a.y) * t
  };
}
Interactive · Click, drag and playOpen full screen

Quadratic Bézier Curves

Add a third control point and apply LERP twice, and you get a quadratic (second-degree) Bézier curve. Given , , and , first interpolate between adjacent control points to get two intermediate points:

Then interpolate between those two results:

Expanding and collecting terms yields the closed-form quadratic Bézier:

The curve starts at , ends at , and is pulled toward the off-curve handle without ever touching it. The curve leaves tangent to the line and arrives at tangent to . This automatic tangent-continuity is what gives Bézier curves their characteristic smoothness.

Cubic Bézier Curves

The cubic (third-degree) Bézier curve with four control points is the workhorse of game development. SVG paths, CSS easing functions, TrueType font outlines, Unity's Animation Curves, and most professional spline editors all use cubic Bézier segments. With , , , :

The four coefficients are the cubic Bernstein basis polynomials. They always sum to exactly 1 for any value of , which enforces the convex hull property: no matter how you position the handles, the curve never strays outside the convex hull of its four control points. This makes Bézier curves safe and predictable for artists.

JavaScript
function cubicBezier(p0, p1, p2, p3, t) {
  const mt = 1 - t;
  const mt2 = mt * mt, mt3 = mt2 * mt;
  const t2  = t  * t,  t3  = t2  * t;
  return {
    x: mt3*p0.x + 3*mt2*t*p1.x + 3*mt*t2*p2.x + t3*p3.x,
    y: mt3*p0.y + 3*mt2*t*p1.y + 3*mt*t2*p2.y + t3*p3.y
  };
}

De Casteljau's Algorithm

The recursive construction behind Bézier curves is called De Casteljau's algorithm. Rather than evaluating the polynomial directly, it applies LERP repeatedly to adjacent pairs of points until one point remains. The interactive demo above visualizes this live: the orange lines are level-1 interpolations, the red lines are level-2, and the white dot is the final curve point.

Interactive · Click, drag and playOpen full screen

For a cubic Bézier at parameter :

  • Level 1 — LERP adjacent control points: , ,
  • Level 2 — LERP the level-1 results: ,
  • Level 3 — Final LERP gives the curve point:
JavaScript
// General De Casteljau — works for any degree
function decasteljau(points, t) {
  let P = points.slice();
  while (P.length > 1) {
    const next = [];
    for (let i = 0; i < P.length - 1; i++) {
      next.push(lerp(P[i], P[i + 1], t));
    }
    P = next;
  }
  return P[0];
}

De Casteljau's algorithm has a useful side property: at split parameter , the intermediate points and are the exact control points for the two sub-curves on either side of the split. This makes it ideal for curve subdivision, splitting a Bézier cleanly into two identical-looking halves at any , a technique used in font rasterization and adaptive tessellation.

The Bernstein Polynomial Form

Expanding the De Casteljau recursion symbolically for a degree- curve produces the Bernstein polynomial form:

where is the binomial coefficient. The basis functions are always non-negative on and sum to 1. These properties guarantee the convex hull bound and make Bézier curves numerically stable. The endpoint-interpolation property follows directly: and always hold, because at the endpoints all basis functions except one collapse to zero.

Tangents and the Derivative

Beyond position, you often need the direction of travel along the curve, for aligning a spaceship to its flight path, orienting a camera look-at, or computing normals along a road. The tangent vector is the derivative :

This is itself a quadratic Bézier on the differences of the original control points. Its magnitude is not constant; it depends on the spacing of the handles, which connects directly to the arc-length problem discussed next.

Interactive · Click, drag and playOpen full screen
JavaScript
function cubicBezierTangent(p0, p1, p2, p3, t) {
  const mt = 1 - t;
  return {
    x: 3*(mt*mt*(p1.x-p0.x) + 2*mt*t*(p2.x-p1.x) + t*t*(p3.x-p2.x)),
    y: 3*(mt*mt*(p1.y-p0.y) + 2*mt*t*(p2.y-p1.y) + t*t*(p3.y-p2.y))
  };
}

Arc-Length Parameterization

The most important gotcha with Bézier curves in games: does not correspond to equal distances along the curve. Incrementing by 0.01 near a gentle section advances the point much further than the same step near a tight bend. Move an object by advancing at a constant rate and it will visibly speed up and slow down.

For constant-speed motion you need arc-length parameterization: a mapping from normalized arc-length to the corresponding value. The standard approach is a precomputed lookup table:

JavaScript
function buildArcTable(curveFn, samples = 200) {
  const table = [{ t: 0, d: 0 }];
  let total = 0, prev = curveFn(0);
  for (let i = 1; i <= samples; i++) {
    const t = i / samples;
    const pt = curveFn(t);
    total += Math.hypot(pt.x - prev.x, pt.y - prev.y);
    table.push({ t, d: total });
    prev = pt;
  }
  return table;
}

function tFromArcLength(table, u) {
  const target = u * table[table.length - 1].d;
  for (let i = 1; i < table.length; i++) {
    if (table[i].d >= target) {
      const alpha = (target - table[i-1].d) / (table[i].d - table[i-1].d);
      return table[i-1].t + alpha * (table[i].t - table[i-1].t);
    }
  }
  return 1;
}

A 100–200-sample table gives sub-pixel accuracy for typical game paths and runs in microseconds. For high-precision applications like font rendering, you can refine the initial estimate with one or two Newton-Raphson iterations.

Bézier Curves in Real Games

Bézier curves appear throughout shipped titles in many forms:

  • Camera rails — Cinematic systems in games like God of War and The Last of Us define camera paths as chains of Bézier segments. Directors position handles to frame shots, and the engine follows the path at arc-length-parameterized speed.
  • Projectile arcs — Rather than fully simulating physics for lobbed grenades or mortar shells, many games bake the trajectory as a cubic Bézier evaluated cheaply each frame, giving designers direct artistic control over the arc shape.
  • UI easing — CSS's cubic-bezier() — used in Unity's iTween, Godot's Tween, and countless web games — is literally a cubic Bézier sampled for its Y-value at a given X-value, producing ease-in, ease-out, and overshoot motion profiles.
  • Road and rail networks — Open-world games like GTA V and Forza Horizon represent road center-lines as Catmull-Rom splines (a close cubic cousin). Artists place waypoints and the engine auto-generates smooth road meshes and AI navigation data.
  • Font rendering ��� TrueType glyphs use quadratic Béziers; OpenType/PostScript use cubics. Any game rendering custom logos or UI text in-engine is drawing Bézier curves per frame.
  • Particle systems — Color-over-lifetime gradients, size curves, and velocity profiles in Unity's Shuriken and Unreal's Niagara are all Bézier splines artists edit in curve editors.

Chaining Segments into Splines

A single cubic Bézier gives one smooth segment. For complex paths you chain segments into a spline. The challenge is ensuring smoothness at joins. Three continuity levels are defined:

  • C0 (positional) — The endpoint of segment equals the start of segment . No gap, but there may be a visible corner.
  • C1 (tangent) — Tangent vectors match in direction and magnitude at the join. No corner. The outgoing handle of segment must be the exact mirror of the incoming handle of segment through the shared endpoint.
  • C2 (curvature) — Both first and second derivatives match. No sudden change in curvature. Natural cubic splines achieve C2 everywhere but require solving a global system; Bézier splines need extra constraints.

For game use, C1 is almost always sufficient. Unity's AnimationCurve, Unreal's SplineComponent, and Godot's Path3D all enforce C1 by default, mirroring the opposite handle automatically when you drag one.

Practical Implementation

Here is a production-ready cubic Bézier class combining everything above:

JavaScript
class CubicBezier {
  constructor(p0, p1, p2, p3) {
    this.P = [p0, p1, p2, p3];
    this._table = null;
  }

  evaluate(t) {
    const [p0, p1, p2, p3] = this.P;
    const mt = 1 - t, mt2 = mt*mt, mt3 = mt2*mt, t2 = t*t, t3 = t2*t;
    return {
      x: mt3*p0.x + 3*mt2*t*p1.x + 3*mt*t2*p2.x + t3*p3.x,
      y: mt3*p0.y + 3*mt2*t*p1.y + 3*mt*t2*p2.y + t3*p3.y
    };
  }

  tangent(t) {
    const [p0, p1, p2, p3] = this.P;
    const mt = 1 - t;
    return {
      x: 3*(mt*mt*(p1.x-p0.x) + 2*mt*t*(p2.x-p1.x) + t*t*(p3.x-p2.x)),
      y: 3*(mt*mt*(p1.y-p0.y) + 2*mt*t*(p2.y-p1.y) + t*t*(p3.y-p2.y))
    };
  }

  _buildTable(n = 200) {
    this._table = [{ t: 0, d: 0 }];
    let total = 0, prev = this.evaluate(0);
    for (let i = 1; i <= n; i++) {
      const t = i / n, pt = this.evaluate(t);
      total += Math.hypot(pt.x - prev.x, pt.y - prev.y);
      this._table.push({ t, d: total });
      prev = pt;
    }
  }

  evaluateArc(u) {
    if (!this._table) this._buildTable();
    const tbl = this._table;
    const target = u * tbl[tbl.length - 1].d;
    for (let i = 1; i < tbl.length; i++) {
      if (tbl[i].d >= target) {
        const alpha = (target - tbl[i-1].d) / (tbl[i].d - tbl[i-1].d);
        return this.evaluate(tbl[i-1].t + alpha * (tbl[i].t - tbl[i-1].t));
      }
    }
    return this.evaluate(1);
  }
}

With evaluate(t) for parameter-based positions, tangent(t) for orientation, and evaluateArc(u) for constant-speed motion, this class covers most game use cases, from projectile arcs to camera rails to procedural geometry generation.

Mechanics Library

The live demo from Skeletal Animation & Skinning.

No.44Animation & cameras

Skeletal Animation & Skinning

Skeletal animation and skinning are the foundation of every character animation in 3D games — discover how bone hierarchies, inverse bind matrices, and linear blend skinning transform static meshes into fluidly moving characters in real time.

1 demo13 min read

The live demo from Third-Person Camera Systems.

No.43Animation & cameras

Third-Person Camera Systems

Learn how third-person camera systems work in games, from basic follow cameras to smooth exponential damping and collision avoidance with raycasting. Explore the math and interact with a live Three.js demo.

1 demo9 min read

The live demo from Procedural Locomotion.

No.40Animation & cameras

Procedural Locomotion

Learn how procedural locomotion generates lifelike walking animations at runtime using step triggering, gait coordination, and two-joint inverse kinematics — no pre-baked animation clips required.

1 demo8 min read

The live demo from Inverse Kinematics.

No.08Animation & cameras

Inverse Kinematics

Inverse Kinematics (IK) is the mathematical technique behind natural-looking limb movement in games — discover how the FABRIK algorithm elegantly solves the chain-reaching problem and how to implement it from scratch.

4 demos9 min read

Esc