Graticule
The lon/lat grid every map draws.
// ═══ typeshade 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.js';import { VsOut, vs, fullscreenUniforms } from './_fullscreen.js';import type { ShaderExample } from './_shared.js';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;
out vec2 uv;
void main() { uint vi = uint(gl_VertexID); float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0); float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0); gl_Position = vec4(_cse0, _cse1, 0.0, 1.0); uv = vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5));}#version 300 esprecision highp float;precision highp int;
layout(std140) uniform Uniforms { float time; vec2 resolution; float spacing;} U;in vec2 uv;layout(location = 0) out vec4 _ret;
void main() { float _cse3 = ((uv.y * 180.0) - 90.0); float _cse0 = abs(_cse3); float _cse1 = (((uv.x * 360.0) - 180.0) + (U.time * 8.0)); float _cse2 = fwidth(_cse3); _ret = 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);}Graticule, drawn at build time.
The WGSL and GLSL tabs are the compiler's own output at commit 26de7be8, read from the goldens its emit suite bakes (emit-goldens.test.ts).
This example is written against the fn() builder API, which the editor in the Playground does not take.