A shadow is a difference of two erfs

Built on the erf approximation 7.1.26 by Abramowitz and Stegun. Sources for this reference

A soft shadow is a shape convolved with a blur. People implement that by taking a pile of samples, because convolution sounds like something you sample. For a rectangle and a Gaussian it has an exact answer, and the answer is one line per axis.

Left: 64 samples per pixel. Right: the closed form, one evaluation. The blur radius animates so you can watch both track it.

The left side is visibly stepped, and it gets worse as the radius grows because the same 64 samples are spread over more area. The right side is exact at every radius and never changes cost.

Where the erf comes from

Convolving with a Gaussian means integrating the Gaussian over the region the shape covers. The integral of a Gaussian is the error function, by definition. So for an axis-aligned box the coverage along one axis is the difference of the erf at the two edges, and because a Gaussian is separable the 2D answer is just the product of the two axes.

vec2 lo = (p - b) / (sigma * sqrt(2.0));
vec2 hi = (p + b) / (sigma * sqrt(2.0));
vec2 v  = 0.5 * (erf(hi) - erf(lo));
return v.x * v.y;

GLSL has no erf, so it needs an approximation. The standard Abramowitz and Stegun polynomial has a maximum absolute error around 1.5e-7, which is roughly a thousandth of one 8-bit code value. For rendering it is exact, and it costs one exp and a handful of multiplies.

What this replaces

Drop shadows, soft rectangular light sources, blurred panel backdrops, and the whole family of effects normally done with a blurred texture. None of them need a texture, a second pass or a sample loop, and all of them become resolution independent because the answer is a function rather than a buffer.

The limit is honest: it is exact for a box and a Gaussian. Rounded corners, arbitrary SDFs and non-Gaussian kernels do not have this closed form, and the usual move there is to approximate the shape as a box for the shadow while drawing the real shape on top. At shadow blur radii nobody can tell.

Rules of thumb

  1. Box convolved with Gaussian is a product of erf differences, one per axis.
  2. Divide by sigma * sqrt(2), not sigma. Getting that wrong makes the blur 40% too tight and it looks almost right.
  3. The A and S polynomial is exact enough for 8-bit output. Do not reach for a longer series.
  4. Use it for the shadow and draw the real silhouette on top. The shadow does not need the true shape.
  5. It is resolution independent, so the same call is correct for a thumbnail and a 4K capture.

All 61 notes How to use them Credits