sdf-morph@0.1.0

SDF morph

The distance field itself, drawn as contour lines: a rounded box and an orbiting disc under a smooth boolean, a smaller disc carving in. The blue isoline is the shape's edge, distance zero.

Controls

1.00
0.090
0.035
#3391ff

Imports

What this entry shader composes; every module resolves from npm.

import { sdCircle, sdRoundedBox2 } from "@vshaders/sdf/2d";

import { opSmoothSubtract, opSmoothUnion } from "@vshaders/sdf/ops";

import { rotate2d } from "@vgpu/wgsl-std/math";

Source

The full entry shader, sdf-morph.wgsl. Copy it and it is yours.

// effect 002 · sdf-morph — the distance field itself, drawn as contour lines.
// A rounded box and an orbiting disc under a smooth boolean, with a smaller
// disc carving into the result. The blue zero isoline is the shape's edge.
// Display-referred on purpose: this is a technical drawing, not a photo.

import { sdCircle, sdRoundedBox2 } from "@vshaders/sdf/2d";
import { opSmoothSubtract, opSmoothUnion } from "@vshaders/sdf/ops";
import { rotate2d } from "@vgpu/wgsl-std/math";

// Tunable members follow resolution/time. Defaults live in lib/effects.ts and
// must be set by the runner: an unset uniform member reads as zero.
struct Uniforms {
  resolution: vec2f,
  time: f32,
  // Multiplies time in every animation term.
  speed: f32,
  // Zero-isoline color, display-referred like the rest of this effect.
  accentColor: vec3f,
  // opSmoothUnion band between box and disc, in field units.
  smoothness: f32,
  // Distance between contour lines, in field units.
  lineSpacing: f32,
}

@group(0) @binding(0) var<uniform> uniforms: Uniforms;

@fragment
fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
  let res = max(uniforms.resolution, vec2f(1.0));
  let uv = position.xy / res;
  let aspect = res.x / res.y;
  let p = (uv - vec2f(0.5)) * vec2f(aspect, 1.0);
  let t = uniforms.time * uniforms.speed;

  let box = sdRoundedBox2(rotate2d(p, t * 0.2), vec2f(0.17), 0.05);
  let orbit = vec2f(cos(t * 0.5), sin(t * 0.5)) * 0.24;
  let disc = sdCircle(p - orbit, 0.11);
  let blob = opSmoothUnion(box, disc, uniforms.smoothness);
  let cutter = sdCircle(p - vec2f(cos(t * 0.31) * 0.12, sin(t * 0.43) * 0.13), 0.08);
  let field = opSmoothSubtract(blob, cutter, 0.06);

  // Screen-space-constant line widths via fwidth.
  let w = max(fwidth(field), 1e-5);
  // The contour fract() divides by the spacing, so clamp it away from zero.
  let spacing = max(uniforms.lineSpacing, 1e-4);
  let contour = abs(fract(field / spacing + 0.5) - 0.5) * spacing;
  let lines = 1.0 - smoothstep(0.5 * w, 1.8 * w, contour);
  let zeroLine = 1.0 - smoothstep(w, 3.5 * w, abs(field));
  let inside = 1.0 - smoothstep(0.0, w, field);

  let ink = vec3f(0.0, 0.0, 0.0);
  let faint = vec3f(0.19, 0.20, 0.23);
  let fill = vec3f(0.075, 0.08, 0.10);

  var color = ink;
  color = mix(color, fill, inside);
  color = mix(color, faint, lines * mix(0.55, 1.0, inside));
  color = mix(color, uniforms.accentColor, zeroLine);
  return vec4f(color, 1.0);
}