Survey model

Contour rings of a noise island rise off a slate plinth into a stacked survey model. A kernel traces each ring by bisecting the height along rays from the peak and writes its points into one set of lines, which draws every ring as a lit enamel tube in one draw, every fifth ring bold, dashing the soundings below the shoreline. A graticule of hairlines a point wide is printed on the plinth, beside a north arrow and a scale bar, and the rings cast a soft shadow under the sun. TAA and bloom finish the picture. Point at a ring to pick it out.

Files, in reading order

  1. scene.tsCreates the scene: its resources, its settings and what each frame records.
  2. contours.tsTraces the island's contour rings into one set of lines and shades them as satin enamel tubes, lit by the lighting of the scene they draw in.
  3. terrain.ts

Kit modules bloom, cameras, finish, instances, lighting, lines, materials, meshPool, owner, passes, pipelines, shading, shadows, taa, targets, tone. Also @typegpu/color, @typegpu/noise, typegpu.

scene.ts

import {  bloom,  cameras,  finish,  instances,  lighting,  lines,  materials,  meshPool,  owner,  passes,  pipelines,  shading,  shadows,  taa,  targets,  tone,  type Frame,  type Surface,} from '@typegpu/kit';import { caps, meshes } from '@typegpu/kit/geometry';import { srgbToLinear } from '@typegpu/color';import { d, std, type TgpuRoot } from 'typegpu';import type { Pointing } from '../contract.ts';import type { Settings } from '../settings.ts';import { createContours } from './contours.ts';const BACKDROP = [0.022, 0.024, 0.028] as const;/** Seconds the view takes to tip from above the model to its working angle. */const TIP = 4;/** Seconds the rings take to rise, from `RISE[0]` to `RISE[1]`. */const RISE = [0.4, 3] as const;/** The view's pitch above the model, and the angle it settles at. */const PITCH = [1.42, 0.74] as const;/** The plinth's half width and thickness, its top at 0. */const PLINTH = { half: 5, thickness: 0.3 };const paint = (r: number, g: number, b: number) => srgbToLinear(d.vec3f(r, g, b));const linear = (rgb: readonly [number, number, number]) => {  const { x, y, z } = paint(...rgb);  return [x, y, z, 1];};const SLATE = materials.glossy(paint(0.065, 0.072, 0.085), 0.55);const BASE = materials.glossy(paint(0.08, 0.085, 0.09), 0.5);const BRASS = [0.78, 0.6, 0.32] as const;const BONE = [0.86, 0.83, 0.76] as const;/** The graticule's spacing and half width in world units, and its ink's opacity. */const GRID = { spacing: 1, half: 4.75, ink: 0.07 };/** World units the graticule's ink lies over the plinth's top. */const PRINT = 0.004;/** Share of the sun's light that reaches into a shadow. */const SHADE = 0.42;/** * Where the scale bar lies on the plinth, its `east` and its `south` end, with the north arrow * west of it, and the bar's length in world units and its parts. */const LEGEND = { east: 4.3, south: -2.2, bar: 2, parts: 4 };/** World units the legend's inlay stands over the plinth's top, above the graticule's ink. */const INLAY = 0.008;/** The north arrow's head: from its centre to its tip, and the turn pointing its tip north, to -z. */const HEAD = { radius: 0.24, rotation: [0, Math.SQRT1_2, 0, Math.SQRT1_2] as const };/** Shades the graticule as faint ink printed flat on the plinth. */const ink = (fragment: lines.Fragment) => {  'use gpu';  const lit = shading.shade(    materials.matte(fragment.color.xyz),    shading.pointAt(fragment.position, d.vec3f(0, 1, 0)),  );  const alpha = fragment.coverage * GRID.ink;  return d.vec4f(lit * alpha, alpha);};/** Lets part of the sun's light into shadows, so they stay lighter than the rings. */const softened = (point: shading.Point) => {  'use gpu';  return std.mix(SHADE, 1, shadows.filtered(point));};/** A unit vector towards the sun from the south-west, `height` its rise. */const sunAt = (height: number): [number, number, number] => [-0.82, height, 0.5];/** The graticule's lines across the plinth at every `GRID.spacing`, and its border. */const graticule = () => {  const { spacing, half } = GRID;  const count = Math.floor(half / spacing);  const rows: number[][] = [];  for (let k = -count; k <= count; k++) {    rows.push([k * spacing, PRINT, -half, k * spacing, PRINT, half]);    rows.push([-half, PRINT, k * spacing, half, PRINT, k * spacing]);  }  for (const side of [-half, half]) {    rows.push([side, PRINT, -half, side, PRINT, half]);    rows.push([-half, PRINT, side, half, PRINT, side]);  }  return rows;};export const defaults = {  /** Smooths the rings' edges over several frames. */  taa: true,  /** Dashes the soundings below the shoreline. */  dashed: true,  /** Index contours' tube width in world units. */  width: 0.03,  /** How high the sun stands. */  sun: 0.86,};/** * Draws a noise island as contour rings that rise off a slate plinth. One set of lines draws * every ring, its points written by a kernel. Another prints a graticule a point wide on the * plinth, a north arrow and a scale bar lie inlaid in it, and the rings cast the sun's softened * shadow. */export async function createScene(  root: TgpuRoot,  surface: Surface,  options: Partial<typeof defaults> = {},) {  const values = { ...defaults, ...options };  const own = owner();  const screen = own(    targets.create(root, surface, {      hdr: { format: 'rgba16float', sampled: true },      depth: { format: 'depth24plus', sampled: true },    }),  );  const view = cameras.orbit(root, {    size: surface,    target: [0.5, 0.15, -0.45],    distance: 8.1,    yaw: 0.75,    pitch: PITCH[0],    pitchRange: [0.12, 1.45],    distanceRange: [4, 22],  });  // TAA reprojects the last frame by the camera's motion and the scene's depth  const aa = own(    taa.create(root, {      source: () => screen.textures.hdr,      depth: () => screen.textures.depth,      camera: view,    }),  );  const light = own(    lighting.create(root, {      settings: {        sunDirection: sunAt(values.sun),        sunColor: [1, 0.78, 0.55],        sunIntensity: 2.8,        skyTop: [0.16, 0.2, 0.28],        skyBottom: [0.04, 0.035, 0.03],        skyIntensity: 0.8,      },      shadow: { eye: view, cascades: 2, far: 26, filter: 9 },      shading: { sunShadow: softened },    }),  );  // The lines' shading reads the lighting, which fills beneath their own fills  const scene = passes    .create(      root,      screen,      { hdr: passes.color, depth: passes.depth },      { jitter: aa.jitter, clear: [...BACKDROP, 1] },    )    .pipe(view.inject())    .beneath(light.inject());  const pool = own(meshPool.create(root, { capacity: 64 }));  const slab = pool.add(    meshes.roundedBox({      width: PLINTH.half * 2,      height: PLINTH.thickness,      depth: PLINTH.half * 2,      radius: 0.06,    }),  );  const strip = pool.add(meshes.box({ width: 1, height: INLAY * 2, depth: 1 }));  // A cylinder with three sides is a triangular prism, with one corner along +x  const head = pool.add(meshes.cylinder({ radius: 1, height: INLAY * 2, radialSegments: 3 }));  const { east, south, bar, parts } = LEGEND;  const things = own(instances.create(root, pool, { capacity: 4 + parts }));  things.add(slab, { position: [0, -PLINTH.thickness / 2, 0], ...SLATE });  things.add(slab, {    position: [0, -PLINTH.thickness * 1.5, 0],    scale: [1.03, 1, 1.03],    ...BASE,  });  // A brass north arrow and a scale bar of brass and bone, inlaid in the plinth's near corner  const brass = materials.glossy(paint(...BRASS), 0.3);  const bone = materials.glossy(paint(...BONE), 0.3);  const tip = south - bar - 0.2;  const tail = south + 0.2;  const neck = tip + HEAD.radius;  things.add(strip, {    position: [east - 0.5, 0, (tail + neck) / 2],    scale: [0.12, 1, tail - neck],    ...brass,  });  things.add(head, {    position: [east - 0.5, 0, neck],    rotation: HEAD.rotation,    scale: [HEAD.radius, 1, HEAD.radius],    ...brass,  });  for (let k = 0; k < parts; k++) {    things.add(strip, {      position: [east, 0, south - ((k + 0.5) * bar) / parts],      scale: [0.07, 1, bar / parts],      ...(k % 2 ? bone : brass),    });  }  const rows = graticule();  const grid = own(    lines.create(root, {      capacity: rows.length,      length: 2,      units: 'points',      startCap: caps.butt,      endCap: caps.butt,      shade: ink,      label: 'graticule',    }),  );  for (const [k, row] of rows.entries()) grid.write(k, row, { color: linear(BONE) });  const contours = own(createContours(root, values));  const plinth = things.renderer();  const drawn = [scene.add(plinth), scene.add(grid.drawing), scene.add(contours.drawing)];  const casters = [light.shadow.add(plinth), light.shadow.add(contours.drawing)];  const glow = own(bloom.create(root, { source: aa, settings: { threshold: 1.6 } }));  const final = own(    finish.create(root, { format: surface.format, source: aa, tone: tone.aces, glow }),  );  await pipelines.compile([scene, light.shadow, aa, glow, final, contours]);  // Trace the rings once to read their radii for picking  const tracing = root.createCommandEncoder();  contours.trace(tracing);  tracing.submit();  await contours.readRadii();  const settings: Settings<typeof values> = {    values,    knobs: {      taa: { label: 'TAA' },      dashed: { label: 'dashed soundings' },      width: { label: 'ring width', min: 0.005, max: 0.08, step: 0.005 },      sun: { label: 'sun height', min: 0.1, max: 1.5, step: 0.02 },    },    write(key) {      if (key === undefined || key === 'taa') aa.enable(values.taa);      if (key === undefined || key === 'dashed') contours.dashed = values.dashed;      if (key === undefined || key === 'width') contours.width = values.width;      if (key === undefined || key === 'sun') {        light.settings.patch({ sunDirection: sunAt(values.sun) });      }    },  };  // The pointer picks a ring each frame, as the view and the rings move under it  const pointer = { x: 0, y: 0, over: false };  const ray = { origin: [0, 0, 0], direction: [0, 0, 0] };  const pointing: Pointing = {    move(x, y) {      pointer.x = x;      pointer.y = y;      pointer.over = true;    },    leave() {      pointer.over = false;      contours.hover();    },  };  return {    view,    settings,    pointing,    frame(frame: Frame) {      // Tip the view down to the model, and raise the rings, as the scene opens      if (frame.time < TIP) {        view.goal.pitch = std.mix(PITCH[0], PITCH[1], std.smoothstep(0, TIP, frame.time));      }      contours.lift(std.smoothstep(RISE[0], RISE[1], frame.time));      view.update(frame.dt);      if (pointer.over) {        view.rayAt(pointer.x, pointer.y, ray);        contours.hover(ray);      }      contours.trace(frame.encoder);      screen.fit();      things.flush();      light.shadow.record(frame, casters);      scene.record(frame, drawn);      aa.record(frame);      glow.record(frame);      final.record(frame, surface.view(frame));    },    destroy: own.destroy,  };}

contours.ts

import { cameras, lines, materials, shading } from '@typegpu/kit';import { d, std, tgpu, type TgpuCommandEncoder, type TgpuRoot } from 'typegpu';import { heightAt, isIndex, LEVELS, levelOf, liftedOf, REACH, SHORE, tints } from './terrain.ts';/** Points round each ring. */const POINTS = 192;const TOTAL = LEVELS * POINTS;const WORKGROUP = 64;/** Steps inward from `REACH` that find a ring's last crossing, and halvings that refine it. */const MARCH = 48;const HALVINGS = 10;/** Dash and gap along the soundings, in world units. */const DASH = 0.14;const GAP = 0.09;/** The cyan of the ring under the pointer, linear and bright enough to bloom. */const CYAN = d.vec3f(0.25, 1.6, 2.2);/** World units outside the outermost ring that still pick it. */const NEAR_RING = 0.06;/** Rays this near level pick no ring. */const LEVEL = { grazing: 1e-4 };/** Width of an intermediate contour, as a share of an index contour's. */const THIN = 0.6;const Shape = d.struct({  /** Rings rise from the plinth to their heights as this goes from 0 to 1. */  lift: d.f32,  /** Index contours' tube width in world units. */  width: d.f32,  /** The ring under the pointer, or `LEVELS` for none. */  hovered: d.u32,  /** 1 to dash the soundings. */  dashed: d.u32,});const radiusAt = (direction: d.v2f, level: number) => {  'use gpu';  const stride = REACH / MARCH;  let inner = d.f32(0);  let outer = d.f32(0);  for (let step = 0; step < MARCH; step++) {    const r = REACH - d.f32(step) * stride;    if (heightAt(direction * r) >= level) {      inner = r;      outer = r + stride;      break;    }  }  for (let k = 0; k < HALVINGS; k++) {    const middle = (inner + outer) * 0.5;    if (heightAt(direction * middle) >= level) inner = middle;    else outer = middle;  }  return std.min((inner + outer) * 0.5, REACH);};type Ray = Parameters<typeof cameras.hitHeight>[0];/** Ring `ring`'s radius in `radii` towards `point`, between the two ring points either side. */const radiusOf = (radii: Float32Array, ring: number, point: readonly number[]) => {  const turn = ((Math.atan2(point[2]!, point[0]!) / (Math.PI * 2) + 1) % 1) * POINTS;  const i = Math.floor(turn);  const r0 = radii[ring * POINTS + (i % POINTS)]!;  const r1 = radii[ring * POINTS + ((i + 1) % POINTS)]!;  return r0 + (r1 - r0) * (turn - i);};export interface ContoursOptions {  readonly width: number;  readonly dashed: boolean;}/** * Traces the island's contour rings into one set of lines and shades them as satin enamel tubes, * lit by the lighting of the scene they draw in. A kernel finds each ring point's radius by * marching in from the edge to the level's last crossing and halving the step, then a second runs * along each ring for its dashes. */export function createContours(root: TgpuRoot, options: ContoursOptions) {  const shape = root.createUniform(Shape, {    lift: 0,    width: options.width,    hovered: LEVELS,    dashed: options.dashed ? 1 : 0,  });  const enamel = (fragment: lines.Fragment) => {    'use gpu';    const ring = fragment.line;    const tint = tints.$[ring]!;    let material = materials.glossy(tint, 0.38);    if (ring === shape.$.hovered) {      material = materials.glossy(CYAN * 0.1, 0.3);      material.emission = CYAN;    }    const lit = shading.shade(material, shading.pointAt(fragment.position, fragment.normal));    let alpha = fragment.coverage;    if (ring < SHORE && shape.$.dashed === 1) alpha *= lines.dash(fragment, DASH, GAP);    return d.vec4f(lit * alpha, alpha);  };  const rings = lines.create(root, {    capacity: LEVELS,    length: POINTS,    closed: true,    blend: 'opaque',    shade: enamel,    label: 'contours',  });  const trace = tgpu.computeFn({    workgroupSize: [WORKGROUP],    in: { gid: d.builtin.globalInvocationId },  })(({ gid }) => {    'use gpu';    if (gid.x >= TOTAL) return;    const ring = std.intdiv(gid.x, d.u32(POINTS));    const i = gid.x % POINTS;    const angle = (d.f32(i) / POINTS) * Math.PI * 2;    const direction = d.vec2f(std.cos(angle), std.sin(angle));    const r = radiusAt(direction, levelOf(ring));    rings.setPoint(      ring,      i,      lines.Point({        position: d.vec3f(direction.x * r, liftedOf(ring, shape.$.lift), direction.y * r),        width: std.select(shape.$.width * THIN, shape.$.width, isIndex(ring)),        color: d.vec4f(1),        along: 0,      }),    );    if (i === 0) rings.setCount(ring, POINTS);  });  /** Measures each ring's length from its first point, one ring a thread. */  const measure = tgpu.computeFn({    workgroupSize: [WORKGROUP],    in: { gid: d.builtin.globalInvocationId },  })(({ gid }) => {    'use gpu';    if (gid.x >= LEVELS) return;    let along = d.f32(0);    let before = rings.pointAt(gid.x, 0).position.xz;    for (let i = d.u32(1); i < POINTS; i++) {      const point = lines.Point(rings.pointAt(gid.x, i));      along += std.distance(point.position.xz, before);      before = point.position.xz;      point.along = along;      rings.setPoint(gid.x, i, point);    }  });  const tracing = root.pipe(rings.inject()).createComputePipeline({ compute: trace });  const measuring = root.pipe(rings.inject()).createComputePipeline({ compute: measure });  /** Each ring's radius at each point, read back once for picking. */  let radii: Float32Array | undefined;  const values = { lift: 0, width: options.width };  let hovered = LEVELS;  let changed = true;  const hit = [0, 0, 0];  /**   * The ring under `ray`, or `LEVELS` for none. The rings read as the terraces of a solid model,   * so the ray lands on the highest terrace whose ring encloses it, and picks the nearer of that   * terrace's edges. A ring the island hides is never picked.   */  const ringUnder = (ray: Ray, measured: Float32Array) => {    for (let ring = LEVELS - 1; ring >= 0; ring--) {      if (cameras.hitHeight(ray, liftedOf(ring, values.lift), hit, LEVEL) === undefined) continue;      const out = Math.hypot(hit[0], hit[2]);      const edge = radiusOf(measured, ring, hit);      if (out > edge + (ring === 0 ? NEAR_RING : 0)) continue;      if (ring === LEVELS - 1) return ring;      // Higher rings are nested inside lower ones round the one peak      return out - radiusOf(measured, ring + 1, hit) < edge - out ? ring + 1 : ring;    }    return LEVELS;  };  return {    drawing: rings.drawing,    initAsync: () => Promise.all([tracing.initAsync(), measuring.initAsync()]),    /** Reads back the rings' radii, which stay as they rise. */    async readRadii() {      const points = await rings.points!.read();      radii = Float32Array.from(points, ({ position }) => Math.hypot(position.x, position.z));    },    /** Sets how far the rings have risen, from 0 to 1. */    lift(to: number) {      if (to === values.lift) return;      values.lift = to;      changed = true;    },    set width(width: number) {      if (width === values.width) return;      values.width = width;      changed = true;    },    set dashed(dashed: boolean) {      shape.patch({ dashed: dashed ? 1 : 0 });    },    /** Records a compute pass that traces the rings, when their height or width changed. */    trace(encoder: TgpuCommandEncoder) {      if (!changed) return;      changed = false;      shape.patch({ lift: values.lift, width: values.width });      const pass = encoder.beginComputePass();      pass.setPipeline(tracing);      pass.dispatchWorkgroups(Math.ceil(TOTAL / WORKGROUP));      pass.setPipeline(measuring);      pass.dispatchWorkgroups(1);      pass.end();    },    /**     * Picks out a ring under `ray`: of the highest terrace whose ring encloses where the ray lands,     * the nearer of its two edges. Picks none without a ray.     */    hover(ray?: Ray) {      const picked = ray && radii ? ringUnder(ray, radii) : LEVELS;      if (picked === hovered) return;      hovered = picked;      shape.patch({ hovered });    },    destroy() {      rings.destroy();    },  };}

terrain.ts

import { hexToRgb, srgbToLinear } from '@typegpu/color';import { perlin2d } from '@typegpu/noise';import { d, std, tgpu } from 'typegpu';/** Contour levels, the deepest first: ten soundings, the shoreline, then the land. */export const LEVELS = 41;/** The shoreline's level index; those below it are soundings. */export const SHORE = 10;/** Every `INDEX`th level from the shoreline is an index contour, drawn bold. */const INDEX = 5;/** Height between levels, in the terrain's units and in world units once lifted. */const STEP = 0.04;/** World units the lowest ring sits over the plinth's top. */const BASE = 0.05;/** World units from the peak to the farthest a ring reaches. */export const REACH = 4.3;/** The terrain height at level `ring`. */export const levelOf = (ring: number) => {  'use gpu';  return (d.f32(ring) - SHORE) * STEP;};/** World height of ring `ring` lifted by `lift`, from resting on the plinth at 0 to its level at 1. */export const liftedOf = (ring: number, lift: number) => {  'use gpu';  return BASE + d.f32(ring) * STEP * lift;};/** * The island's height at `p` on the plinth: one peak, its slopes roughened by noise that fades * near the summit so every level closes round it, falling away before `REACH` so every sounding * closes inside it. */export const heightAt = (p: d.v2f) => {  'use gpu';  const r = std.length(p);  const peak = 2.1 * std.exp(-(r * r) / 5.2) - 0.55;  const rough = perlin2d.sample(p * 0.42 + 3.1) * 0.75 + perlin2d.sample(p * 1.05 - 7.4) * 0.3;  return peak + rough * std.smoothstep(0.4, 2.2, r) - std.smoothstep(2.4, REACH, r) * 0.75;};/** Hypsometric stops in sRGB: the deep soundings, the shallow ones, then the land up to the peak. */const DEEP = [0x1d4e89, 0x4f92c4].map((hex) => hexToRgb(hex));const LAND = [  0x3f8f5a, 0x67a65b, 0x93b95b, 0xbcc566, 0xd9c36c, 0xd5a159, 0xbf7d4d, 0xa4664a, 0x8c5446,].map((hex) => hexToRgb(hex));/** The shoreline's pale sea glass. */const GLASS = hexToRgb(0xd6ece0);/** The slate that intermediate contours lean towards. */const MUTE = hexToRgb(0x3a4048);/** The colour at `t` from 0 to 1 along `stops`. */const along = (stops: d.v3f[], t: number) => {  const at = t * (stops.length - 1);  const k = Math.min(Math.floor(at), stops.length - 2);  return std.mix(stops[k]!, stops[k + 1]!, at - k);};/** Whether level `ring` is an index contour. */export const isIndex = (ring: number) => {  'use gpu';  return (ring + LEVELS * INDEX - SHORE) % INDEX === 0;};/** Level `ring`'s tint in sRGB. */const tintOf = (ring: number) => {  if (ring === SHORE) return GLASS;  const srgb =    ring < SHORE      ? along(DEEP, ring / (SHORE - 1))      : along(LAND, (ring - SHORE - 1) / (LEVELS - SHORE - 2));  return isIndex(ring) ? srgb : std.mix(srgb, MUTE, 0.45);};/** * Hypsometric tints, the deepest sounding first. The shoreline is pale sea glass, and the * intermediate contours between index contours lean towards slate. */export const tints = tgpu.const(  d.arrayOf(d.vec3f, LEVELS),  Array.from({ length: LEVELS }, (_, ring) => srgbToLinear(tintOf(ring))),);