Examples
cartographic graticule.ts
Graticule
The lon/lat grid every map draws — anti-aliased with screen-constant width via fwidth, gold equator, slowly spinning over time. Adjust the spacing in degrees.
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 — graticule (lon/lat grid) ═══//// A cartographic shader: the lon/lat graticule every map draws. The fragment maps screen// UV → longitude/latitude, draws anti-aliased grid lines at a uniform `spacing` (degrees)// with screen-constant width via `fwidth`, emphasises the equator in gold, and slowly// pans longitude over `time` so the globe appears to spin. Same DSL → WGSL + WebGL2 GLSL.
import { fn, module, vec3, vec4, abs, fract, smoothstep, max, mix, clamp, fwidth, f32, f32T,} from '../src/index.ts'import { VsOut, vs, fullscreenUniforms } from './_fullscreen.ts'import type { ShaderExample } from './_shared.ts'const U = fullscreenUniforms({ spacing: f32T })
const fs = fn( 'fs', { vo: VsOut }, ({ vo }) => { const uv = vo.uv const sp = U.field.spacing // UV → lon/lat, with a slow longitudinal pan over time. const lon = uv.x.mul(360).sub(180).add(U.field.time.mul(8)) const lat = uv.y.mul(180).sub(90)
// Distance (in degrees) to the nearest gridline, for lon and lat. const dLon = abs(fract(lon.div(sp).add(0.5)).sub(0.5)).mul(sp) const dLat = abs(fract(lat.div(sp).add(0.5)).sub(0.5)).mul(sp) // Screen-constant line width: fwidth gives degrees-per-pixel here. const gLon = f32(1).sub(smoothstep(0, fwidth(lon).mul(1.5), dLon)) const gLat = f32(1).sub(smoothstep(0, fwidth(lat).mul(1.5), dLat)) const grid = max(gLon, gLat) // Equator emphasis (fixed — uses the un-panned latitude). const eq = f32(1).sub(smoothstep(0, fwidth(lat).mul(3), abs(lat)))
// Ocean background: deeper toward the poles. const ocean = mix(vec3(0.05, 0.13, 0.24), vec3(0.03, 0.07, 0.14), abs(lat).div(90)) const lineCol = vec3(0.55, 0.72, 0.86) const withGrid = mix(ocean, lineCol, clamp(grid, 0, 1)) const withEq = mix(withGrid, vec3(0.96, 0.84, 0.45), clamp(eq, 0, 1)) return vec4(withEq, 1) }, { stage: 'fragment', retAttr: '@location(0)' },)
const graticuleModule = module({ structs: [U.struct, VsOut.decl], bindings: [U.binding], funcs: [vs, fs],})
export const graticule: ShaderExample = { id: 'graticule', title: 'Graticule', blurb: 'The lon/lat grid every map draws — anti-aliased with screen-constant width via fwidth, gold equator, slowly spinning over time. Adjust the spacing in degrees.', category: 'cartographic', file: 'graticule.ts', module: graticuleModule, renderable: true, controls: { time: { kind: 'time' }, resolution: { kind: 'resolution' }, spacing: { kind: 'slider', label: 'Spacing (°)', min: 5, max: 45, step: 1, value: 15 }, },}struct Uniforms { time: f32, resolution: vec2<f32>, spacing: 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 = ((vo.uv.y * 180.0) - 90.0); let _cse0 = abs(_cse3); let _cse1 = (((vo.uv.x * 360.0) - 180.0) + (U.time * 8.0)); let _cse2 = fwidth(_cse3); return vec4<f32>(mix(mix(mix(vec3<f32>(0.05, 0.13, 0.24), vec3<f32>(0.03, 0.07, 0.14), (_cse0 / 90.0)), vec3<f32>(0.55, 0.72, 0.86), clamp(max((1.0 - smoothstep(0.0, (fwidth(_cse1) * 1.5), (abs((fract(((_cse1 / U.spacing) + 0.5)) - 0.5)) * U.spacing))), (1.0 - smoothstep(0.0, (_cse2 * 1.5), (abs((fract(((_cse3 / U.spacing) + 0.5)) - 0.5)) * U.spacing)))), 0.0, 1.0)), vec3<f32>(0.96, 0.84, 0.45), clamp((1.0 - smoothstep(0.0, (_cse2 * 3.0), _cse0)), 0.0, 1.0)), 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 spacing;} U;in vec2 uv;layout(location = 0) out vec4 _ret;
vec4 fs_impl(VsOut vo) { float _cse3 = ((vo.uv.y * 180.0) - 90.0); float _cse0 = abs(_cse3); float _cse1 = (((vo.uv.x * 360.0) - 180.0) + (U.time * 8.0)); float _cse2 = fwidth(_cse3); return vec4(mix(mix(mix(vec3(0.05, 0.13, 0.24), vec3(0.03, 0.07, 0.14), (_cse0 / 90.0)), vec3(0.55, 0.72, 0.86), clamp(max((1.0 - smoothstep(0.0, (fwidth(_cse1) * 1.5), (abs((fract(((_cse1 / U.spacing) + 0.5)) - 0.5)) * U.spacing))), (1.0 - smoothstep(0.0, (_cse2 * 1.5), (abs((fract(((_cse3 / U.spacing) + 0.5)) - 0.5)) * U.spacing)))), 0.0, 1.0)), vec3(0.96, 0.84, 0.45), clamp((1.0 - smoothstep(0.0, (_cse2 * 3.0), _cse0)), 0.0, 1.0)), 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": "spacing", "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 graticule
— prints the WGSL, GLSL, reflection. Source:
GitHub.
New to the IR? See Concepts; the full authoring + emit surface is in the API reference.