Code and working examples

SDF Reference.

Signed distance fields describe a shape by the distance to its edge. These notes use them to build characters, break shapes apart, and work with light and color.

Two posed figures built from signed distance fields

Start with a figure

Build it. Pose it. Break it.

The explanation, the code and a working demo, together. 61 notes, grouped by what you are building.

Explore the figure

Building shapes

The primitive arsenal

Eight 2D primitives that between them cover most of what you will draw, and the one test that tells you a primitive is correct.

Interactive demo

Fields that return their own gradient

The normal is usually reconstructed with four extra field evaluations. Most primitives know their gradient in closed form, and it composes through the operators.

Interactive demo

Choosing the blend radius

k is the most load-bearing number in any shape built from primitives, and a constant k is wrong the moment anything tapers.

Interactive demo

Blend only what is jointed

smin does not know your skeleton. Blend every part with every other and a hand passing near a hip grows a web of material between them.

Interactive demo

Carrying material through a blend

smin already computes the blend weight. Return it and your colors transition exactly where the geometry does, instead of near where it does.

Interactive demo

t is not distance along a curve

Stepping a Bezier's parameter evenly does not step evenly along it. Anything placed by t bunches where the curve is slow and thins where it is fast.

Interactive demo

An SDF atlas cannot store a corner

Signed distance stored in a texture is a sampled field, and sampling rounds every sharp feature. That single fact is why direct outline evaluation exists, and it is the whole trade.

Interactive demo

Characters

Building a figure out of a field

A character is a skeleton first and a distance field second. Joints in world space, primitives hung off them, blended by local radius.

Interactive demo

The tapered limb is not a lerped capsule

Interpolating a capsule's radius along its axis looks right and is not a distance function. The outline gives it away before the silhouette does.

Interactive demo

Deformers: bend, taper, shear

Do not deform the shape. Deform the space it is measured in, and remember what that does to the distance.

Interactive demo

Cape and scarf as a swept field

Cloth is a curve you can evaluate, swept by a tapered capsule. The thing that makes it read is lag, not physics.

Interactive demo

Clothing is the body's field, pushed out

Armour and garments modeled as separate shapes drift off the body the moment it moves. Derive them from the body's own distance instead and they fit by construction.

Interactive demo

Morphing between two shapes

Lerping two distance fields works and is subtly wrong. A smin crossfade keeps every intermediate a real shape.

Interactive demo

What a crowd actually costs

A field has no instancing. Every pixel evaluates every figure, so a crowd is figure cost times crowd size times every pixel on screen.

Interactive demo

A squashed field is not a distance any more

Sphere tracing steps the full reported distance, which is safe only while the field never overreports. One non-uniform scale breaks that, and the surface fills with holes.

Interactive demo

A 3D accent needs a depth to sort against

Dropping a raymarched object into a 2D scene works until it has to pass behind something. A 2D scene has no depth, so you have to assign one, and one number per layer is enough.

Interactive demo

Destruction

Shattering with a Voronoi field

Break a shape without ever building geometry: carve each piece with its own cell, and evaluate the body in the piece's rest space.

Interactive demo

Growing a crack network

Cracks run straight and branch. A random walk reads as scribble, and the difference is one line in the direction update.

Interactive demo

A flying piece needs every term in its own frame

Cutting a shape into pieces is easy. Keeping each piece rigid while it moves means transforming every term of its construction, and it is always a cut plane that gets left behind.

Interactive demo

The seam is two correct edges added wrong

Where a field-rendered shape meets polygon geometry, both sides antialias their own edge and the background survives in between. Neither piece is wrong and the join is.

Interactive demo

A cut face is a different material

Splitting a shape gives you a new surface that was never meant to be seen. Shade it like the outside and the pieces read as smaller copies rather than as fragments.

Interactive demo

Shatter a figure along its own structure

A generic Voronoi shatter does not know it is cutting a body, so it slices limbs lengthwise and leaves shards with no thickness. Seed the cells from the skeleton instead.

Interactive demo

What a shard costs, measured

A shard in a WebGL2 fragment shader costs about 0.007ms per megapixel on an M4 Max. The bounding-circle optimization everyone reaches for buys 22 percent, not an order of magnitude.

Interactive demo

Light and glow

The three-layer neon glow

One exponential falloff gives you a blurry shape. A light needs three, at different radii, plus a crisp stroke.

Interactive demo

A neon tube is a line, not a point

Treating a tube light as a point source is correct far away and badly wrong close up. The line integral is a difference of two arctangents.

Interactive demo

Halation is not bloom

Bloom is neutral and symmetric. Halation is red, much wider, and gated to genuinely bright sources. They are different physics and they should not share a slider.

Interactive demo

Penumbrae get softer with distance

Two-dimensional global illumination is a gather over directions, and the ray count needed for a clean result is brutal. Radiance cascades exploit the fact that far light needs angular detail and near light needs spatial detail, never both.

Interactive demo

Lighting ratios in stops

Key to fill is the single dial that sets mood, and it is measured in stops because light is multiplicative. 1:1 is flat, 8:1 is a thriller.

Interactive demo

A flare is the transform of the aperture

The star pattern around a bright light is not a decoration, it is the Fourier transform of the hole the light came through. That gives you the streak count, the falloff and the color, none of which are taste.

Interactive demo

Jump flooding does not pay until 500 seeds

Brute force is linear in seed count and jump flooding is flat, so there is a crossover. Measured on an M4 Max at 960 by 540, it sits around five hundred, which is far higher than the advice suggests.

Interactive demo

Color and the display

ACES skews hue on saturated neon

A per-channel tonemapper compresses each channel independently, so the brightest one saturates first and the color rotates on its way to white. It is why neon reds go orange.

Interactive demo

Red floats, blue sinks

Saturated red and saturated blue on black appear to sit at different depths. It is an optical fact about your eye, not a design choice, and it makes text on a dark UI physically tiring.

Interactive demo

Contrast is not symmetric

Light text on dark and dark text on light are not equally readable, and the classic contrast ratio cannot tell you that because it is symmetric by construction.

Interactive demo

The tonemapper is doing the work

Neon whitens at the core because a curve rolls the channels off in order. Clamp instead and you get a flat block of the wrong color.

Interactive demo

Add light in linear, not in gamma

Adding colors in display space makes every overlap too bright and shifts the hue. It is the most common rendering bug that nobody reports as a bug.

Interactive demo

Dither before you quantize

A dark gradient bands because 1/255 is a huge relative step down there. Half a code value of noise fixes it, and it has to go before the write.

Interactive demo

Not all dither noise is the same noise

White noise clumps, and the clumps are exactly the low-frequency structure the eye is built to find. Blue noise puts its energy where you cannot see it.

Interactive demo

Edges and sampling

Grain belongs in the midtones

Film grain is not uniform noise. Its amplitude follows the exposure, peaking in the midtones and vanishing in both the shadows and the highlights.

Interactive demo

Grain does not survive the delivery encode

Per-pixel noise is the first thing a video encoder throws away, so grain authored at one pixel arrives as blocky mush. Author it coarser, or add it after delivery.

Interactive demo

Camera and cutscenes

Motion blur without subframes

Shutter coverage for a moving disc is a quadratic. Solve it and you get exact blur at one evaluation.

Interactive demo

A captured cutscene has no frame budget

A cutscene that ships as video is rendered once, on your machine, with no deadline. Almost nobody uses that, and it is the largest free quality increase available in a game.

Interactive demo

Motion streaks and the brightness you lose

A streak is the shape swept along its velocity. The part people forget is that the same light is now spread over a longer smear, so fast things get dimmer, not brighter.

Interactive demo

Planimetric staging is a hold, not a snap

Putting the camera on a compass point and the props on an axis is half of it. The half that does the work is stopping there long enough for the arrangement to be read.

Interactive demo

Stepping a cutscene on twos

Quantize the time you sample at, not the simulation. And never step the whole frame at once.

Interactive demo

Score the frame, do not trust your eye

You already know where the subject is, so you cannot see your own composition. A cheap saliency map answers where a first-time viewer looks, and it is often not where you staged.

Interactive demo

Type arrives in sequence or it does not arrive

A word that fades in as one block reads as a slide. Sixty milliseconds of stagger per letter and a settle instead of a ramp is most of what separates kinetic typography from a fade.

Interactive demo

Bokeh is a picture of the aperture

Out-of-focus highlights are not blurred blobs. They are images of the hole the light came through, and the rim is brighter than the middle.

Interactive demo

Turbulence without a simulation

Rotated sines stack into a convincing fluid warp, stay seekable, and cost a handful of sin calls. The rotation between octaves is the whole trick.

Interactive demo

The shape of a punch

Same peak, same duration, three envelopes. Linear reads mechanical, exponential reads physical, and a two-frame attack is the difference between a hit and a value being assigned.

Interactive demo

Perception and safety

The hundred millisecond flash law

Under about 100ms the eye sums light rather than measuring it, so a shorter flash must be brighter to read at all.

Interactive demo

Flicker has published thresholds, so use them

Three flashes a second over a quarter of the screen is the line the accessibility standards draw. Most attention-getting effects sit near it by accident, and there is almost always a version that reads the same without flashing.

Interactive demo

Performance

What a field actually costs

Every pixel evaluates every primitive unless you stop it. A bounding early-out is the one change that moves the number in 2D.

Interactive demo

Render scale is cheap until something is thin

Dropping to 50 percent render scale is a 4x saving and it costs almost nothing on soft content. What it destroys is thin features, and a field renderer is usually full of them.

Interactive demo

One full-screen pass, and the flip that eats the day

Getting a distance field shader onto the screen in URP is a render feature, a material and a blit. The part that costs a day is a sign on the V coordinate that is correct on one graphics API and wrong on another.

Interactive demo

One SDF core, two dialects, four traps

Sharing distance field code between a web prototype and a Unity build is mostly a rename. The parts that are not a rename compile cleanly and draw the wrong picture.

Interactive demo