Voronoi

Voronoi / Worley cellular noise

Quick Start

import { createVoronoi } from './webgpu-market/voronoi/voronoi';

const voronoi = createVoronoi(device);

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

// Each frame
voronoi.update(texture, {
  time: elapsed,
  scale: 8.0,
  mode: 'f1',
  metric: 'euclidean',
  jitter: 0.9,
});

// texture is now filled — bind it in your pipeline
voronoi.destroy();
Source
// Voronoi / Worley noise compute shader
// Generates cellular noise patterns by finding distances to random feature points.
// Supports F1 (nearest), F2 (second nearest), and F2-F1 (cell edges) output modes,
// with Euclidean, Manhattan, and Chebyshev distance metrics.

struct Uniforms {
  time: f32,
  scale: f32,
  jitter: f32,
  width: f32,
  height: f32,
  mode: u32,    // 0 = F1, 1 = F2, 2 = F2-F1
  metric: u32,  // 0 = euclidean, 1 = manhattan, 2 = chebyshev
  _pad: u32,
  offset_x: f32,
  offset_y: f32,
  _pad2: u32,
  _pad3: u32,
}

@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var output: texture_storage_2d<rgba8unorm, write>;

// Hash function for deterministic random points per cell.
// Based on hash-without-sine by Dave Hoskins.
fn hash2(p: vec2f) -> vec2f {
  var p3 = fract(vec3f(p.x, p.y, p.x) * vec3f(0.1031, 0.1030, 0.0973));
  p3 += dot(p3, p3.yzx + 33.33);
  return fract((p3.xx + p3.yz) * p3.zy);
}

// Distance calculation using the selected metric
fn cell_distance(a: vec2f, b: vec2f) -> f32 {
  let d = abs(a - b);
  if (u.metric == 1u) {
    // Manhattan distance
    return d.x + d.y;
  }
  if (u.metric == 2u) {
    // Chebyshev distance
    return max(d.x, d.y);
  }
  // Euclidean distance
  return length(a - b);
}

// Voronoi evaluation: searches 3x3 cell neighborhood around the sample point.
// Returns vec2f(F1, F2) — distances to the nearest and second nearest feature points.
fn voronoi(p: vec2f) -> vec2f {
  let cell = floor(p);
  let local = fract(p);

  var f1 = 999.0;
  var f2 = 999.0;

  // Search the 3x3 neighborhood of cells
  for (var y = -1; y <= 1; y++) {
    for (var x = -1; x <= 1; x++) {
      let neighbor = vec2f(f32(x), f32(y));

      // Deterministic random point within this cell.
      // The hash seed is fixed per cell — animation comes from sine/cosine offset, not re-hashing.
      let cell_coord = cell + neighbor;
      let rand = hash2(cell_coord);

      // Animate by smoothly orbiting around the base position
      let phase = hash2(cell_coord + vec2f(127.1, 311.7));
      let offset = vec2f(
        sin(u.time + phase.x * 6.28) * 0.5,
        cos(u.time + phase.y * 6.28) * 0.5
      );

      // Jitter controls how far the point can move from the cell center.
      // jitter=0 places points at cell centers (regular grid); jitter=1 is fully random.
      let point = neighbor + (rand + offset * 0.3) * u.jitter;

      let dist = cell_distance(local, point);

      // Maintain sorted order: f1 <= f2
      if (dist < f1) {
        f2 = f1;
        f1 = dist;
      } else if (dist < f2) {
        f2 = dist;
      }
    }
  }

  return vec2f(f1, f2);
}

@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) id: vec3u) {
  let dims = vec2u(u32(u.width), u32(u.height));
  if (id.x >= dims.x || id.y >= dims.y) {
    return;
  }

  // Normalize pixel coordinates and apply scale
  let uv = vec2f(f32(id.x), f32(id.y)) / vec2f(u.width, u.height);
  let p = uv * u.scale + vec2f(u.offset_x, u.offset_y);

  let distances = voronoi(p);

  // Select output value based on mode
  var value: f32;
  if (u.mode == 0u) {
    // F1: distance to nearest point (dark at cell centers, bright at edges)
    value = distances.x;
  } else if (u.mode == 1u) {
    // F2: distance to second nearest point
    value = distances.y;
  } else {
    // F2-F1: highlights cell boundaries (thin bright lines between cells)
    value = distances.y - distances.x;
  }

  // Normalize to [0, 1] range — distances can exceed 1.0 depending on metric and scale,
  // so clamp after a reasonable normalization
  value = clamp(value, 0.0, 1.0);

  textureStore(output, vec2i(id.xy), vec4f(value, value, value, 1.0));
}
Documentation

Voronoi

Voronoi / Worley noise generator. Writes animated cellular noise to a caller-provided GPUTexture via a compute shader.

API

createVoronoi(device)

Returns a Voronoi instance. No options — dimensions come from the target texture.

voronoi.update(target, options?)

Dispatches the compute shader to write cellular noise into the target texture.

  • targetGPUTexture to write into (must have STORAGE_BINDING usage, format rgba8unorm)
Option Type Default Description
time number 0 Animation time (drives feature point movement)
scale number 8.0 Number of cells across the texture
jitter number 0.9 Point randomness (0 = grid, 1 = fully random)
offset [number, number] [0, 0] UV offset — shift to get a different pattern
mode 'f1' | 'f2' | 'f2-f1' 'f1' Distance output mode
metric 'euclidean' | 'manhattan' | 'chebyshev' 'euclidean' Distance function

voronoi.destroy()

Releases the uniform buffer. Does not destroy the target texture.

Further Reading

Further Reading

Resources on Voronoi noise, Worley noise, and cellular texture generation.

Original Research

  • Steven Worley, "A Cellular Texture Basis Function" (SIGGRAPH 1996) The original paper introducing cellular noise. Defines the F1, F2, F3, F4 distance basis functions and demonstrates their use for procedural textures like stone, water caustics, and organic patterns. https://dl.acm.org/doi/10.1145/237170.237267

  • Georgy Voronoi, "Nouvelles applications des parametres continus a la theorie des formes quadratiques" (1908) The mathematical foundation of Voronoi diagrams — partitioning a plane into regions based on distance to a set of seed points. Worley noise is essentially a rasterized Voronoi diagram with distance-based output.

Practical Guides

  • Inigo Quilez, "Voronoi Edges" GPU-friendly Voronoi implementation with exact edge distance computation. Shows how to efficiently compute F1, F2, and cell edges in a fragment shader. https://iquilezles.org/articles/voronoilines/

  • Inigo Quilez, "Smooth Voronoi" Technique for computing smooth (differentiable) Voronoi patterns by blending distance contributions instead of taking a hard minimum. https://iquilezles.org/articles/smoothvoronoi/

  • The Book of Shaders, "Cellular Noise" chapter by Patricio Gonzalez Vivo and Jen Lowe Interactive tutorial covering Voronoi patterns in shaders, with live code examples showing F1, F2, and distance metric variations. https://thebookofshaders.com/12/

Distance Metrics

  • Wikipedia, "Minkowski Distance" The generalized distance metric that encompasses Euclidean (p=2), Manhattan (p=1), and Chebyshev (p=infinity) as special cases. Understanding the Lp norm family helps when experimenting with non-standard cell shapes. https://en.wikipedia.org/wiki/Minkowski_distance

Hash Functions

  • Dave Hoskins, "Hash without Sine" (Shadertoy) The arithmetic hash functions used in this module. Avoids trigonometric functions for better GPU performance and cross-platform consistency. https://www.shadertoy.com/view/4djSRW

Applications

General References

  • Texture & Modeling: A Procedural Approach (Ebert et al., 3rd edition) Chapter on cellular textures covers Worley noise in depth, including multi-frequency layering, distance combinations, and practical applications in procedural material design.

  • Stefan Gustavson, "Simplex noise demystified" (2005) While focused on simplex noise, includes discussion of the relationship between different noise types and how cellular noise complements gradient-based approaches. https://cgvr.cs.uni-bremen.de/teaching/cg_literatur/simplexnoise.pdf