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.
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
- Box convolved with Gaussian is a product of erf differences, one per axis.
- Divide by
sigma * sqrt(2), notsigma. Getting that wrong makes the blur 40% too tight and it looks almost right. - The A and S polynomial is exact enough for 8-bit output. Do not reach for a longer series.
- Use it for the shadow and draw the real silhouette on top. The shadow does not need the true shape.
- It is resolution independent, so the same call is correct for a thumbnail and a 4K capture.
One email when something new goes up. No newsletter, no schedule, nothing else.
Double opt-in, so watch for a confirmation email. Unsubscribe any time.