X-GIS
Examples
generic voronoi.ts

Voronoi

Animated cellular noise — each fragment shaded by the distance to its nearest of a grid of orbiting feature points (a 3×3 neighbour scan). Built on the `distance` builtin + nested loops.

Live
0.0s

One source, compiled every way

The DSL source below, compiled to WebGPU (WGSL), WebGL2 (GLSL ES 3.00 — two stages), and a reflection JSON the host binds from. Nothing but the DSL tab is hand-written.

// ═══ @xgis/shader-dsl example — animated Voronoi (cellular noise) ═══
//
// The classic ShaderToy cellular pattern: tile the plane into cells, scatter one
// animated feature point per cell, and shade each fragment by the DISTANCE to its
// nearest point (a 3×3 neighbour scan). Showcases the `distance` builtin + the DSL's
// nested `Loop`. One source → WGSL (WebGPU) + GLSL ES 3.00 (WebGL2) + Reflection.
import {
fn,
module,
i32,
f32,
toF32,
vec2,
vec3,
vec4,
sin,
cos,
floor,
fract,
dot,
min,
distance,
Loop,
Let,
f32T,
vec2fT,
} from '../src/index.ts'
import { VsOut, vs, fullscreenUniforms } from './_fullscreen.ts'
import type { ShaderExample } from './_shared.ts'
const U = fullscreenUniforms({ cells: f32T })
// Hash a cell coordinate → a stable point in [0,1]² (the per-cell feature seed).
const hash2 = fn('hash2', { c: vec2fT }, ({ c }) => {
const h = vec2(dot(c, vec2(127.1, 311.7)), dot(c, vec2(269.5, 183.3)))
return fract(vec2(sin(h.x), sin(h.y)).mul(43758.5453))
})
const fs = fn(
'fs',
{ vo: VsOut },
({ vo }) => {
const uv = vo.uv
const p = uv.mul(U.field.cells)
const cell = floor(p)
const f = fract(p)
const md = f32(8) // nearest-point distance accumulator
// 3×3 neighbour scan — the nearest feature point may live in an adjacent cell.
Loop(
i32(-1),
(j) => j.le(1),
(j) => {
Loop(
i32(-1),
(i) => i.le(1),
(i) => {
const g = Let(vec2(toF32(i), toF32(j)))
const seed = Let(hash2({ c: cell.add(g) })) // materialise once: the loop counters are mutated `var`s, so CSE can't hoist the hash — without Let the 3 `seed.*` reads re-call hash2() per iteration
// animate each point on a small orbit around its cell so the pattern shimmers
const pt = g
.add(seed.mul(0.5).add(0.25))
.add(
vec2(
sin(U.field.time.add(seed.x.mul(6.283))),
cos(U.field.time.add(seed.y.mul(6.283))),
).mul(0.18),
)
md.assign(min(md, distance(f, pt)))
},
)
},
)
// tint the distance field: dark cores, cool cell walls
const c = vec3(md.mul(md))
.mul(vec3(0.35, 0.6, 1.0))
.add(vec3(0.02, 0.03, 0.06))
return vec4(c, 1)
},
{ stage: 'fragment', retAttr: '@location(0)' },
)
// `hash2` is called via its handle in `fs`, so module() collects it transitively — funcs lists only the entry points.
const voronoiModule = module({
structs: [U.struct, VsOut.decl],
bindings: [U.binding],
funcs: [vs, fs],
})
export const voronoi: ShaderExample = {
id: 'voronoi',
title: 'Voronoi',
blurb:
'Animated cellular noise — each fragment shaded by the distance to its nearest of a grid of orbiting feature points (a 3×3 neighbour scan). Built on the `distance` builtin + nested loops.',
category: 'generic',
file: 'voronoi.ts',
module: voronoiModule,
renderable: true,
controls: {
time: { kind: 'time' },
resolution: { kind: 'resolution' },
cells: { kind: 'slider', label: 'Cell density', min: 2, max: 16, step: 1, value: 6 },
},
}

Run it locally: npx tsx examples/print.ts voronoi — prints the WGSL, GLSL, reflection. Source: GitHub.

New to the IR? See Concepts; the full authoring + emit surface is in the API reference.