Continuation sheets 08–15 · follows the quadratic Bézier set

B-splines
and NURBSthe knots do the work

A Bézier gives every control point a say in every part of the curve. A B-spline breaks that by inserting a knot vector — a list of parameter values marking where one polynomial piece hands off to the next. Drag the blue beads on the ruler below and watch the basis functions slide, narrow, and merge.

Knots
Active
Sheet

Parameter and degree

Degree
Knots

Interior knots

valuemult.continuity
Sheet 08of 15

The thing a Bézier can't do

The Bernstein weights \(b_i(t)=\binom{n}{i}t^i(1-t)^{n-i}\) are nonzero everywhere on the open interval \((0,1)\). Every control point pulls on every part of the curve. Move one and the whole shape breathes.

For a three-point curve that's fine, even desirable. For a car body outline with forty control points it's unusable: you cannot adjust the door line without disturbing the roof. And raising the degree to get more points makes it worse — degree 40 polynomials are numerically miserable.

The obvious fix is to chain many short Béziers. That works, and it is what font files do. But now smoothness at the joints is your problem: to keep the seam invisible you must manually keep each junction's neighbouring points collinear, and re-enforce that every time you edit. The constraint is real but nothing in the representation enforces it.

A B-spline is the same idea with the bookkeeping built in. You choose a degree \(p\) once — 2 or 3, and it stays there no matter how many control points you add — and the representation guarantees the pieces meet smoothly.

The name. "Spline" was a thin strip of wood that draughtsmen bent through a set of points, held down by lead weights called ducks. It settles into the shape that minimises bending energy. The "B" is for basis: Schoenberg's 1946 term for the minimal building blocks that span the space of all such piecewise polynomials.
Sheet 09of 15

The knot vector

Fix a degree \(p\) and \(n+1\) control points \(P_0,\dots,P_n\). The knot vector is a nondecreasing list

$$U=\{u_0,u_1,\dots,u_m\},\qquad m=n+p+1$$

These are values of the parameter, not points in space. They partition the parameter interval into spans, and the curve is a separate polynomial piece of degree \(p\) on each span. The count is forced: \(m+1 = n+p+2\) knots, no choice about it.

The curve is only defined on \([u_p,\,u_{n+1}]\) — the first \(p\) and last \(p\) spans are there to give the outermost basis functions somewhere to ramp up. That's the shaded strip on the ruler.

Non-uniform

That's the N in NURBS, and it just means the spacing need not be equal. Evenly spaced knots give a uniform B-spline; unequal spacing lets you stretch some pieces and compress others. Drag a bead toward its neighbour and watch the corresponding basis function narrow — that region of the curve now gets traversed in less parameter time.

Clamped ends

Repeat the first knot \(p+1\) times and the last knot \(p+1\) times and the curve is pulled to \(P_0\) and \(P_n\) exactly, tangent to the first and last legs of the control polygon — Bézier-like endpoints. That's the default here and the default nearly everywhere, because an interpolated endpoint is what you want when joining pieces. Without clamping, the curve floats short of its outer control points.

The other common choice is periodic: wrap the knots and repeat control points around the seam to close the curve smoothly, with no endpoints at all.

Sheet 10of 15

Cox–de Boor, from the bottom

The basis functions are defined by recursion on the degree. The base case is a box:

$$N_{i,0}(t)=\begin{cases}1 & u_i \le t < u_{i+1}\\[2pt] 0 & \text{otherwise}\end{cases}$$

and each higher degree blends two neighbours from the degree below, with weights that ramp linearly across their supports:

$$N_{i,p}(t)=\frac{t-u_i}{u_{i+p}-u_i}\,N_{i,p-1}(t)\;+\;\frac{u_{i+p+1}-t}{u_{i+p+1}-u_{i+1}}\,N_{i+1,p-1}(t)$$

Any term with a zero denominator is taken to be zero — that convention is what makes repeated knots legal rather than a division by zero.

Here is one function's whole ancestry. The highlighted degree-2 bump is built from the two highlighted hats, which are built from the three highlighted boxes:

Uniform knots. Each step up in degree widens the support by exactly one span and adds one order of smoothness.

Read the picture and the two facts you need fall out. A degree-\(p\) function is supported on \(p+1\) spans, because each recursion step glues one more span onto the support of a box. And it is \(C^{p-1}\), because multiplying by a linear ramp and adding raises smoothness by one at each level: boxes are discontinuous, hats are continuous, quadratics have continuous first derivatives.

Compare with Bernstein. There, \(b_i\) has support on the whole interval and the "spans" are one. Here the support is local by construction, and everything else follows.

Still a partition of unity. On the domain the \(N_{i,p}\) are non-negative and sum to 1, exactly as the Bernstein weights did. So the convex-hull property, affine invariance, and variation diminishing all carry over — locally now, to the hull of each active group of \(p+1\) points rather than to all of them.
Sheet 11of 15

Local support, counted

The curve is the same weighted sum you already know:

$$C(t)=\sum_{i=0}^{n} N_{i,p}(t)\,P_i$$

but almost every term is zero. \(N_{i,p}\) is nonzero only on \([u_i,u_{i+p+1})\), so at any \(t\) in span \(k\), exactly \(p+1\) basis functions are nonzero: those with \(i=k-p,\dots,k\). The sum has \(n+1\) terms and \(p+1\) of them do anything.

Turn on Highlight the active control points and sweep. The solid black points are the ones with a nonzero weight right now; the hollow ones are contributing nothing at all. Set the degree to 3 and only four points are ever live, whether you have seven control points or seven hundred.

Read the same fact the other way: control point \(P_i\) affects the curve only on \([u_i,u_{i+p+1})\), which is \(p+1\) spans. Drag a middle point on the sheet — the far ends of the curve do not move at all. That is the property the whole construction exists to deliver, and it is why every modelling program in existence uses these.

Cost. Evaluation touches \(p+1\) points regardless of \(n\), so it is \(O(p^2)\) — independent of how long the curve is. Editing is local too: change one point and only \(p+1\) spans need redrawing.
Sheet 12of 15

Multiplicity buys and spends smoothness

Knots may repeat. If a value appears \(s\) times, the curve is \(C^{\,p-s}\) there instead of \(C^{\,p-1}\). Each extra copy costs one order of continuity:

Multiplicity \(s\)Continuity at that knotWhat you see
1\(C^{p-1}\)invisible join — the default
2\(C^{p-2}\)for \(p=3\): curvature jumps, tangent still smooth
\(p\)\(C^{0}\)a corner; the curve passes exactly through a control point
\(p+1\)the curve comes apart into two independent pieces

Try it: set the degree to 3 and press Corner. All three interior knots stack at \(t=0.5\), the basis functions collapse into a Bézier-like cluster, and the curve develops a hard corner that touches its control point. Then drag them apart one at a time and watch the corner heal.

This is the mechanism behind clamped ends too. Repeating the end knot \(p+1\) times isn't a special rule bolted on — it is multiplicity \(p+1\) at the boundary, which is exactly the "curve comes apart" case, and what it detaches is the curve's start from the phantom span before it.

It is also how a B-spline contains Bézier as a special case. Take \(p=n\), so there are no interior knots left at all: the knot vector is \(p+1\) zeros followed by \(p+1\) ones, the basis functions become the Bernstein polynomials exactly, and de Boor's algorithm becomes de Casteljau's. Press 6 = Bézier — with seven control points, degree 6 leaves nothing interior, and the sheet reverts to the previous set's behaviour: one polynomial, global control, no locality.

Knot insertion. You can add a knot without changing the curve's shape at all — recompute the affected control points and the geometry is identical, you just have more handles. Repeat until every interior knot has multiplicity \(p\) and the B-spline has been converted into a chain of Bézier segments. That's how a renderer that only speaks Bézier draws a NURBS.
Sheet 13of 15

de Boor's algorithm

The evaluation method is de Casteljau with the knot vector supplying the interpolation fractions instead of \(t\) itself. Find the span \(k\) containing \(t\), take the \(p+1\) live control points, and collapse them:

$$d_j^{[0]}=P_{j+k-p},\qquad j=0,\dots,p$$ $$d_j^{[r]}=(1-\alpha)\,d_{j-1}^{[r-1]}+\alpha\,d_j^{[r-1]},\qquad \alpha=\frac{t-u_{j+k-p}}{u_{j+1+k-r}-u_{j+k-p}}$$

After \(p\) rounds one point is left, and it is \(C(t)\). That is the blue scaffolding on the sheet: same shrinking triangle you saw with de Casteljau, but the fractions come from knot spacing, so uneven knots make it slide unevenly.

Two practical consequences. It is numerically stable — every step is a convex combination, so nothing amplifies rounding error, unlike evaluating the basis functions and summing. And it hands you subdivision for free: the intermediate \(d_j^{[r]}\) are precisely the new control points you need to split the curve at \(t\) into two curves with the same shape.

Derivatives work the way you'd hope. \(C'(t)\) is itself a B-spline, of degree \(p-1\) on the same knot vector with the ends trimmed, with control points

$$Q_i=\frac{p}{u_{i+p+1}-u_{i+1}}\,(P_{i+1}-P_i)$$

which is the same shape of result as \(B'(t)=n\sum b_i \Delta P_i\) for a Bézier — differences of consecutive control points, scaled. Here the scale factor is per-interval, because the intervals aren't equal.

Sheet 14of 15

Rational: the R in NURBS

Everything so far is still polynomial, so the objection from the last set still stands: no polynomial curve is exactly a circle. Fix it by giving each control point a weight \(w_i>0\) and dividing:

$$C(t)=\frac{\displaystyle\sum_{i=0}^{n} w_i N_{i,p}(t)\,P_i}{\displaystyle\sum_{i=0}^{n} w_i N_{i,p}(t)}$$

The denominator restores the sum-to-one property after weighting, so this is still a convex combination and the hull property survives. Setting every \(w_i=1\) collapses it back to the ordinary B-spline.

Below is a single rational quadratic with weights \(1,\,w,\,1\) on a right-angle control triangle. The dashed red arc is a true circle. Drag the weight:

One weight sweeps the entire family of conics. Below 1 you get ellipse arcs, at exactly 1 the denominator is constant and you're back to the ordinary parabola from the last set, above 1 hyperbola arcs. And at \(w=\tfrac{\sqrt2}{2}\) the deviation from a true circle drops to zero — not "to within a pixel," but exactly, to the last bit of the floating-point representation. In general a circular arc of angle \(\theta\) needs \(w=\cos(\theta/2)\), which is why quarter circles use \(\cos 45°\).

Why the division is natural, not a hack

Lift each control point into homogeneous coordinates \((w_ix_i,\;w_iy_i,\;w_i)\) and run an ordinary polynomial B-spline up there in 3D. Then project back down to the plane by dividing through by the last coordinate — a pinhole projection from the origin. The rational curve is just the shadow of a polynomial one.

That reframing pays for itself immediately: because the curve is a projection, it is invariant under projective transformations, not merely affine. You can transform the control points by a perspective matrix and re-evaluate, and get the correctly foreshortened curve. Polynomial Béziers only manage this for affine maps.

Weights are a shape handle too. Raising \(w_i\) pulls the curve toward \(P_i\); as \(w_i\to\infty\) it reaches the point. But they're a fiddly control — most modellers hide them, leave them all at 1, and expose them only where an exact conic is required. Negative weights can make the denominator vanish, so they're forbidden.
Sheet 15of 15

Where this ended up

NURBS won CAD because one representation covers everything the others needed separately:

  • Lines, planes, conics, free-form curves and surfaces — all one data structure, so a kernel needs one intersector, one evaluator, one file format. A cylinder and a fender are the same kind of object.
  • Exact primitives. A machined hole must be a real circle, not a good approximation. Manufacturing tolerances are tighter than the error a polynomial fit leaves behind.
  • Standardised. IGES and then STEP (ISO 10303) put NURBS in the interchange format, so it became the lingua franca between Catia, NX, SolidWorks, Rhino, and everything else.

Surfaces are the same construction with two parameters: \(S(u,v)=\sum_i\sum_j N_{i,p}(u)N_{j,q}(v)P_{ij}\), a tensor product over a rectangular grid of control points, with weights for the rational version.

That rectangular grid is also the catch. Real objects are not topological rectangles, so you trim patches against curves and stitch them, and neighbouring trimmed patches only agree to within a tolerance. Keeping a model watertight is a large fraction of what a CAD kernel does, and cross-package "healing" is an industry unto itself.

Which is why animation went elsewhere. Pixar and everyone downstream use subdivision surfaces — Catmull–Clark — which take an arbitrary polygon mesh and refine it toward a smooth limit. No patch grid, arbitrary topology, watertight by construction. The tradeoff is no exact conics and no closed-form parametrisation, which a film does not care about and a machine shop very much does.

Meanwhile the humble cubic Bézier never went away. Fonts, SVG, and GPU vector renderers still use degree 2 and 3 pieces, because when you're rasterising a million curves per frame the cheap thing wins. Each representation sits where its tradeoff is the right one.

If you keep going. Knot insertion (Boehm's algorithm) and degree elevation are the two workhorse operations; interpolation — solving for the control points that make the curve pass through given data — is a banded linear system, pleasantly cheap thanks to local support. T-splines relax the rectangular grid and are the interesting modern patch on the topology problem.