Colour ramp
데이터로 색을 정하는 단계구분도 색상 램프.
// ═══ typeshade example — choropleth colour ramp ═══//// A cartographic shader: the data-driven colour ramp a thematic map uses to paint a value// field. A reusable `ramp()` DSL function maps a normalised value [0,1] through a 5-stop// sequential palette; the fragment samples an animated value field, colours it, and overlays// anti-aliased contour isolines whose count is a live uniform.
import { fn, module, vec3, vec4, sin, fract, smoothstep, min, clamp, fwidth, mix, f32, f32T,} from '../src/index.js';import { VsOut, vs, fullscreenUniforms } from './_fullscreen.js';import type { ShaderExample } from './_shared.js';const U = fullscreenUniforms({ bands: f32T });
// 5-stop sequential ramp (light yellow → deep red), a classic choropleth legend.const ramp = fn('ramp', { x: f32T }, ({ x }) => { const c0 = vec3(0.99, 0.95, 0.74); const c1 = vec3(0.99, 0.8, 0.45); const c2 = vec3(0.96, 0.5, 0.24); const c3 = vec3(0.84, 0.19, 0.15); const c4 = vec3(0.5, 0.0, 0.05); const a = mix(c0, c1, smoothstep(0, 0.25, x)); const b = mix(a, c2, smoothstep(0.25, 0.5, x)); const d = mix(b, c3, smoothstep(0.5, 0.75, x)); return mix(d, c4, smoothstep(0.75, 1, x));});
const fs = fn( 'fs', { vo: VsOut }, ({ vo }) => { const uv = vo.uv; const t = U.field.time; const bands = U.field.bands;
// Animated data field in [0,1]. const val = clamp( sin(uv.x.mul(6.2832).add(t.mul(0.5))) .mul(0.3) .add(0.5) .add(sin(uv.y.mul(6.2832).sub(t.mul(0.35))).mul(0.2)), 0, 1, );
const col = ramp({ x: val });
// Contour isolines at evenly spaced value levels, screen-constant width via fwidth. const e = fract(val.mul(bands)); const dd = min(e, f32(1).sub(e)); const lineMask = f32(1).sub(smoothstep(0, fwidth(val.mul(bands)).mul(1.2), dd)); const enabled = smoothstep(0.5, 1.5, bands); // disables the overlay when bands ≈ 0 const contour = clamp(lineMask.mul(enabled), 0, 1);
return vec4(mix(col, col.mul(0.2), contour), 1); }, { stage: 'fragment', retAttr: '@location(0)' },);
// `ramp` is called via its handle in `fs`, so module() collects it transitively — funcs lists only the entry points.const colorRampModule = module({ structs: [U.struct, VsOut.decl], bindings: [U.binding], funcs: [vs, fs],});
export const colorRamp: ShaderExample = { id: 'color-ramp', title: 'Colour ramp', blurb: 'The data-driven choropleth ramp: a reusable ramp() function maps a value field through a 5-stop palette, with anti-aliased contour isolines. Set how many bands to draw.', category: 'cartographic', file: 'color-ramp.ts', module: colorRampModule, renderable: true, controls: { time: { kind: 'time' }, resolution: { kind: 'resolution' }, bands: { kind: 'slider', label: 'Contour bands', min: 0, max: 12, step: 1, value: 6 }, },};struct Uniforms { time: f32, resolution: vec2<f32>, bands: f32,}
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32>,}
@group(0) @binding(0) var<uniform> U: Uniforms;
fn ramp(x: f32) -> vec3<f32> { return mix(mix(mix(mix(vec3<f32>(0.99, 0.95, 0.74), vec3<f32>(0.99, 0.8, 0.45), smoothstep(0.0, 0.25, x)), vec3<f32>(0.96, 0.5, 0.24), smoothstep(0.25, 0.5, x)), vec3<f32>(0.84, 0.19, 0.15), smoothstep(0.5, 0.75, x)), vec3<f32>(0.5, 0.0, 0.05), smoothstep(0.75, 1.0, x));}
@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 = clamp((((sin(((vo.uv.x * 6.2832) + (U.time * 0.5))) * 0.3) + 0.5) + (sin(((vo.uv.y * 6.2832) - (U.time * 0.35))) * 0.2)), 0.0, 1.0); let _cse0 = ramp(_cse3); let _cse2 = (_cse3 * U.bands); let _cse1 = fract(_cse2); return vec4<f32>(mix(_cse0, (_cse0 * 0.2), clamp(((1.0 - smoothstep(0.0, (fwidth(_cse2) * 1.2), min(_cse1, (1.0 - _cse1)))) * smoothstep(0.5, 1.5, U.bands)), 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 bands;} U;vec3 ramp(float x) { return mix(mix(mix(mix(vec3(0.99, 0.95, 0.74), vec3(0.99, 0.8, 0.45), smoothstep(0.0, 0.25, x)), vec3(0.96, 0.5, 0.24), smoothstep(0.25, 0.5, x)), vec3(0.84, 0.19, 0.15), smoothstep(0.5, 0.75, x)), vec3(0.5, 0.0, 0.05), smoothstep(0.75, 1.0, x));}in vec2 uv;layout(location = 0) out vec4 _ret;
void main() { float _cse3 = clamp((((sin(((uv.x * 6.2832) + (U.time * 0.5))) * 0.3) + 0.5) + (sin(((uv.y * 6.2832) - (U.time * 0.35))) * 0.2)), 0.0, 1.0); vec3 _cse0 = ramp(_cse3); float _cse2 = (_cse3 * U.bands); float _cse1 = fract(_cse2); _ret = vec4(mix(_cse0, (_cse0 * 0.2), clamp(((1.0 - smoothstep(0.0, (fwidth(_cse2) * 1.2), min(_cse1, (1.0 - _cse1)))) * smoothstep(0.5, 1.5, U.bands)), 0.0, 1.0)), 1.0);}Colour ramp. 빌드할 때 그린 화면입니다.
WGSL과 GLSL 탭은 커밋 c66579bf의 컴파일러가 직접 낸 출력입니다. 컴파일러의 출력 검사가 구워 둔 골든 파일에서 그대로 읽어 왔습니다(emit-goldens.test.ts).
이 예제는 fn() 빌더 API로 작성해서 Playground의 편집기가 받지 않습니다.