atelier

Catalog / Technique

Displacement field

Use when a field is seen in perspective on a page, as a drawing on a tilted plane or as a displaced mesh with real relief, and settles flat. Covers the camera, the mesh built from the vertex index, the horizon, hidden lines, and what a camera move owes the reader.

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

Displacement field

The contour map’s field, seen from an angle. Two forms: a flat drawing on a tilted plane, which is the fragment shader plus eight lines, and a mesh that is really displaced, which needs a vertex shader and a depth buffer and nothing else. Both settle until they lie flat in the page.

When to use

  • A load effect or a hero where the page’s own map or grid arrives from a lean and lands on the page.
  • Relief that has to be real: a near hill hiding a far one, a surveyed point cloud.

Do not use it for:

  • Anywhere reduced motion cannot swap it for the flat form. A camera move is the motion most likely to cause discomfort.
  • Over content that should be read during the move.
  • A phone, for the mesh, until it has been measured on one.

Rules

  • The camera is eight lines of GLSL and no matrix code. Put the page in z = 0 with y from -1 to 1, the eye at distance D with a field of view whose tangent is 1 / D. At zero tilt a ray through a pixel meets the plane at that pixel, so the rest frame is the flat form exactly. A tilt is one rotation about the pivot line.
  • A flat thing at an angle is a fragment shader; relief is a mesh. Relief faked by re-intersecting the ray breaks above about 30 degrees of lean and costs 3.6 times the flat form; a height-field march costs nineteen times. A mesh is cheaper than the flat form, because the field runs per vertex.
  • The mesh needs no vertex data. Six vertices a cell from gl_VertexID by division and remainder, one a corner for points. No buffer, no attribute, no upload.
  • Draw lines in the fragment shader. gl.lineWidth is 1 on every platform measured, so a GL_LINES wireframe is one device pixel whatever is asked. Fill the triangles and put the line set on the interpolated page coordinate; a filled triangle under a depth test hides what is behind it for free.
  • Contours on a mesh re-evaluate the field per fragment. The interpolated height is piecewise linear and its derivative jumps at every edge.
  • The horizon fade is relative. Fade between one and three times a pixel’s footprint at rest; an absolute footprint wiped out a correct render at a small canvas size.
  • Take the footprint before the discard, and hold the ray’s depth away from zero. An infinite neighbour reaches the derivative of a pixel that is not, and a line’s edge goes black.
  • Clear before every frame. A fragment that discards above the horizon leaves the previous frame in the hole otherwise.
  • precision highp int in both shaders. An int uniform shared between them fails to link when their default precisions differ.
  • An opaque fill goes behind the content only. The flat form draws premultiplied ink on a transparent canvas and may go over or under; the mesh’s surface hides words.
  • Points shrink with distance. Without a size falloff the far half fuses into a mass.
  • The field is contour-field’s. Same hash, same noise, same scaling, same seed; the perspective form at seed 7 lands on the flat form at seed 7.

Decisions

Question Choose Because
Plane or mesh? Plane unless relief is the point The plane is the flat recipe plus eight lines; the mesh is a second shader and a depth buffer
Which pivot? The bottom edge The near edge stays put and the page tips away; the centre tips both ways
Wire or points? Wire Points need a size falloff and read as a survey, not a surface
Contours on the mesh? Only on gentle relief On a low camera hill faces foreshorten and the lines read as noise
What settles? Tilt and amplitude together, over the first 72% A frame on the flat page with relief still in it reads as a bump, not a landing
Under reduced motion? Nothing. Mount the flat form’s Survey instead A camera move is not a reveal that can be made gentle
Ends on? Nothing for a page load; the flat map or grid as a still for a hero The still is the flat form’s still, at the still’s ink

Tuning

Dial Start Range Low end High end
Tilt (degrees) 55 20 to 65 A slight lean Near the ground, horizon high in the frame
Distance (page heights) 2.4 1.5 to 4 Wide and dramatic Flat, telephoto
Horizon (multiple of the rest footprint) 1 0.5 to 2 The far ground fades early Specks near the horizon
Relief amplitude (page heights, mesh) 0.28 0 to 0.4 A flat sheet Peaks cross and hide each other
Mesh cells per side 160 64 to 256 Faceted hills Smooth; cost is the square
Grid pitch (CSS px) 56 24 to 120 Dense Tie it to the page’s column width
Point size (CSS px) 2 1 to 4 A fine survey Blobs near the camera
Reach beyond the top edge (page heights, mesh) 0.5 0 to 2 The mesh ends a third of the way up at a 55 degree lean Runs on to the horizon
Duration 1200 to 1400 ms 800 to 2000 A flick A loading screen

Ranges are from stills; the research’s and this lab’s. Motion has not been tuned by eye.

Rest state

Nothing by default. With a still: the flat map or grid as a plain 2D canvas at the still’s ink strength. Under reduced motion, forced colours, without WebGL2 or on a software renderer nothing starts, and a page that wants a map there mounts the flat form’s Survey.

Cost

  • To build: small for the plane on a finished flat recipe; medium for the mesh, where the tuning (horizon, pivot, cells, landing on the page grid) is the work.
  • To run: the plane costs about the flat map plus the camera, and three times that near the horizon. The mesh costs less than the flat map per frame on an integrated GPU, plus a depth buffer the size of the canvas. Not measured on a phone.
  • To the reader: a camera move over the page for about a second. The costliest kind of motion there is for comfort, which is why it never runs under reduced motion.

Checklist

  • npm run stills, and the settle frames land on the flat form.
  • npm run check:browser: the settled plane and the flat map are the same frame within antialiasing.
  • Above the horizon the canvas is transparent, not the last frame.
  • Lines hold their width into the distance and fade, not speck.
  • The mesh’s surface is behind the content; nothing is hidden.
  • Under reduced motion no context is requested.
  • Looked at once at phone size and once at 30 fps; the mesh measured on a real phone before it is marked passed.

Recipes

  • plane-settle: the map or the page’s grid on a tilted plane.
  • terrain-settle: the displaced mesh, as wire or points.
  • contour-field: the flat form both land on.

Sources

  • iq-distance: the first-order distance to a level set, and where it fails
  • evanw-grid-shader: constant-width lines from the screen-space derivative
  • mdn-webgl-linewidth: how to query the line width range, which is 1 to 1
  • webkit-scroll-driven-guide: simulated depth movement as the motion most likely to cause discomfort
  • mdn-prefers-reduced-motion: the media feature that stops it starting