Examples
generic raymarch-sphere.ts
Raymarched sphere
A sphere signed-distance field, sphere-traced from a camera ray and Blinn-Phong shaded with an orbiting light. The "hello world" of raymarching — built on normalize / length / dot + a Loop march.
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 — raymarched sphere (signed-distance field) ═══//// A sphere distance-field, sphere-traced from a camera ray, then Blinn-Phong shaded// with an orbiting light. The "hello world" of raymarching. Showcases the new// `normalize` builtin (ray + surface normal), `length` (the SDF), `dot`, a `Loop`// march with early `Break`, and a mutable colour `var`. WGSL + GLSL ES 3.00.
import { fn, module, u32, f32, vec2, vec3, vec4, sin, cos, dot, max, pow, normalize, length, Loop, If, Break, Let,} from '../src/index.ts'import { VsOut, vs, fullscreenUniforms } from './_fullscreen.ts'import type { ShaderExample } from './_shared.ts'const U = fullscreenUniforms()
const fs = fn( 'fs', { vo: VsOut }, ({ vo }) => { const uv = vo.uv const ndc = vec2(uv.x.mul(2).sub(1), uv.y.mul(2).sub(1)) const ro = vec3(0, 0, 3) // camera const rd = normalize(vec3(ndc.x, ndc.y, -1.5)) // ray direction // sphere-trace a unit sphere at the origin: SDF = |p| − 1 const t = f32(0) const hit = f32(0) Loop( u32(0), (i) => i.lt(u32(72)), () => { const p = ro.add(rd.mul(t)) const d = Let(length(p).sub(1)) // materialise once: `t` is a mutated var so CSE can't hoist — without Let the SDF (a `length`) re-emits in both the hit test and `t += d` If(d.lt(0.001), () => { hit.assign(1) Break() }) t.assign(t.add(d)) If(t.gt(8), () => { Break() }) }, ) // background: a soft vertical gradient const col = vec3(0.04, 0.05, 0.09).add(vec3(0.0, 0.04, 0.1).mul(uv.y)) If(hit.gt(0.5), () => { const p = ro.add(rd.mul(t)) const n = Let(normalize(p)) // materialise once: the 3 lighting dots below each read `n`, and `t` (a var) blocks CSE — without Let the normal (sqrt + divides) re-emits 3x const ld = normalize(vec3(cos(U.field.time), 0.75, sin(U.field.time))) const diff = max(dot(n, ld), 0) const h = normalize(ld.sub(rd)) // Blinn-Phong halfway (view dir = −rd) const spec = pow(max(dot(n, h), 0), 48) const rim = pow(f32(1).sub(max(dot(n, rd.neg()), 0)), 2) col.assign( vec3(0.22, 0.48, 0.95) .mul(diff.mul(0.85).add(0.12)) // diffuse + ambient .add(vec3(1.0).mul(spec.mul(0.6))) // specular highlight .add(vec3(0.25, 0.35, 0.55).mul(rim.mul(0.5))), // fresnel rim ) }) return vec4(col, 1) }, { stage: 'fragment', retAttr: '@location(0)' },)
const raymarchModule = module({ structs: [U.struct, VsOut.decl], bindings: [U.binding], funcs: [vs, fs],})
export const raymarchSphere: ShaderExample = { id: 'raymarch-sphere', title: 'Raymarched sphere', blurb: 'A sphere signed-distance field, sphere-traced from a camera ray and Blinn-Phong shaded with an orbiting light. The "hello world" of raymarching — built on normalize / length / dot + a Loop march.', category: 'generic', file: 'raymarch-sphere.ts', module: raymarchModule, renderable: true, controls: { time: { kind: 'time' }, resolution: { kind: 'resolution' }, },}struct Uniforms { time: f32, resolution: vec2<f32>,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> U: Uniforms;
@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 _cse3 = vec2<f32>(((vo.uv.x * 2.0) - 1.0), ((vo.uv.y * 2.0) - 1.0)); let _cse0 = vec3<f32>(0.0, 0.0, 3.0); let _cse1 = normalize(vec3<f32>(_cse3.x, _cse3.y, -1.5)); let _cse2 = normalize(vec3<f32>(cos(U.time), 0.75, sin(U.time))); var _av0: f32 = 0.0; var _av1: f32 = 0.0; for (var _v0: u32 = 0u; (_v0 < 72u); _v0 = (_v0 + 1u)) { let _v1 = (length((_cse0 + (_cse1 * _av1))) - 1.0); if ((_v1 < 0.001)) { _av0 = 1.0; break; } _av1 = (_av1 + _v1); if ((_av1 > 8.0)) { break; } } var _av2: vec3<f32> = (vec3<f32>(0.04, 0.05, 0.09) + (vec3<f32>(0.0, 0.04, 0.1) * vo.uv.y)); if ((_av0 > 0.5)) { let _v2 = normalize((_cse0 + (_cse1 * _av1))); _av2 = (((vec3<f32>(0.22, 0.48, 0.95) * ((max(dot(_v2, _cse2), 0.0) * 0.85) + 0.12)) + (vec3<f32>(1.0) * (pow(max(dot(_v2, normalize((_cse2 - _cse1))), 0.0), 48.0) * 0.6))) + (vec3<f32>(0.25, 0.35, 0.55) * (pow((1.0 - max(dot(_v2, (-_cse1)), 0.0)), 2.0) * 0.5))); } 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;} U;in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_impl(VsOut vo) { vec2 _cse3 = vec2(((vo.uv.x * 2.0) - 1.0), ((vo.uv.y * 2.0) - 1.0)); vec3 _cse0 = vec3(0.0, 0.0, 3.0); vec3 _cse1 = normalize(vec3(_cse3.x, _cse3.y, -1.5)); vec3 _cse2 = normalize(vec3(cos(U.time), 0.75, sin(U.time))); float _av0 = 0.0; float _av1 = 0.0; for (uint _v0 = 0u; (_v0 < 72u); _v0 = (_v0 + 1u)) { float _v1 = (length((_cse0 + (_cse1 * _av1))) - 1.0); if ((_v1 < 0.001)) { _av0 = 1.0; break; } _av1 = (_av1 + _v1); if ((_av1 > 8.0)) { break; } } vec3 _av2 = (vec3(0.04, 0.05, 0.09) + (vec3(0.0, 0.04, 0.1) * vo.uv.y)); if ((_av0 > 0.5)) { vec3 _v2 = normalize((_cse0 + (_cse1 * _av1))); _av2 = (((vec3(0.22, 0.48, 0.95) * ((max(dot(_v2, _cse2), 0.0) * 0.85) + 0.12)) + (vec3(1.0) * (pow(max(dot(_v2, normalize((_cse2 - _cse1))), 0.0), 48.0) * 0.6))) + (vec3(0.25, 0.35, 0.55) * (pow((1.0 - max(dot(_v2, (-_cse1)), 0.0)), 2.0) * 0.5))); } 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": 16, "align": 16, "fields": [ { "name": "time", "type": "f32", "offset": 0, "align": 4, "size": 4 }, { "name": "resolution", "type": "vec2<f32>", "offset": 8, "align": 8, "size": 8 } ] } ], "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-sphere
— prints the WGSL, GLSL, reflection. Source:
GitHub.
New to the IR? See Concepts; the full authoring + emit surface is in the API reference.