atelier

Catalog / Technique

Contour field

Use when drawing contour lines, isolines or a topographic map from a noise or height field in a fragment shader. Covers the line technique, the reveals, the tuning, and where such a map may and may not sit on a page.

Loads as
technique-contour-field
Written for
An agent, to follow

Contour field

A height field that is never shown, read as the lines of a map. Drawn entirely in the fragment shader: no geometry, no texture.

When to use

  • A load effect or a hero band that should read as craft and still belong to the page: the lines are the page’s own ink.
  • A background mark for a region with no text in it.

Do not use it for:

  • Behind body text, at any strength.
  • Anywhere near real numbers, charts or maps. It reads as part of them.
  • Listings and card grids.

Rules

  • Differentiate the continuous height, not the wrapped value. The wrap has a jump in it, and the derivative at the jump is a false line.
  • Use the length of the derivative vector, not fwidth, where line evenness matters. fwidth thickens diagonals.
  • Multiply CSS widths by the canvas’s real pixel ratio. Below one device pixel, fade the line instead of thinning it.
  • Drop intermediate contours on steep ground and keep the index lines. Otherwise a slope is a smear.
  • Normalise the field and set the interval as a number of levels. Otherwise the line count changes when the octave count does.
  • Spread the levels over the height the field really has. Layered gradient noise stays within about a third of its nominal range. Left unscaled, eleven levels draw four, and a reveal that sweeps the range is over in a fifth of its time.
  • Size features in CSS pixels, never as a share of the element.
  • Hash with integers. A sine hash differs between GPUs.
  • Write the noise from the published algorithm. Do not copy a shader snippet whose licence you have not read.
  • Output premultiplied ink on a transparent canvas, so the ground is the page’s and one shader serves both themes.
  • Explain in comments above the shader string, not inside it. A comment in the string ships.

Decisions

Question Choose Because
Which reveal? Tide by default It is the one where the map visibly arrives
Can a line draw itself on? No An implicit line has no start, end or length. Tide is the honest substitute
Which noise? Gradient noise Value noise gives lattice-shaped contours; simplex gains nothing in 2D
Relief in perspective? A real mesh, not a fragment-shader fake The fake breaks above about 30 degrees of tilt and costs 3.6 times as much
Ends on? Nothing for a page load, a still for a hero band A still behind text must be far weaker than the reveal was
Which colour? The ink by default; the accent, or ink rising to accent by height, as a named version Colour comes from the page’s two tokens and nowhere else, so it is right on every skin. A third colour would be the effect’s own
One recipe or several looks? One recipe with named versions in PRESETS A look is a few options that belong together. A second file for it would be a second harness to keep the same

Tuning

Dial Start Range Low end High end
Feature size (CSS px) 180 90 to 400 Busy, many small islands A few broad sweeps
Octaves 3 1 to 5 Smooth rings, very designed Ragged coastlines; cost is linear
Gain 0.5 0.35 to 0.7 The base shape dominates Detail as strong as the hills
Warp 0 0 to 0.8 Plain terrain Folded, geological; at 1.5 lines break up
Levels 11 6 to 22 Sparse and airy Dense; steep ground drops lines
Line width (CSS px) 1.25 0.75 to 2 Hairline Heavy, diagram-like
Ink while playing 0.6 0.3 to 1 A watermark passing Full ink; text under it is hard to read
Ink in a still behind a headline 0.2 0.12 to 0.3 Barely there Above 0.3 it costs reading
Drift (rad/s) 0.25 0 to 1 A frozen map Visibly churning

Ranges are from stills. Motion has not been tuned by eye.

Rest state

Nothing by default. With a still: the last frame as a 2D canvas, at the still’s ink strength, behind the content. Under reduced motion, without WebGL2 or on a software renderer there is no map at all.

Cost

  • To build: small on a finished harness. The line function is the whole craft.
  • To run: about 1.3 kB of shader on a 2.3 kB harness. Cost per pixel is linear in octaves, and doubles with warp on.
  • To the reader: lines over the page for under a second. A still costs nothing to run and something to read past.

Checklist

  • npm run stills, and the frames look like the reveal’s description.
  • Lines are even on diagonals and upright runs alike.
  • On a 2x screen a 1.25 px line is 2.5 device pixels, not 1.25.
  • Steep ground shows index lines, not a black mass.
  • The same settings read on the light theme and the dark.
  • No text sits under lines at more than a watermark.
  • Looked at once at phone size and once at 30 fps.

Recipes

  • contour-field: the flat form, with four reveals.
  • plane-settle: the same map on a tilted plane that swings flat and lands on the flat form.
  • terrain-settle: the same field as real relief on a mesh, with its contours re-evaluated per fragment.

Sources

  • mdn-prefers-reduced-motion: the media feature that stops it starting