Graticule

Graticule

지도라면 다 그리는 경위선 격자.

TypeScript로 쓰고 TypeShade가 컴파일한 Graticule 셰이더. 빌드할 때 렌더링함.
// ═══ 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;
@vertex
fn 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)));
}
@fragment
fn 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 es
precision 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 es
precision 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. 빌드할 때 그린 화면입니다.

WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).

이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.

GitHub의 파일

이 페이지 편집 문제 보고