SDF Flow

SDF to flowmap via Jump Flooding

Initializing WebGPU...

Quick Start

Loading...

Source

Loading...

Documentation

sdf-flow

Converts a binary shape (black/white image) into a flowmap texture using signed distance fields and the Jump Flooding Algorithm. The output encodes flow direction and strength — sample it from a particle system or shader to make things flow around shapes.

Quick Start

import { createSdfFlow } from './gpu-modules/sdf-flow/sdf-flow';

const flow = createSdfFlow(device);

const flowmap = device.createTexture({
  size: [512, 512],
  format: 'rgba8unorm',
  usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING
});

// Source must be a GPUTexture with TEXTURE_BINDING usage
flow.render(shapeTexture, flowmap, {
  bevelWidth: 16,
  threshold: 128
});

// flowmap now contains the result — bind it in your pipeline

flow.destroy();

Output Format

The output is an rgba8unorm texture:

  • R — Flow direction X (0.5 = no flow, 0 = left, 1 = right)
  • G — Flow direction Y (0.5 = no flow, 0 = up, 1 = down)
  • B — 0.5 (neutral)
  • A — Flow strength (0 = no flow, 1 = max flow)

Values are premultiplied by alpha, so transparent areas are (0, 0, 0, 0).

API

createSdfFlow(device)

Returns { render, destroy }. No options — dimensions are derived from the source texture. The renderer auto-resizes internal JFA buffers if the source texture dimensions change.

render(source, target, params?)

  • sourceGPUTexture containing the shape (must have TEXTURE_BINDING usage)
  • targetGPUTexture to write the flowmap into (must have STORAGE_BINDING usage, format rgba8unorm)
  • params.bevelWidth — Pixel radius of the flow falloff zone. Default 16.
  • params.threshold — Brightness threshold (0–255) to binarize the input. Default 128.
  • params.smoothing — Central-difference step radius. Higher = smoother gradients. Default 1.
  • params.invert — Swap inside/outside. Default false.

How It Works

  1. Threshold — Binarize the input texture into inside/outside regions, seeding two JFA textures.
  2. Jump Flooding — Run JFA separately for inside and outside seeds. Each pixel finds its nearest boundary pixel in O(log N) passes.
  3. SDF + Gradient — Compute the signed distance (outside distance minus inside distance), apply smoothstep bevel, then derive flow direction via central differences.

How to Modify

  • Change the output format: Edit the storageTexture format in the bind group layout and the texture_storage_2d declaration in flowmap.wgsl.
  • Adjust the bevel curve: The height_at function in flowmap.wgsl uses a smoothstep. Replace with a linear ramp or custom curve.
  • Use as a distance field only: Modify flowmap.wgsl to output the raw SDF value instead of the gradient. Store sdf_at(coords) directly.
  • Multiple shapes: Render multiple shapes into the source texture before passing it to render.

Further Reading

Rationale

Flowmaps tell particles, fluids, or UV animations which direction to move. Generating them from a shape is a common need: water flowing around rocks, wind deflected by buildings, UI particles avoiding text. The standard approach is to compute a signed distance field and take its gradient — but doing this on the GPU at interactive rates requires a parallel distance transform algorithm.

This module implements the full pipeline: threshold → JFA (parallel distance transform) → SDF → gradient → flow encoding. The output is a standard flowmap texture you can sample from any shader.

Original Research

  • Rong & Tan (2006) — Jump Flooding in GPU with Applications to Voronoi Diagram and Distance Transform — The original JFA paper. Describes the O(log N) parallel algorithm for computing nearest-seed maps on the GPU. https://dl.acm.org/doi/10.1145/1124772.1124848

  • Felzenszwalb & Huttenlocher (2012) — Distance Transforms of Sampled Functions — The CPU reference algorithm (separable 1D passes). Useful for understanding the problem JFA solves in parallel. https://cs.brown.edu/people/pfelzens/dt/

Existing Implementations

Further Learning