Continuation sheets 08–15 · follows the quadratic Bézier set
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.
Parameter and degree
Interior knots
| value | mult. | continuity |
|---|
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.
Fix a degree \(p\) and \(n+1\) control points \(P_0,\dots,P_n\). The knot vector is a nondecreasing list
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.
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.
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.
The basis functions are defined by recursion on the degree. The base case is a box:
and each higher degree blends two neighbours from the degree below, with weights that ramp linearly across their supports:
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:
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.
The curve is the same weighted sum you already know:
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.
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 knot | What 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.
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:
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
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.
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:
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°\).
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.
NURBS won CAD because one representation covers everything the others needed separately:
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.