Examples
generic raymarch-boxes.ts
Raymarched box field
Domain repetition — one rounded-box SDF floor-modded into an infinite lattice, flown through forever. Move the pointer to look around the corridor; a reusable scene() helper serves the march and the six-tap normal. Fly speed is live.
Live
Pointer-interactive
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 — raymarched box field (domain repetition) ═══//// One rounded-box SDF + floor-mod domain repetition = an infinite lattice of// boxes, flown through forever. The step up from raymarch-sphere.ts: a reusable// scene-SDF helper fn (called by the march AND six more times for the// finite-difference normal), per-cell hashing for colour, and exponential fog.// Domain repetition uses `mod` (#839) — the portable FLOOR-mod; WGSL `%` is// trunc-mod and GLSL `%` is integer-only, so both would break on negatives.// WGSL (WebGPU) + GLSL ES 3.00 (WebGL2).
import { fn, module, u32, f32, vec3, vec4, sin, cos, floor, fract, dot, max, mix, abs, exp, mod, normalize, length, Loop, If, Break, Let, f32T, vec3fT, vec4fT,} from '../src/index.ts'import { VsOut, vs, fullscreenUniforms, screenCoords } from './_fullscreen.ts'import type { ShaderExample } from './_shared.ts'const U = fullscreenUniforms({ speed: f32T, mouse: vec4fT })
// scene SDF: a rounded box repeated every 2.6 units in all three axes —// mod's floor-mod keeps WGSL and GLSL agreeing on negative coordinates.const scene = fn('scene', { p: vec3fT }, ({ p }) => { const q = Let(mod(p, 2.6).sub(vec3(1.3))) const b = abs(q).sub(vec3(0.62)) return length(max(b, vec3(0, 0, 0))).sub(0.14)})
const palette = fn('palette', { t: f32T }, ({ t }) => { const ph = vec3(0.0, 0.33, 0.67) return vec3(0.5).add(cos(t.add(ph).mul(6.283)).mul(0.5))})
const fs = fn( 'fs', { vo: VsOut }, ({ vo }) => { const time = U.field.time const res = U.field.resolution const ndc = screenCoords(vo.uv, res) // camera: weaving gently down the corridor BETWEEN the boxes. Boxes are // centred at 1.3 + 2.6k per axis and reach ±0.76, so the free corridor is // ±0.54 around the axis — a ±0.4 wobble never enters a box. const ro = Let( vec3( sin(time.mul(0.23)).mul(0.4), cos(time.mul(0.2)).mul(0.4), time.mul(U.field.speed).neg(), ), ) const rd0 = Let(normalize(vec3(ndc.x, ndc.y, f32(1.6).neg()))) // pointer looks around: yaw (about y) from pointer x, pitch (about x) from // pointer y. m.w = 0 (never touched) makes both angles exactly 0, so gates // and thumbnails see the straight-down-the-corridor view. const m = U.field.mouse const yaw = Let(m.x.div(res.x).sub(0.5).mul(1.6).mul(m.w)) const pit = Let(m.y.div(res.y).sub(0.5).mul(m.w)) const cy = Let(cos(yaw)) const sy = Let(sin(yaw)) const cp = Let(cos(pit)) const sp = Let(sin(pit)) // rotY(yaw) then rotX(pitch) const rx = Let(rd0.x.mul(cy).add(rd0.z.mul(sy))) const rz1 = Let(rd0.z.mul(cy).sub(rd0.x.mul(sy))) const ry = Let(rd0.y.mul(cp).sub(rz1.mul(sp))) const rz = Let(rz1.mul(cp).add(rd0.y.mul(sp))) const rd = Let(vec3(rx, ry, rz)) const t = f32(0) const hit = f32(0) Loop( u32(0), (i) => i.lt(u32(90)), () => { const p = ro.add(rd.mul(t)) const d = Let(scene({ p })) If(d.lt(0.002), () => { hit.assign(1) Break() }) t.assign(t.add(d.mul(0.9))) If(t.gt(30), () => { Break() }) }, ) const bg = Let(vec3(0.04, 0.05, 0.09).add(vec3(0.02, 0.04, 0.08).mul(vo.uv.y))) const col = bg.add(vec3(0, 0, 0)) If(hit.gt(0.5), () => { const p = Let(ro.add(rd.mul(t))) // finite-difference normal — six more SDF taps through the helper const e = 0.0015 const n = Let( normalize( vec3( scene({ p: p.add(vec3(e, 0, 0)) }).sub(scene({ p: p.sub(vec3(e, 0, 0)) })), scene({ p: p.add(vec3(0, e, 0)) }).sub(scene({ p: p.sub(vec3(0, e, 0)) })), scene({ p: p.add(vec3(0, 0, e)) }).sub(scene({ p: p.sub(vec3(0, 0, e)) })), ), ), ) const ld = normalize(vec3(0.5, 0.8, 0.3)) const diff = max(dot(n, ld), 0) // per-cell tint from the cell id const id = floor(p.div(2.6)) const hcell = fract( sin(id.x.mul(12.9898).add(id.y.mul(78.233)).add(id.z.mul(37.719))).mul(43758.5453), ) const surf = palette({ t: hcell }).mul(diff.mul(0.8).add(0.16)) const fog = exp(t.mul(0.09).neg()) col.assign(mix(bg, surf, fog)) }) return vec4(col, 1) }, { stage: 'fragment', retAttr: '@location(0)' },)
const boxesModule = module({ structs: [U.struct, VsOut.decl], bindings: [U.binding], funcs: [vs, fs],})
export const raymarchBoxes: ShaderExample = { id: 'raymarch-boxes', title: 'Raymarched box field', blurb: 'Domain repetition — one rounded-box SDF floor-modded into an infinite lattice, flown through forever. Move the pointer to look around the corridor; a reusable scene() helper serves the march and the six-tap normal. Fly speed is live.', category: 'generic', file: 'raymarch-boxes.ts', module: boxesModule, renderable: true, controls: { time: { kind: 'time' }, resolution: { kind: 'resolution' }, speed: { kind: 'slider', label: 'Fly speed', min: 0, max: 3, step: 0.1, value: 1.4 }, mouse: { kind: 'mouse' }, },}struct Uniforms { time: f32, resolution: vec2<f32>, speed: f32, mouse: vec4<f32>,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> U: Uniforms;
fn scene(p: vec3<f32>) -> f32 { let _v0 = ((p - 2.6 * floor(p / 2.6)) - vec3<f32>(1.3)); return (length(max((abs(_v0) - vec3<f32>(0.62)), vec3<f32>(0.0, 0.0, 0.0))) - 0.14);}
fn palette(t: f32) -> vec3<f32> { return (vec3<f32>(0.5) + (cos(((t + vec3<f32>(0.0, 0.33, 0.67)) * 6.283)) * 0.5));}
@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 _cse0 = vec2<f32>((((vo.uv.x * 2.0) - 1.0) * (U.resolution.x / U.resolution.y)), ((vo.uv.y * 2.0) - 1.0)); let _cse1 = vec3<f32>(0.0015, 0.0, 0.0); let _cse2 = vec3<f32>(0.0, 0.0015, 0.0); let _cse3 = vec3<f32>(0.0, 0.0, 0.0015); let _v0 = vec3<f32>((sin((U.time * 0.23)) * 0.4), (cos((U.time * 0.2)) * 0.4), (-(U.time * U.speed))); let _v1 = normalize(vec3<f32>(_cse0.x, _cse0.y, -1.6)); let _v2 = ((((U.mouse.x / U.resolution.x) - 0.5) * 1.6) * U.mouse.w); let _v3 = (((U.mouse.y / U.resolution.y) - 0.5) * U.mouse.w); let _v4 = cos(_v2); let _v5 = sin(_v2); let _v6 = cos(_v3); let _v7 = sin(_v3); let _v8 = ((_v1.x * _v4) + (_v1.z * _v5)); let _v9 = ((_v1.z * _v4) - (_v1.x * _v5)); let _v10 = ((_v1.y * _v6) - (_v9 * _v7)); let _v11 = ((_v9 * _v6) + (_v1.y * _v7)); let _v12 = vec3<f32>(_v8, _v10, _v11); var _av0: f32 = 0.0; var _av1: f32 = 0.0; for (var _v13: u32 = 0u; (_v13 < 90u); _v13 = (_v13 + 1u)) { let _v14 = scene((_v0 + (_v12 * _av1))); if ((_v14 < 0.002)) { _av0 = 1.0; break; } _av1 = (_av1 + (_v14 * 0.9)); if ((_av1 > 30.0)) { break; } } let _v15 = (vec3<f32>(0.04, 0.05, 0.09) + (vec3<f32>(0.02, 0.04, 0.08) * vo.uv.y)); var _av2: vec3<f32> = (_v15 + vec3<f32>(0.0, 0.0, 0.0)); if ((_av0 > 0.5)) { let _v16 = (_v0 + (_v12 * _av1)); let _v17 = normalize(vec3<f32>((scene((_v16 + _cse1)) - scene((_v16 - _cse1))), (scene((_v16 + _cse2)) - scene((_v16 - _cse2))), (scene((_v16 + _cse3)) - scene((_v16 - _cse3))))); let _lc0 = floor((_v16 / 2.6)); _av2 = mix(_v15, (palette(fract((sin((((_lc0.x * 12.9898) + (_lc0.y * 78.233)) + (_lc0.z * 37.719))) * 43758.5453))) * ((max(dot(_v17, normalize(vec3<f32>(0.5, 0.8, 0.3))), 0.0) * 0.8) + 0.16)), exp((-(_av1 * 0.09)))); } return vec4<f32>(_av2, 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 speed; vec4 mouse;} U;float scene(vec3 p);vec3 palette(float t);float scene(vec3 p) { vec3 _v0 = (mod(p, 2.6) - vec3(1.3)); return (length(max((abs(_v0) - vec3(0.62)), vec3(0.0, 0.0, 0.0))) - 0.14);}
vec3 palette(float t) { return (vec3(0.5) + (cos(((t + vec3(0.0, 0.33, 0.67)) * 6.283)) * 0.5));}in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_impl(VsOut vo) { vec2 _cse0 = vec2((((vo.uv.x * 2.0) - 1.0) * (U.resolution.x / U.resolution.y)), ((vo.uv.y * 2.0) - 1.0)); vec3 _cse1 = vec3(0.0015, 0.0, 0.0); vec3 _cse2 = vec3(0.0, 0.0015, 0.0); vec3 _cse3 = vec3(0.0, 0.0, 0.0015); vec3 _v0 = vec3((sin((U.time * 0.23)) * 0.4), (cos((U.time * 0.2)) * 0.4), (-(U.time * U.speed))); vec3 _v1 = normalize(vec3(_cse0.x, _cse0.y, -1.6)); float _v2 = ((((U.mouse.x / U.resolution.x) - 0.5) * 1.6) * U.mouse.w); float _v3 = (((U.mouse.y / U.resolution.y) - 0.5) * U.mouse.w); float _v4 = cos(_v2); float _v5 = sin(_v2); float _v6 = cos(_v3); float _v7 = sin(_v3); float _v8 = ((_v1.x * _v4) + (_v1.z * _v5)); float _v9 = ((_v1.z * _v4) - (_v1.x * _v5)); float _v10 = ((_v1.y * _v6) - (_v9 * _v7)); float _v11 = ((_v9 * _v6) + (_v1.y * _v7)); vec3 _v12 = vec3(_v8, _v10, _v11); float _av0 = 0.0; float _av1 = 0.0; for (uint _v13 = 0u; (_v13 < 90u); _v13 = (_v13 + 1u)) { float _v14 = scene((_v0 + (_v12 * _av1))); if ((_v14 < 0.002)) { _av0 = 1.0; break; } _av1 = (_av1 + (_v14 * 0.9)); if ((_av1 > 30.0)) { break; } } vec3 _v15 = (vec3(0.04, 0.05, 0.09) + (vec3(0.02, 0.04, 0.08) * vo.uv.y)); vec3 _av2 = (_v15 + vec3(0.0, 0.0, 0.0)); if ((_av0 > 0.5)) { vec3 _v16 = (_v0 + (_v12 * _av1)); vec3 _v17 = normalize(vec3((scene((_v16 + _cse1)) - scene((_v16 - _cse1))), (scene((_v16 + _cse2)) - scene((_v16 - _cse2))), (scene((_v16 + _cse3)) - scene((_v16 - _cse3))))); vec3 _lc0 = floor((_v16 / 2.6)); _av2 = mix(_v15, (palette(fract((sin((((_lc0.x * 12.9898) + (_lc0.y * 78.233)) + (_lc0.z * 37.719))) * 43758.5453))) * ((max(dot(_v17, normalize(vec3(0.5, 0.8, 0.3))), 0.0) * 0.8) + 0.16)), exp((-(_av1 * 0.09)))); } return vec4(_av2, 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": 48, "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": "speed", "type": "f32", "offset": 16, "align": 4, "size": 4 }, { "name": "mouse", "type": "vec4<f32>", "offset": 32, "align": 16, "size": 16 } ] } ], "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 raymarch-boxes
— prints the WGSL, GLSL, reflection. Source:
GitHub.
New to the IR? See Concepts; the full authoring + emit surface is in the API reference.