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 }, },}struct Uniforms { time: f32, resolution: vec2<f32>, cells: f32,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> U: Uniforms;
fn hash2(c: vec2<f32>) -> vec2<f32> { let _cse0 = vec2<f32>(dot(c, vec2<f32>(127.1, 311.7)), dot(c, vec2<f32>(269.5, 183.3))); return fract((vec2<f32>(sin(_cse0.x), sin(_cse0.y)) * 43758.5453));}
@vertexfn vs(@builtin(vertex_index) vi: u32) -> VsOut { let _cse0 = ((f32((vi & 1u)) * 4.0) - 1.0); let _cse1 = ((f32((vi >> 1u)) * 4.0) - 1.0); return VsOut(vec4<f32>(_cse0, _cse1, 0.0, 1.0), vec2<f32>(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));}
@fragmentfn fs(vo: VsOut) -> @location(0) vec4<f32> { let _licm0 = U.time; let _cse0 = (vo.uv * U.cells); var _av0: f32 = 8.0; for (var _v0: i32 = -1; (_v0 <= 1); _v0 = (_v0 + 1)) { for (var _v1: i32 = -1; (_v1 <= 1); _v1 = (_v1 + 1)) { let _v2 = vec2<f32>(f32(_v1), f32(_v0)); let _v3 = hash2((floor(_cse0) + _v2)); _av0 = min(_av0, distance(fract(_cse0), ((_v2 + ((_v3 * 0.5) + 0.25)) + (vec2<f32>(sin((_licm0 + (_v3.x * 6.283))), cos((_licm0 + (_v3.y * 6.283)))) * 0.18)))); } } return vec4<f32>(((vec3<f32>((_av0 * _av0)) * vec3<f32>(0.35, 0.6, 1.0)) + vec3<f32>(0.02, 0.03, 0.06)), 1.0);}#version 300 esprecision highp float;precision highp int;
struct VsOut { vec4 pos; vec2 uv;};out vec2 uv;
VsOut vs_impl(uint vi) { float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0); float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0); return VsOut(vec4(_cse0, _cse1, 0.0, 1.0), vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));}
void main() { VsOut _out = vs_impl(uint(gl_VertexID)); gl_Position = _out.pos; uv = _out.uv;}#version 300 esprecision highp float;precision highp int;
struct VsOut { vec4 pos; vec2 uv;};layout(std140) uniform Uniforms { float time; vec2 resolution; float cells;} U;vec2 hash2(vec2 c);vec2 hash2(vec2 c) { vec2 _cse0 = vec2(dot(c, vec2(127.1, 311.7)), dot(c, vec2(269.5, 183.3))); return fract((vec2(sin(_cse0.x), sin(_cse0.y)) * 43758.5453));}in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_impl(VsOut vo) { float _licm0 = U.time; vec2 _cse0 = (vo.uv * U.cells); float _av0 = 8.0; for (int _v0 = -1; (_v0 <= 1); _v0 = (_v0 + 1)) { for (int _v1 = -1; (_v1 <= 1); _v1 = (_v1 + 1)) { vec2 _v2 = vec2(float(_v1), float(_v0)); vec2 _v3 = hash2((floor(_cse0) + _v2)); _av0 = min(_av0, distance(fract(_cse0), ((_v2 + ((_v3 * 0.5) + 0.25)) + (vec2(sin((_licm0 + (_v3.x * 6.283))), cos((_licm0 + (_v3.y * 6.283)))) * 0.18)))); } } return vec4(((vec3((_av0 * _av0)) * vec3(0.35, 0.6, 1.0)) + vec3(0.02, 0.03, 0.06)), 1.0);}
void main() { VsOut vo; vo.pos = gl_FragCoord; vo.uv = uv; _ret = fs_impl(vo);}{ "bindGroups": [ { "group": 0, "entries": [ { "group": 0, "binding": 0, "name": "U", "space": "uniform", "resourceKind": "uniform-buffer", "structName": "Uniforms" } ] } ], "uniforms": [ { "name": "Uniforms", "size": 32, "align": 16, "fields": [ { "name": "time", "type": "f32", "offset": 0, "align": 4, "size": 4 }, { "name": "resolution", "type": "vec2<f32>", "offset": 8, "align": 8, "size": 8 }, { "name": "cells", "type": "f32", "offset": 16, "align": 4, "size": 4 } ] } ], "storage": [], "entries": [ { "name": "vs", "stage": "vertex", "inputs": [ "u32" ], "output": "struct:VsOut" }, { "name": "fs", "stage": "fragment", "inputs": [ "struct:VsOut" ], "output": "vec4<f32>" } ], "overrides": []}
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.