shadertown 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/export.d.ts.map +1 -1
- package/dist/export.js +55 -5
- package/dist/export.js.map +1 -1
- package/dist/generated/sources.d.ts +41 -0
- package/dist/generated/sources.d.ts.map +1 -1
- package/dist/generated/sources.js +41 -0
- package/dist/generated/sources.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +8 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +23 -1
- package/dist/react.js.map +1 -1
- package/dist/registry.d.ts +1050 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +642 -0
- package/dist/registry.js.map +1 -1
- package/dist/renderer.d.ts +15 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +127 -0
- package/dist/renderer.js.map +1 -1
- package/dist/source.d.ts +56 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +191 -0
- package/dist/source.js.map +1 -0
- package/dist/stage.d.ts +13 -1
- package/dist/stage.d.ts.map +1 -1
- package/dist/stage.js +5 -1
- package/dist/stage.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1,22 +1,63 @@
|
|
|
1
1
|
export declare const shaderSources: {
|
|
2
2
|
readonly aurora: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// aurora\n// ---------------------------------------------------------------------------\n\n// Aurora — layered curtains of domain-warped noise over a night sky.\n\nfn curtain(p : vec2f, offset : f32, t : f32) -> f32 {\n var q = p;\n q.x = q.x + st_fbm2(p * vec2f(1.4, 0.7) + vec2f(offset, t * 0.25), 4) * params.warp;\n let band = st_fbm2(vec2f(q.x * 2.2 + offset * 3.0, q.y * 0.6 - t * 0.4), 5);\n let ribbon = pow(clamp(band, 0.0, 1.0), 2.2);\n let falloff = smoothstep(0.62, -0.12, p.y);\n let top = smoothstep(-0.55, -0.05, p.y);\n return ribbon * falloff * top;\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * params.scale;\n let t = st_time();\n\n // Night sky gradient, darkest at the horizon.\n var col = mix(vec3f(0.004, 0.008, 0.028), vec3f(0.02, 0.03, 0.07), smoothstep(1.0, -0.2, uv.y));\n\n // Stars, thinned out towards the aurora.\n let starCell = floor(uv * params.resolution / 3.0);\n let star = st_hash21(starCell);\n let twinkle = 0.6 + 0.4 * sin(t * 2.0 + star * TAU);\n col = col + vec3f(step(0.9985, star) * twinkle * params.d);\n\n // Three curtains at different depths.\n let layers = 3.0;\n var glow = 0.0;\n var tint = vec3f(0.0);\n for (var i = 0; i < 3; i = i + 1) {\n let fi = f32(i);\n let depth = 1.0 - fi / layers * 0.55;\n let amount = curtain(p * (1.0 + fi * 0.35), fi * 7.31, t) * depth;\n glow = glow + amount;\n tint = tint + st_palette(fi / (layers - 1.0)) * amount;\n }\n\n let energy = glow * params.intensity;\n col = col + tint * params.intensity;\n col = col + st_palette(0.5) * pow(energy, 3.0) * 0.35 * params.b;\n\n return st_post(st_tonemap(col * params.a), uv);\n}\n";
|
|
3
|
+
readonly bloom: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// bloom\n// ---------------------------------------------------------------------------\n\n// Bloom — light bleeding out of the bright parts of the layer below.\n\n// Only what is already bright blooms. The soft knee keeps the threshold from\n// showing up as a hard outline around every highlight.\nfn bloom_bright(uv : vec2f) -> vec3f {\n let c = st_srcRgb(uv);\n return c * smoothstep(params.a, params.a + 0.25, st_luma(c));\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let base = st_srcRgb(uv);\n\n // A wide spiral over the thresholded image. Thresholding per tap, rather\n // than blurring first, is what keeps a dark neighbour from dimming a bloom.\n let taps = 16;\n let radius = params.b * 0.12;\n // Stretching the spiral sideways gives the horizontal streak an anamorphic\n // lens leaves on every point of light.\n let stretch = vec2f(1.0 + params.warp * 3.0, 1.0);\n\n var glow = vec3f(0.0);\n for (var i = 0; i < taps; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / f32(taps));\n glow = glow + bloom_bright(uv + vec2f(cos(angle), sin(angle)) * r * stretch);\n }\n glow = glow / f32(taps);\n\n // Tint pulls the halo towards the palette — the coating on a real lens is\n // rarely neutral, and a warm bloom over cool light is most of the look.\n glow = mix(glow, glow * st_palette(st_luma(glow)) * 2.0, clamp(params.d, 0.0, 1.0));\n\n let col = base + glow * params.c;\n return st_post(col * params.intensity, uv);\n}\n";
|
|
3
4
|
readonly bokeh: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// bokeh\n// ---------------------------------------------------------------------------\n\n// Bokeh — out-of-focus lights drifting through depth. Additive, soft-edged.\n\nfn bokehLayer(p : vec2f, depth : f32, t : f32) -> vec3f {\n let density = mix(2.5, 7.0, depth);\n let drift = vec2f(t * 0.06 * (1.0 - depth * 0.7), -t * 0.09 * (1.0 - depth * 0.5));\n let grid = p * density + drift * density;\n let base = floor(grid);\n let frac = fract(grid) - 0.5;\n\n var acc = vec3f(0.0);\n for (var y = -1; y <= 1; y = y + 1) {\n for (var x = -1; x <= 1; x = x + 1) {\n let neighbour = vec2f(f32(x), f32(y));\n let rnd = st_hash22(base + neighbour + depth * 91.7);\n if (rnd.x < 0.35) { continue; }\n\n let wobble = vec2f(sin(t * 0.5 + rnd.y * TAU), cos(t * 0.4 + rnd.x * TAU)) * 0.12;\n let centre = neighbour + rnd - 0.5 + wobble;\n let d = length(frac - centre);\n\n // A disc with a bright rim — how a real lens renders defocused points.\n let radius = (0.10 + rnd.y * 0.22) * params.a * mix(1.6, 0.5, depth);\n let disc = smoothstep(radius, radius * (1.0 - params.b), d);\n let rim = smoothstep(radius * (1.0 - params.b * 0.8), radius, d) * disc;\n\n let tint = st_palette(rnd.y);\n acc = acc + tint * (disc * 0.55 + rim * params.c) * mix(0.35, 1.0, 1.0 - depth);\n }\n }\n return acc;\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * params.scale;\n let t = st_time();\n\n var col = mix(params.colorA.rgb * 0.10, vec3f(0.008, 0.008, 0.02), uv.y);\n for (var i = 0; i < 4; i = i + 1) {\n col = col + bokehLayer(p, f32(i) / 3.0, t) * params.d;\n }\n\n return st_post(st_tonemap(col * params.intensity), uv);\n}\n";
|
|
4
5
|
readonly caustics: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// caustics\n// ---------------------------------------------------------------------------\n\n// Caustics — light focused through a moving water surface onto a pool floor.\n\nfn causticField(p : vec2f, t : f32, sharpness : f32) -> f32 {\n var acc = 0.0;\n var q = p;\n // Stacked, counter-rotating cell fields approximate the focusing pattern.\n for (var i = 0; i < 3; i = i + 1) {\n let fi = f32(i);\n let rotated = st_rot(fi * 1.1 + t * 0.05 * (fi + 1.0)) * q;\n let wave = sin(rotated.x * 6.0 + t * (1.1 + fi * 0.3))\n + sin(rotated.y * 6.0 - t * (0.9 + fi * 0.2))\n + sin((rotated.x + rotated.y) * 4.0 + t * 0.7);\n acc = acc + abs(wave) / 3.0;\n q = q * 1.35;\n }\n let v = 1.0 - clamp(acc / 3.0, 0.0, 1.0);\n return pow(v, 1.0 + sharpness * 14.0);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n var p = st_centered(uv) * params.scale * 2.0;\n\n // Surface ripple displaces the floor lookup.\n let ripple = vec2f(st_fbm2(p * 1.5 + t * 0.1, 3), st_fbm2(p * 1.5 + 4.2 - t * 0.08, 3));\n p = p + (ripple - 0.5) * params.warp;\n\n let light = causticField(p, t, params.a);\n\n // Chromatic split — the three wavelengths focus at slightly different depths.\n let split = params.b * 0.03;\n let r = causticField(p + vec2f(split, 0.0), t, params.a);\n let b = causticField(p - vec2f(split, 0.0), t, params.a);\n\n let floorTone = mix(params.colorA.rgb, params.colorB.rgb, clamp(st_fbm2(p * 0.8, 3), 0.0, 1.0)) * 0.35;\n var col = floorTone;\n col = col + params.colorC.rgb * vec3f(r, light, b) * params.intensity * 2.2;\n\n // Depth falloff towards the pool edges.\n col = col * (0.55 + 0.45 * smoothstep(1.1, 0.1, length(st_centered(uv)) * params.c));\n\n return st_post(st_tonemap(col), uv);\n}\n";
|
|
5
6
|
readonly dither: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// dither\n// ---------------------------------------------------------------------------\n\n// Dither — a smooth field quantised through an 8x8 Bayer matrix.\n// Deliberately low-fi: flat colour, a visible pattern, no antialiasing.\n\n// Ordered-dither threshold, built by interleaving the bits of y and x^y.\nfn bayer8(coord : vec2f) -> f32 {\n let x = i32(coord.x) & 7;\n let y = i32(coord.y) & 7;\n var index = 0;\n for (var i = 0u; i < 3u; i = i + 1u) {\n let shift = 2u - i;\n index = (index << 1u) | ((y >> shift) & 1);\n index = (index << 1u) | (((x ^ y) >> shift) & 1);\n }\n return f32(index) / 64.0;\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n let p = st_centered(uv) * params.scale;\n\n // Centred on 0.5 so every quantisation level gets used. Biasing the source\n // upwards instead just crushes the image to the top level.\n let field = st_fbm2(p * 2.0 + vec2f(t * 0.10, t * 0.06), 5);\n let sweep = clamp((field - 0.5) * 1.7 + (0.5 - uv.y) * params.c + 0.5, 0.0, 1.0);\n\n // Pixelate first, so the pattern reads the same at any resolution.\n let pixel = max(params.a, 1.0);\n let screenPos = floor(uv * params.resolution / pixel);\n let threshold = (bayer8(screenPos) - 0.5) / max(floor(params.b), 2.0);\n\n let levels = max(floor(params.b), 2.0);\n let stepped = min(floor(clamp(sweep + threshold * params.d * 2.0, 0.0, 1.0) * levels), levels - 1.0);\n let quantised = stepped / (levels - 1.0);\n\n let col = st_palette(quantised) * params.intensity;\n return st_post(col, uv);\n}\n";
|
|
7
|
+
readonly duotone: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// duotone\n// ---------------------------------------------------------------------------\n\n// Duotone — the layer's brightness read through your palette.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let src = st_srcRgb(uv);\n var l = st_luma(src);\n\n // Black and white point first, contrast about the midpoint after. In the\n // other order a hard contrast would clip before the range is set.\n l = clamp((l - params.a) / max(params.b - params.a, 0.01), 0.0, 1.0);\n l = clamp((l - 0.5) * max(params.c, 0.01) + 0.5, 0.0, 1.0);\n\n let mapped = st_palette(l);\n // Keeping some of the original chroma stops a busy photograph from turning\n // into a flat two-colour poster when that is not what you wanted.\n let col = mix(mapped, mix(mapped, src, 0.85), clamp(params.d, 0.0, 1.0));\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
8
|
+
readonly edges: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// edges\n// ---------------------------------------------------------------------------\n\n// Edges — the layer reduced to its outlines.\n\nfn edges_luma(srcUv : vec2f) -> f32 {\n return st_luma(st_srcAt(srcUv).rgb);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let s = st_srcUv(uv);\n // Widening the step is what turns a hairline into a drawn line. Sub-texel\n // steps would just sample the same texel three times.\n let e = st_srcTexel() * max(params.a, 0.5);\n\n let tl = edges_luma(s + vec2f(-e.x, e.y));\n let tc = edges_luma(s + vec2f( 0.0, e.y));\n let tr = edges_luma(s + vec2f( e.x, e.y));\n let ml = edges_luma(s + vec2f(-e.x, 0.0));\n let mr = edges_luma(s + vec2f( e.x, 0.0));\n let bl = edges_luma(s + vec2f(-e.x, -e.y));\n let bc = edges_luma(s + vec2f( 0.0, -e.y));\n let br = edges_luma(s + vec2f( e.x, -e.y));\n\n // Sobel: the same kernel turned ninety degrees for the second axis.\n let gx = (tr + 2.0 * mr + br) - (tl + 2.0 * ml + bl);\n let gy = (tl + 2.0 * tc + tr) - (bl + 2.0 * bc + br);\n let gradient = length(vec2f(gx, gy));\n\n let line = smoothstep(params.b, params.b + 0.2, gradient);\n\n // The ground under the lines: the palette's dark end, with as much of the\n // original showing through as `fill` asks for.\n let ground = mix(st_palette(0.0), st_srcAt(s).rgb, clamp(params.c, 0.0, 1.0));\n var col = mix(ground, st_palette(1.0), line);\n\n // A little bleed off the line reads as ink, not as aliasing.\n col = col + st_palette(1.0) * pow(line, 3.0) * params.d;\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
6
9
|
readonly "film-grain": "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// film-grain\n// ---------------------------------------------------------------------------\n\n// Film grain — a texture pass. Dark wash, heavy stock grain, halation, scanlines.\n\nfn wash(p : vec2f, t : f32) -> f32 {\n return st_fbm2(p * 1.2 + vec2f(t * 0.05, t * 0.03), 4);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n let p = st_centered(uv) * params.scale;\n\n // Chromatic aberration: read the same wash at three lateral offsets.\n let ca = params.c * 0.02;\n let wr = wash(p + vec2f(ca, 0.0), t);\n let wg = wash(p, t);\n let wb = wash(p - vec2f(ca, 0.0), t);\n\n var col = vec3f(\n st_palette(clamp(wr * 1.3 - 0.1, 0.0, 1.0)).r,\n st_palette(clamp(wg * 1.3 - 0.1, 0.0, 1.0)).g,\n st_palette(clamp(wb * 1.3 - 0.1, 0.0, 1.0)).b\n ) * params.intensity;\n\n // Stock grain, sampled per cell so it stays the same size at any DPR.\n let cell = floor(uv * params.resolution / max(params.d, 0.5));\n let n1 = st_hash21(cell + floor(t * 24.0) * 17.13);\n let n2 = st_hash21(cell.yx + floor(t * 24.0) * 3.71);\n col = col + (n1 * 0.65 + n2 * 0.35 - 0.5) * params.a;\n\n // Scanlines.\n let lines = sin(uv.y * params.resolution.y * 1.5) * 0.5 + 0.5;\n col = col * (1.0 - lines * params.b);\n\n // Halation blooming out of the brightest areas.\n col = col + st_palette(1.0) * pow(clamp(st_luma(col), 0.0, 1.0), 4.0) * 0.4;\n\n return st_post(col, uv);\n}\n";
|
|
10
|
+
readonly flow: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// flow\n// ---------------------------------------------------------------------------\n\n// Flow — the layer pushed around by a slow, turbulent field.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n let p = st_centered(uv) * params.scale * 2.5;\n\n // Two fbm fields sampled a long way apart stand in for a divergence-free\n // field. It is not one, but the eye reads the swirl and not the maths.\n let drift = vec2f(t * 0.05, t * 0.03);\n // Turbulence trades soft hills for sharp crests. Ridged noise gives the flow\n // filaments and stringy detail; smooth noise gives it broad, lazy currents.\n let turbulence = clamp(params.a, 0.0, 1.0);\n let fx = mix(st_fbm2(p + drift, 4), st_ridge2(p + drift, 3), turbulence);\n let fy = mix(\n st_fbm2(p + drift + vec2f(17.3, 9.1), 4),\n st_ridge2(p + drift + vec2f(17.3, 9.1), 3),\n turbulence,\n );\n var field = vec2f(fx, fy) - 0.5;\n\n // Curl the field so it circulates rather than pulling everything one way.\n field = mix(field, vec2f(-field.y, field.x), clamp(params.b, 0.0, 1.0));\n\n let shove = field * params.warp * 0.12;\n\n // Smearing along the flow, rather than in a circle, is what makes it look\n // stirred instead of merely blurred.\n let smear = params.c * 0.06;\n let step = normalize(field + vec2f(0.0001)) * smear;\n var col = vec3f(0.0);\n for (var i = 0; i < 5; i = i + 1) {\n let k = (f32(i) / 4.0 - 0.5);\n // Inset, so the field has material to push around at the border rather\n // than running off the picture and smearing the last row of texels.\n col = col + st_srcAt(st_srcUvInset(uv + shove + step * k, 0.1)).rgb;\n }\n col = col / 5.0;\n\n // Tint by how hard the field is pushing, so the fast water shows colour.\n let speed = clamp(length(field) * 2.0, 0.0, 1.0);\n col = mix(col, col * st_palette(speed) * 1.8, clamp(params.d, 0.0, 1.0));\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
11
|
+
readonly glass: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// glass\n// ---------------------------------------------------------------------------\n\n// Glass — a refracting, frosted sheet over the layer behind it.\n\n// Two slow counter-drifting fields. Mixing smooth hills with sharp ridges is\n// what reads as a thick pane; either one alone reads as water.\nfn glass_height(p : vec2f, t : f32) -> f32 {\n let hills = st_fbm2(p + vec2f(t * 0.06, -t * 0.04), 3);\n let ridges = st_ridge2(p * 0.7 - vec2f(t * 0.03, t * 0.05), 2);\n return mix(hills, ridges, 0.55);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n let p = st_centered(uv) * params.scale * 3.0;\n\n // Slope of the height field stands in for the surface normal. Forward\n // differences, not central: three evaluations instead of five, and the half\n // texel of bias is invisible next to the refraction it feeds.\n let e = 0.006;\n let h = glass_height(p, t);\n let hx = glass_height(p + vec2f(e, 0.0), t);\n let hy = glass_height(p + vec2f(0.0, e), t);\n let slope = vec2f(hx - h, hy - h) / e;\n\n let bend = st_srcWarp(-slope * params.warp * 0.012);\n let base = st_srcUv(uv) + bend;\n\n // Frost is a small spiral of taps around the bent coordinate. The height\n // rotates the spiral, so neighbouring pixels blur along different axes and\n // the result grains up instead of banding.\n let taps = 6;\n let frost = params.a * 0.03;\n var blurred = st_srcAt(base).rgb;\n for (var i = 0; i < taps; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963 + h * 5.0;\n let radius = frost * sqrt((fi + 0.5) / f32(taps));\n blurred = blurred + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n blurred = blurred / f32(taps + 1);\n\n // Dispersion: the red and blue ends bend by different amounts, so steep\n // slopes fringe the way real glass does.\n let spread = st_srcWarp(-slope * params.b * 0.004);\n var col = vec3f(\n st_srcAt(base + spread).r,\n blurred.g,\n st_srcAt(base - spread).b,\n );\n col = mix(blurred, col, clamp(params.b, 0.0, 1.0));\n\n // Steep ground catches the light. Squaring keeps the rims thin.\n let steep = clamp(length(slope) * 0.35, 0.0, 1.0);\n let rim = steep * steep * params.c;\n col = col + mix(vec3f(1.0), st_palette(h), 0.5) * rim * 0.6;\n\n // A pane is rarely colourless. Tint pulls the whole sheet toward the palette.\n col = mix(col, col * st_palette(h * 0.7 + 0.15) * 1.6, clamp(params.d, 0.0, 1.0));\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
7
12
|
readonly halftone: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// halftone\n// ---------------------------------------------------------------------------\n\n// Halftone — a duotone image reproduced as a rotated dot screen.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n let p = st_centered(uv) * params.scale;\n\n // The continuous-tone image being screened. Centred on 0.5 so the screen\n // actually has a range of dot sizes to work with instead of saturating.\n let field = st_fbm2(p * 1.8 + vec2f(t * 0.12, t * 0.07), 4);\n let sweep = clamp((field - 0.5) * 1.6 + (p.x + p.y) * 0.35 + 0.5, 0.0, 1.0);\n\n // Dot screen, rotated the way a real halftone plate would be.\n let angle = params.c * PI;\n let grid = st_rot(angle) * uv * params.resolution / max(params.a, 2.0);\n let cellPos = fract(grid) - 0.5;\n\n let ink = pow(sweep, 1.0 / max(params.d, 0.05));\n // 0.62 lets neighbouring dots touch at full ink while leaving the corners\n // open — past ~0.71 the screen fills solid and stops reading as halftone.\n let radius = sqrt(ink) * 0.62;\n let coverage = 1.0 - smoothstep(radius - params.b, radius + params.b, length(cellPos));\n\n let paper = params.colorA.rgb;\n let inkColor = mix(params.colorB.rgb, params.colorC.rgb, sweep);\n\n let col = mix(paper, inkColor, coverage * params.intensity);\n return st_post(col, uv);\n}\n";
|
|
8
13
|
readonly julia: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// julia\n// ---------------------------------------------------------------------------\n\n// Julia — escape-time fractal with orbit-trap colouring. A moving c parameter\n// walks the set through connected and dust-like shapes.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n var z = st_centered(uv) * (3.4 / max(params.scale, 0.05));\n z = st_rot(t * 0.03 * params.d) * z;\n\n // The seed orbits a cardioid edge, which is where the interesting shapes live.\n let angle = t * 0.12;\n let c = vec2f(\n 0.7885 * cos(angle) * params.a,\n 0.7885 * sin(angle) * params.a\n ) + vec2f(params.b - 0.5, 0.0) * 0.6;\n\n let maxIter = 96;\n var i = 0;\n var trap = 1e9;\n var escaped = false;\n\n for (; i < maxIter; i = i + 1) {\n z = vec2f(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;\n trap = min(trap, abs(length(z) - 1.0));\n if (dot(z, z) > 64.0) { escaped = true; break; }\n }\n\n if (!escaped) {\n // Interior: shade by how close the orbit came to the trap circle.\n let inner = st_palette(clamp(trap * 4.0, 0.0, 1.0)) * 0.5;\n return st_post(inner * params.intensity, uv);\n }\n\n // Smooth iteration count removes the banding of a raw integer count.\n let smoothed = f32(i) - log2(max(log2(length(z)), 1.0)) + 4.0;\n let shade = fract(smoothed * 0.09 * params.c + t * 0.04);\n\n // Points that escape immediately are the empty outer plane; letting them\n // take a full palette colour washes the whole frame out.\n let escapeDepth = clamp(smoothed / 18.0, 0.0, 1.0);\n\n var col = st_paletteCyclic(shade) * pow(escapeDepth, 1.15);\n col = col * (0.25 + 0.9 * clamp(1.0 - trap * 1.1, 0.0, 1.0));\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
9
14
|
readonly kaleidoscope: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// kaleidoscope\n// ---------------------------------------------------------------------------\n\n// Kaleidoscope — polar mirror folding over a drifting noise field.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n var p = st_centered(uv) * params.scale;\n let t = st_time();\n\n let segments = max(floor(params.a), 2.0);\n var angle = atan2(p.y, p.x);\n let radius = length(p);\n\n // Fold the angle into one wedge, then mirror it.\n let wedge = TAU / segments;\n angle = angle + t * 0.15 * params.d;\n angle = angle - wedge * floor(angle / wedge);\n angle = abs(angle - wedge * 0.5);\n\n var q = vec2f(cos(angle), sin(angle)) * radius;\n q = q + vec2f(st_fbm2(q * 3.0 + t * 0.2, 4) - 0.5) * params.warp;\n\n let rings = sin(radius * 22.0 * params.b - t * 2.0) * 0.5 + 0.5;\n let field = st_fbm2(q * 4.0 + vec2f(0.0, t * 0.3), 5);\n\n var col = st_palette(fract(field * 1.6 + rings * params.c));\n\n // Bright core, dark rim — reads like looking down a tube.\n col = col * (0.4 + 0.9 * smoothstep(0.75, 0.0, radius));\n col = col + st_palette(1.0) * pow(clamp(1.0 - radius * 2.2, 0.0, 1.0), 3.0) * 0.6;\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
15
|
+
readonly lens: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// lens\n// ---------------------------------------------------------------------------\n\n// Lens — barrel distortion, colour fringing and soft corners.\n\n// Brown-Conrady, truncated after the second radial term. That is enough to\n// bend a straight line convincingly and cheap enough to run three times.\nfn lens_distort(p : vec2f, k : f32) -> vec2f {\n let r2 = dot(p, p);\n return p * (1.0 + k * r2 + k * k * r2 * r2 * 0.35);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let aspect = st_aspect();\n let k = params.warp * 0.6;\n\n // Barrel distortion throws the corners outside the frame, which would leave\n // a black border around every preview. Measure how far the corner moves and\n // pull the whole field back in by that much, so bending the picture does not\n // also shrink it. Pincushion pulls inwards on its own and needs no help,\n // hence the clamp at 1.\n let corner = vec2f(aspect, 1.0) * 0.5;\n let stretch = length(lens_distort(corner, k)) / max(length(corner), 0.0001);\n let fill = 1.0 / max(stretch, 1.0);\n\n // Centred and aspect-corrected, so the distortion is radial about the frame\n // rather than about the corner of a stretched uv square.\n let p = (uv - 0.5) * vec2f(aspect, 1.0) * fill / max(params.c, 0.05);\n\n // Each channel gets its own constant. That is what glass does, and it is why\n // the fringing is invisible in the middle and worst in the corners.\n let spread = params.b * 0.06;\n let toUv = vec2f(1.0 / aspect, 1.0);\n let centre = lens_distort(p, k) * toUv + 0.5;\n let red = lens_distort(p, k * (1.0 - spread)) * toUv + 0.5;\n let blue = lens_distort(p, k * (1.0 + spread)) * toUv + 0.5;\n\n // Corners sit off the focal plane, so they lose sharpness first.\n let falloff = clamp(length(p) * 1.5, 0.0, 1.0);\n let soft = params.a * falloff * falloff * 0.02;\n\n var col = vec3f(\n st_srcSoften(red, soft).r,\n st_srcSoften(centre, soft).g,\n st_srcSoften(blue, soft).b,\n );\n\n // Past the frame the sampler repeats its last row. Fade to black instead.\n col = col * st_srcInside(st_srcUv(centre));\n\n // Barrel distortion pulls light towards the middle, so the corners dim.\n col = col * (1.0 - params.d * falloff * falloff);\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
10
16
|
readonly "liquid-metal": "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// liquid-metal\n// ---------------------------------------------------------------------------\n\n// Liquid metal — a warped height field lit like polished chrome.\n// The look lives in the banded environment reflection, not in the noise: keep\n// the field low-frequency, and let the bands do the work.\n\nfn height(p : vec2f, t : f32) -> f32 {\n var q = p;\n q = q + vec2f(st_fbm2(q * 0.9 + vec2f(t * 0.18, 0.0), 3),\n st_fbm2(q * 0.9 + vec2f(0.0, t * 0.15), 3)) * params.warp * 1.6;\n return st_fbm2(q * 1.1 + vec2f(0.0, t * 0.08), 3);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * params.scale * 2.2;\n let t = st_time();\n\n // Fixed sampling offset in field units: a resolution-derived epsilon makes\n // the surface look completely different in a card and in a hero.\n let eps = 0.012;\n let h = height(p, t);\n let hx = height(p + vec2f(eps, 0.0), t);\n let hy = height(p + vec2f(0.0, eps), t);\n\n let slope = params.a * 0.55;\n let n = normalize(vec3f((h - hx) / eps * slope, (h - hy) / eps * slope, 1.0));\n\n let lightDir = normalize(vec3f(cos(t * 0.35) * 0.7, sin(t * 0.35) * 0.7, 0.9));\n let viewDir = vec3f(0.0, 0.0, 1.0);\n let halfway = normalize(lightDir + viewDir);\n\n let diffuse = clamp(dot(n, lightDir), 0.0, 1.0);\n let spec = pow(clamp(dot(n, halfway), 0.0, 1.0), 16.0 + params.b * 180.0);\n let fresnel = pow(1.0 - clamp(dot(n, viewDir), 0.0, 1.0), 4.0);\n\n // Wide, smooth reflection bands. Reflecting the view around the normal and\n // reading a ramp off it is what separates chrome from rough plastic.\n let reflected = reflect(-viewDir, n);\n let band = reflected.y * 1.6 + reflected.x * 0.5 + t * 0.05;\n let env = st_palette(clamp(band * 0.5 + 0.5, 0.0, 1.0));\n\n // A second, tighter band set gives the hard highlight streaks of polish.\n let streak = pow(abs(sin(band * PI * 1.5)), 8.0 + params.b * 40.0);\n\n var col = env * (0.35 + 0.75 * diffuse) * params.c;\n col = col + st_palette(1.0) * streak * 0.6 * params.c;\n col = col + vec3f(1.0) * spec * params.intensity * 1.4;\n col = col + st_palette(0.9) * fresnel * params.d;\n\n return st_post(st_tonemap(col), uv);\n}\n";
|
|
11
17
|
readonly "mesh-gradient": "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// mesh-gradient\n// ---------------------------------------------------------------------------\n\n// Mesh gradient — soft colour centres orbiting behind frosted glass.\n\n// A gaussian well, raised to a power so nearby colours dominate rather than\n// averaging. Without the exponent every centre contributes everywhere and the\n// middle of the frame turns grey.\nfn blob(p : vec2f, centre : vec2f, radius : f32) -> f32 {\n let d = length(p - centre);\n let g = exp(-d * d / max(radius * radius, 0.0001));\n return g * g * g;\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n var p = st_centered(uv) * params.scale;\n let t = st_time();\n\n // Gentle domain warp keeps the blend from looking like plain radial gradients.\n let warpNoise = vec2f(\n st_fbm2(p * 1.6 + vec2f(0.0, t * 0.12), 3),\n st_fbm2(p * 1.6 + vec2f(5.2, t * 0.12 + 1.3), 3)\n );\n p = p + (warpNoise - 0.5) * params.warp;\n\n let r = params.a;\n let spread = params.b;\n\n let c0 = vec2f(cos(t * 0.31) * spread, sin(t * 0.24) * spread * 0.6);\n let c1 = vec2f(cos(t * 0.19 + 2.1) * spread * 0.9, sin(t * 0.37 + 1.1) * spread * 0.7);\n let c2 = vec2f(cos(t * 0.27 + 4.3) * spread * 0.8, sin(t * 0.21 + 3.7) * spread * 0.75);\n let c3 = vec2f(cos(t * 0.15 + 5.6) * spread, sin(t * 0.33 + 0.4) * spread * 0.5);\n\n // Sharpening the weights before normalising is what keeps the colours\n // distinct. Plain gaussian weights average towards grey in the middle.\n let sharpen = 2.6;\n let w0 = pow(blob(p, c0, r), sharpen);\n let w1 = pow(blob(p, c1, r * 0.9), sharpen);\n let w2 = pow(blob(p, c2, r * 1.15), sharpen);\n let w3 = pow(blob(p, c3, r * 0.8), sharpen);\n let total = w0 + w1 + w2 + w3 + 1e-6;\n\n let base = mix(params.colorA.rgb, params.colorB.rgb, 0.5) * 0.25;\n var col = (params.colorA.rgb * w0\n + params.colorB.rgb * w1\n + params.colorC.rgb * w2\n + mix(params.colorA.rgb, params.colorC.rgb, 0.5) * w3) / total;\n\n // Coverage decides how much of the dark backdrop shows through.\n let coverage = clamp(pow(total, 1.0 / sharpen) * params.c, 0.0, 1.0);\n col = mix(base, col, coverage) * params.intensity;\n\n return st_post(col, uv);\n}\n";
|
|
18
|
+
readonly mosaic: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// mosaic\n// ---------------------------------------------------------------------------\n\n// Mosaic — the layer rebuilt out of tiles.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n // Cells are counted across the short axis and scaled by aspect, so a tile\n // stays square whatever shape the canvas is.\n let cells = vec2f(max(params.a, 2.0) * st_aspect(), max(params.a, 2.0));\n let cell = floor(uv * cells);\n let middle = (cell + 0.5) / cells;\n\n // One tap at the tile's centre, not an average of it. Cheaper, and the\n // hard colour is the point — averaging would only reinvent a blur.\n var tile = st_srcRgb(middle);\n\n // Posterise after sampling: quantising the tile colour is what makes it look\n // assembled out of a limited set rather than merely coarse.\n let steps = max(params.d, 2.0);\n tile = floor(tile * steps + 0.5) / steps;\n\n let local = fract(uv * cells) - 0.5;\n\n // Square tile or round dot, and everything between. The dot grows with the\n // tile's brightness, so dark areas thin out the way a printed screen does.\n let square = max(abs(local.x), abs(local.y));\n let round = length(local) / max(st_luma(tile), 0.05) * 0.55;\n let shape = mix(square, round, clamp(params.b, 0.0, 1.0));\n\n let edge = 0.5 - clamp(params.c, 0.0, 0.45);\n // Anti-alias in tile units. A fixed smoothstep width is far narrower than a\n // device pixel once the tiles are small, which is what turns clean grout into\n // a moire of hard, uneven lines.\n let aa = max(cells.y / max(params.resolution.y, 1.0), 0.002);\n let mask = 1.0 - smoothstep(edge - aa, edge + aa, shape);\n\n let ground = st_palette(0.0);\n let col = mix(ground, tile, mask);\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
12
19
|
readonly "neon-grid": "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// neon-grid\n// ---------------------------------------------------------------------------\n\n// Neon grid — perspective floor, banded sun, horizon haze. Synthwave by the book.\n\nfn gridLines(coord : vec2f, weight : f32) -> f32 {\n let width = max(fwidth(coord), vec2f(0.0001));\n let g = abs(fract(coord - 0.5) - 0.5) / width;\n return 1.0 - smoothstep(0.0, weight, min(g.x, g.y));\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n let p = st_centered(uv);\n let horizon = 0.02;\n\n // Floor is evaluated unconditionally: fwidth needs uniform control flow.\n let depth = 1.0 / max(p.y - horizon, 0.004);\n let floorCoord = vec2f(p.x * depth * 0.35, depth * 0.35 - t * params.b);\n let line = gridLines(floorCoord * params.a, 1.8);\n let below = step(horizon, p.y);\n let fade = smoothstep(0.0, 0.09, p.y - horizon) * smoothstep(1.4, 0.12, depth * 0.05);\n\n // Sky above the horizon.\n let skyT = clamp((horizon - p.y) * 2.4, 0.0, 1.0);\n var col = mix(params.colorA.rgb * 0.32, vec3f(0.015, 0.008, 0.05), skyT);\n\n // Sun: a disc sliced by horizontal bands, resting on the horizon.\n let sunCentre = vec2f(0.0, horizon - 0.17);\n let sunDist = length((p - sunCentre) * vec2f(1.0, 1.25));\n let sunMask = smoothstep(0.20, 0.19, sunDist);\n let bands = step(0.38, fract((p.y - sunCentre.y) * 26.0 + t * 0.4))\n * step(sunCentre.y + 0.015, p.y);\n let sunColor = mix(params.colorC.rgb, params.colorB.rgb,\n clamp((p.y - sunCentre.y + 0.2) * 2.5, 0.0, 1.0));\n col = mix(col, sunColor, sunMask * (1.0 - bands));\n col = col + sunColor * smoothstep(0.55, 0.0, sunDist) * 0.3 * params.d;\n\n // Floor: darken the plate, then lay the neon lines over it.\n col = mix(col, col * 0.3, below);\n col = col + params.colorB.rgb * line * fade * below * params.intensity * 1.8;\n\n // Horizon haze.\n col = col + params.colorB.rgb * exp(-abs(p.y - horizon) * 80.0) * params.c;\n\n return st_post(st_tonemap(col), uv);\n}\n";
|
|
13
20
|
readonly plasma: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// plasma\n// ---------------------------------------------------------------------------\n\n// Plasma — stacked sine fields, the oldest trick in the demoscene.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * params.scale * 6.0;\n let t = st_time();\n\n var v = 0.0;\n v = v + sin(p.x * 1.1 + t);\n v = v + sin(p.y * 1.3 + t * 1.17);\n v = v + sin((p.x + p.y) * 0.8 + t * 0.83);\n v = v + sin(length(p * params.a) * 1.4 - t * 1.6);\n\n // Rotating secondary field adds the interference bloom.\n let q = st_rot(t * 0.15 * params.b) * p;\n v = v + sin(q.x * 0.9 - q.y * 1.4 + t * 0.6) * params.c;\n\n let band = v * 0.125 + 0.5 + t * 0.05;\n var col = st_paletteCyclic(band * params.d);\n\n // Contour lines pick out the field's iso-levels.\n let contour = abs(fract(v * 0.9) - 0.5) * 2.0;\n col = col * (1.0 - smoothstep(0.85, 1.0, contour) * 0.55);\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
21
|
+
readonly reveal: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// reveal\n// ---------------------------------------------------------------------------\n\n// Reveal — the layer wiped in over a colour ground.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n // At speed 0, progress is exactly the control: a scrubber to drive from your\n // own code, bound to a scroll position or a load event.\n //\n // Running, it wipes in, holds the layer for most of the cycle, then clears.\n // A linear loop would spend half its time on an empty ground, and the offset\n // starts it revealed so the first frame is the picture and not the backdrop.\n let phase = fract(params.a + 0.4 + st_time() * 0.25);\n let cycled = smoothstep(0.0, 0.3, phase) * (1.0 - smoothstep(0.9, 1.0, phase));\n let progress = select(cycled, params.a, params.speed < 0.001);\n\n // The field the threshold cuts through. Noise dissolves, a rotated ramp\n // wipes, and `shape` slides between the two.\n let noise = st_fbm2(st_centered(uv) * params.scale * 4.0, 3);\n let axis = st_rot(params.c * TAU) * (uv - 0.5);\n let ramp = clamp(axis.x + 0.5, 0.0, 1.0);\n let field = mix(noise, ramp, clamp(params.b, 0.0, 1.0));\n\n // Widen the threshold band to soften the boundary. It has to cover the\n // whole 0..1 range at both ends, or the wipe never finishes.\n let soft = max(params.d, 0.001);\n let cut = progress * (1.0 + 2.0 * soft) - soft;\n let mask = 1.0 - smoothstep(cut - soft, cut + soft, field);\n\n // The ground follows the clean ramp, never the noise. Colouring it by the\n // dissolve field would leave static behind the wipe instead of a backdrop.\n let ground = st_palette(ramp);\n var col = mix(ground, st_srcRgb(uv), mask);\n\n // A bright rim rides the boundary, the way a burn does.\n let rim = 1.0 - abs(mask - 0.5) * 2.0;\n col = col + st_palette(1.0) * pow(rim, 3.0) * params.warp;\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
14
22
|
readonly ripple: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// ripple\n// ---------------------------------------------------------------------------\n\n// Ripple — interference between travelling circular waves.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * params.scale;\n let t = st_time();\n\n let sourceCount = 5;\n var wave = 0.0;\n var gradient = vec2f(0.0);\n\n for (var i = 0; i < sourceCount; i = i + 1) {\n let fi = f32(i);\n let phase = fi * 2.399963;\n let orbit = 0.28 * params.b;\n let src = vec2f(cos(t * 0.23 + phase) * orbit, sin(t * 0.31 + phase * 1.7) * orbit);\n let delta = p - src;\n let d = length(delta) + 0.0001;\n let k = 40.0 * params.a;\n let decay = 1.0 / (1.0 + d * 6.0);\n let s = sin(d * k - t * 4.0 + phase) * decay;\n wave = wave + s;\n gradient = gradient + (delta / d) * cos(d * k - t * 4.0 + phase) * decay * k;\n }\n\n wave = wave / f32(sourceCount);\n gradient = gradient / f32(sourceCount);\n\n // Treat the wave slope as a refracting surface and read the palette through it.\n let refracted = clamp(0.5 + wave * 1.4 + gradient.x * 0.02 * params.c, 0.0, 1.0);\n var col = st_palette(refracted);\n\n // Caustic highlights where the wavefronts focus.\n let caustic = pow(clamp(1.0 - abs(wave) * 3.2, 0.0, 1.0), 6.0);\n col = col + vec3f(1.0) * caustic * params.d;\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
23
|
+
readonly shockwave: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// shockwave\n// ---------------------------------------------------------------------------\n\n// Shockwave — expanding rings that shove the layer aside as they pass.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let aspect = st_aspect();\n // Pointer, so the wave starts wherever the cursor is when tracking is on.\n let centre = params.pointer;\n let offset = (uv - centre) * vec2f(aspect, 1.0);\n let dist = length(offset);\n let dir = offset / max(dist, 0.0001);\n\n let t = st_time() * 0.3;\n let rings = 3;\n let width = max(params.b * 0.12, 0.005);\n\n // Each ring is a gaussian band around its own radius, evenly staggered in\n // phase. A ring loses force as it grows, the way a real front spends itself.\n var push = 0.0;\n var crest = 0.0;\n for (var i = 0; i < rings; i = i + 1) {\n let phase = fract(t + f32(i) / f32(rings));\n let radius = phase * 1.3 * max(params.a, 0.1);\n let band = exp(-pow((dist - radius) / width, 2.0));\n let force = band * pow(1.0 - phase, max(params.c, 0.1));\n // Signed by which side of the crest we are on, so the front compresses\n // ahead of itself and rarefies behind — that is what makes it read as a\n // wave and not a glowing ring.\n push = push + force * sign(dist - radius);\n crest = crest + band * (1.0 - phase);\n }\n\n // Read a slightly smaller crop, so the front has material to shove around\n // even at the frame's edge. Without it the border goes black wherever a ring\n // is passing through it.\n let base = st_srcUvInset(uv, 0.09);\n let shove = st_srcWarp(dir * push * params.warp * 0.06);\n let spread = shove * params.d * 0.3;\n\n var col = vec3f(\n st_srcAt(base + shove + spread).r,\n st_srcAt(base + shove).g,\n st_srcAt(base + shove - spread).b,\n );\n col = col + st_palette(0.85) * crest * crest * 0.35;\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
15
24
|
readonly silk: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// silk\n// ---------------------------------------------------------------------------\n\n// Silk — anisotropic sheen across warped fibre lines. Soft, cloth-like.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n var p = st_centered(uv) * params.scale;\n\n // Two-stage domain warp gives the fabric its folds.\n let w1 = vec2f(st_fbm2(p * 1.1 + vec2f(0.0, t * 0.08), 4),\n st_fbm2(p * 1.1 + vec2f(3.7, t * 0.06), 4));\n p = p + (w1 - 0.5) * params.warp * 2.0;\n let w2 = st_fbm2(p * 2.6 + vec2f(t * 0.05, 0.0), 5);\n\n // Fibre direction: dense parallel lines bent by the warp.\n let fibres = sin((p.x * 18.0 + p.y * 6.0) * params.a + w2 * 12.0 * params.b);\n let sheen = pow(clamp(fibres * 0.5 + 0.5, 0.0, 1.0), 3.0);\n\n // The base tone comes from the fold height, the highlight from the fibres.\n var col = st_palette(clamp(w2 * 1.4 - 0.1, 0.0, 1.0));\n col = col + st_palette(1.0) * sheen * params.c;\n\n // Slight darkening in the creases sells the depth.\n let crease = smoothstep(0.55, 0.15, w2);\n col = col * (1.0 - crease * params.d * 0.7);\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
16
25
|
readonly starfield: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// starfield\n// ---------------------------------------------------------------------------\n\n// Starfield — a warp corridor. Layered depth slices flying at the camera.\n\n// Two independent hashes per cell; sharing one seed across layers is what\n// produces the diagonal moiré you get from adding a scalar to both components.\nfn cellRandom(cell : vec2f, layerSeed : f32) -> vec3f {\n let a = st_hash22(cell + vec2f(layerSeed * 37.19, layerSeed * 11.73));\n let b = st_hash21(cell.yx + layerSeed * 91.31);\n return vec3f(a, b);\n}\n\nfn layer(p : vec2f, layerIndex : f32, t : f32) -> vec3f {\n let z = fract(layerIndex * 0.1667 - t * 0.12);\n let scaleByDepth = mix(16.0, 2.0, z);\n let fade = smoothstep(0.0, 0.22, z) * smoothstep(1.0, 0.7, z);\n if (fade <= 0.0) { return vec3f(0.0); }\n\n // Rotating each slice breaks the alignment between neighbouring layers.\n let grid = st_rot(layerIndex * 1.37) * p * scaleByDepth;\n let base = floor(grid);\n let frac = fract(grid) - 0.5;\n\n var acc = vec3f(0.0);\n for (var y = -1; y <= 1; y = y + 1) {\n for (var x = -1; x <= 1; x = x + 1) {\n let neighbour = vec2f(f32(x), f32(y));\n let rnd = cellRandom(base + neighbour, layerIndex);\n if (rnd.z < 0.72) { continue; }\n\n let point = neighbour + rnd.xy - 0.5;\n var offset = frac - point;\n // Motion blur: stretch each star along its radial direction.\n let radial = normalize(p + vec2f(1e-5));\n let along = dot(offset, radial);\n let across = offset - radial * along;\n offset = radial * along / (1.0 + params.b * 8.0 * (1.0 - z)) + across;\n\n // A tight inverse-square core keeps stars as points. Widen the epsilon\n // and six layers of them blur into fog.\n let d = length(offset);\n let brightness = 0.00022 * params.a * (0.35 + rnd.z);\n acc = acc + st_palette(rnd.y) * (brightness / (d * d + 0.000018)) * fade;\n }\n }\n return acc;\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n var p = st_centered(uv) * params.scale;\n let t = st_time();\n p = st_rot(t * 0.05 * params.d) * p;\n\n var col = vec3f(0.004, 0.005, 0.012);\n for (var i = 0; i < 6; i = i + 1) {\n col = col + layer(p, f32(i), t);\n }\n\n // Core glow at the vanishing point.\n let core = params.c * 0.05 / (length(p) + 0.06);\n col = col + st_palette(1.0) * core;\n\n return st_post(st_tonemap(col * params.intensity), uv);\n}\n";
|
|
26
|
+
readonly swirl: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// shadertown source prelude — appended to the shared prelude for shaders that\n// read a layer instead of inventing one.\n//\n// Every binding here is additive. A generative shader declares none of them and\n// keeps the exact layout it always had, and `Source` is its own uniform block\n// rather than four more fields on `Params`, so the ABI that every already\n// shipped shader was built against never moves.\n\n@group(0) @binding(1) var srcTexture : texture_2d<f32>;\n@group(0) @binding(2) var srcSampler : sampler;\n\nstruct Source {\n // Pixel size of the bound texture. Zero means \"assume it matches the canvas\".\n size : vec2f,\n // 0 stretches the source over the canvas, 1 covers the canvas and crops.\n fit : f32,\n _pad : f32,\n}\n\n@group(0) @binding(3) var<uniform> source : Source;\n\n// How much of the source each axis shows once it is fitted. Both components are\n// 1 or less: covering means cropping, never adding.\nfn st_srcScale() -> vec2f {\n if (source.fit < 0.5 || source.size.x < 1.0 || source.size.y < 1.0) {\n return vec2f(1.0);\n }\n let canvas = st_aspect();\n let src = source.size.x / source.size.y;\n if (src > canvas) {\n return vec2f(canvas / src, 1.0);\n }\n return vec2f(1.0, src / canvas);\n}\n\n/** Canvas uv to source uv. */\nfn st_srcUv(uv : vec2f) -> vec2f {\n return (uv - 0.5) * st_srcScale() + 0.5;\n}\n\n/**\n * Canvas uv to source uv, with room left around the edge.\n *\n * A shader that displaces its reads runs off the source at the border, and the\n * sampler answers by smearing its last row of texels across the gap. Reading a\n * slightly smaller crop gives the displacement somewhere to go, at the cost of\n * a few per cent of the picture.\n */\nfn st_srcUvInset(uv : vec2f, margin : f32) -> vec2f {\n return (uv - 0.5) * st_srcScale() * (1.0 - clamp(margin, 0.0, 0.4)) + 0.5;\n}\n\n/**\n * A canvas-space displacement, in source uv.\n *\n * Divides out the aspect so a push of 0.01 moves the same distance sideways as\n * it does up, whatever shape the canvas or the source is.\n */\nfn st_srcWarp(offset : vec2f) -> vec2f {\n return offset * st_srcScale() / vec2f(st_aspect(), 1.0);\n}\n\n/** Samples in source uv. Kernels that walk neighbours want this one. */\nfn st_srcAt(srcUv : vec2f) -> vec4f {\n return textureSample(srcTexture, srcSampler, srcUv);\n}\n\n/** Samples in canvas uv — the ordinary read. */\nfn st_src(uv : vec2f) -> vec4f {\n return st_srcAt(st_srcUv(uv));\n}\n\nfn st_srcRgb(uv : vec2f) -> vec3f {\n return st_src(uv).rgb;\n}\n\n/** One source texel, in source uv. */\nfn st_srcTexel() -> vec2f {\n return 1.0 / max(source.size, vec2f(1.0));\n}\n\n/**\n * 1 inside the source, falling to 0 just outside it.\n *\n * The sampler clamps, so a shader that pushes past the border smears the last\n * row of texels across the gap. Anything that displaces should fade instead.\n */\nfn st_srcInside(srcUv : vec2f) -> f32 {\n let edge = min(srcUv, vec2f(1.0) - srcUv);\n return smoothstep(0.0, 0.004, min(edge.x, edge.y));\n}\n\n/** Four-tap softening. Cheap enough to run per colour channel. */\nfn st_srcSoften(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = vec3f(0.0);\n for (var i = 0; i < 4; i = i + 1) {\n let angle = f32(i) * 1.570796 + 0.785398;\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * radius)).rgb;\n }\n return sum * 0.25;\n}\n\n/**\n * Thirteen-tap spiral blur. `radius` is in canvas space.\n *\n * The golden angle keeps the taps from lining up into spokes, which is what a\n * uniform ring blur gives you at low tap counts.\n */\nfn st_srcBlur(uv : vec2f, radius : f32) -> vec3f {\n let base = st_srcUv(uv);\n var sum = st_srcAt(base).rgb;\n for (var i = 0; i < 12; i = i + 1) {\n let fi = f32(i);\n let angle = fi * 2.399963;\n let r = radius * sqrt((fi + 0.5) / 12.0);\n sum = sum + st_srcAt(base + st_srcWarp(vec2f(cos(angle), sin(angle)) * r)).rgb;\n }\n return sum / 13.0;\n}\n\n// ---------------------------------------------------------------------------\n// swirl\n// ---------------------------------------------------------------------------\n\n// Swirl — a vortex that winds the layer around a point.\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let aspect = st_aspect();\n let centre = params.pointer;\n let offset = (uv - centre) * vec2f(aspect, 1.0);\n let dist = length(offset);\n\n // Falloff decides the whole character: 1 is a gentle whirlpool, 4 is a tight\n // knot with still water around it.\n let radius = max(params.a, 0.02);\n let falloff = pow(clamp(1.0 - dist / radius, 0.0, 1.0), max(params.b, 0.1));\n let angle = falloff * params.warp * 4.0 + st_time() * params.c;\n\n // Rotation about the centre pushes the corners outside a rectangle, so the\n // read is inset enough to keep them on the picture.\n let toUv = vec2f(1.0 / aspect, 1.0);\n let twisted = st_rot(angle) * offset;\n let source = st_srcUvInset(twisted * toUv + centre, 0.06);\n\n // Winding the channels by slightly different angles smears colour along the\n // direction of travel, which is what sells the spin.\n let apart = params.d * 0.15 * falloff;\n let ahead = st_srcUvInset((st_rot(angle + apart) * offset) * toUv + centre, 0.06);\n let behind = st_srcUvInset((st_rot(angle - apart) * offset) * toUv + centre, 0.06);\n\n let col = vec3f(\n st_srcAt(ahead).r,\n st_srcAt(source).g,\n st_srcAt(behind).b,\n );\n\n return st_post(col * params.intensity, uv);\n}\n";
|
|
17
27
|
readonly truchet: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// truchet\n// ---------------------------------------------------------------------------\n\n// Truchet — quarter-arc tiles that flip in place. Hard, graphic, no noise wash.\n\nfn arcTile(f : vec2f, flipped : bool, weight : f32) -> f32 {\n var q = f;\n if (flipped) { q.x = 1.0 - q.x; }\n let d1 = abs(length(q) - 0.5);\n let d2 = abs(length(q - vec2f(1.0, 1.0)) - 0.5);\n let d = min(d1, d2);\n return 1.0 - smoothstep(weight, weight + 0.02, d);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let t = st_time();\n var p = st_centered(uv) * params.scale * 6.0;\n p = st_rot(t * 0.04 * params.d) * p;\n p = p + vec2f(t * 0.25 * params.c, 0.0);\n\n let base = floor(p);\n let f = fract(p);\n\n // Each tile flips on its own clock.\n let rnd = st_hash21(base);\n let phase = sin(t * 0.9 + rnd * TAU) * 0.5 + 0.5;\n let flipped = (rnd + phase * params.b) > 1.0;\n\n let line = arcTile(f, flipped, params.a * 0.12);\n\n let tint = st_palette(fract(rnd + t * 0.05));\n let bg = params.colorA.rgb * 0.14;\n var col = mix(bg, tint * params.intensity, line);\n\n // A soft bloom off the strokes keeps it from looking flat.\n col = col + tint * line * 0.25;\n\n return st_post(col, uv);\n}\n";
|
|
18
28
|
readonly tunnel: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// tunnel\n// ---------------------------------------------------------------------------\n\n// Tunnel — a raymarched corridor with emissive rings and distance fog.\n\n// The corridor centre wanders as you fly down it. Keep the amplitude well\n// under the radius, or the ray leaves the tube before it has travelled.\nfn tunnelPath(z : f32) -> vec2f {\n return vec2f(sin(z * 0.28) * 0.42, cos(z * 0.21) * 0.34);\n}\n\n// Distance from a point to the wall, measured from inside the tube.\nfn sceneSdf(pos : vec3f, radius : f32, ribs : f32) -> f32 {\n let q = pos.xy - tunnelPath(pos.z);\n let r = length(q);\n let theta = atan2(q.y, q.x);\n let corrugation = sin(pos.z * 2.4) * 0.05 * ribs + sin(theta * 6.0 + pos.z * 0.5) * 0.05 * ribs;\n return radius + corrugation - r;\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * 2.0;\n let t = st_time() * 2.0;\n\n let ro = vec3f(tunnelPath(t), t);\n // Aim slightly along the corridor so the walls sweep past rather than\n // sitting still around a fixed vanishing point.\n let ahead = tunnelPath(t + 2.0) - tunnelPath(t);\n let rd = normalize(vec3f(p / max(params.scale, 0.2) + ahead * 0.45, 1.6));\n\n var travelled = 0.05;\n var hit = false;\n for (var i = 0; i < 96; i = i + 1) {\n let d = sceneSdf(ro + rd * travelled, params.a, params.b);\n if (d < 0.004 * travelled) {\n hit = true;\n break;\n }\n travelled = travelled + max(d * 0.7, 0.01);\n if (travelled > 30.0) { break; }\n }\n\n if (!hit) {\n return st_post(vec3f(0.0), uv);\n }\n\n let pos = ro + rd * travelled;\n let q = pos.xy - tunnelPath(pos.z);\n let theta = atan2(q.y, q.x);\n\n // Wall texture in tunnel space: seams around, plates along. Both are thin\n // lines on a dark wall — wide bands read as a kaleidoscope, not a corridor.\n let seams = 1.0 - smoothstep(0.0, 0.16, abs(fract(theta / TAU * 8.0) - 0.5) * 2.0);\n let plates = 1.0 - smoothstep(0.0, 0.2, abs(fract(pos.z * 0.9) - 0.5) * 2.0);\n let grime = st_fbm2(vec2f(theta * 2.4, pos.z * 0.7), 3);\n let surface = clamp(0.12 + grime * 0.35 + max(seams, plates) * 0.55, 0.0, 1.0);\n\n // Cheap normal from the SDF gradient, lit from a lamp at the camera.\n let e = vec2f(0.01, 0.0);\n let n = normalize(vec3f(\n sceneSdf(pos + e.xyy, params.a, params.b) - sceneSdf(pos - e.xyy, params.a, params.b),\n sceneSdf(pos + e.yxy, params.a, params.b) - sceneSdf(pos - e.yxy, params.a, params.b),\n sceneSdf(pos + e.yyx, params.a, params.b) - sceneSdf(pos - e.yyx, params.a, params.b),\n ));\n let lamp = clamp(dot(n, -rd), 0.0, 1.0);\n\n // Falling off with distance as well as with the lamp angle is what makes\n // the near wall read as near.\n let nearness = 1.0 / (1.0 + travelled * 0.35);\n var col = st_palette(clamp(surface, 0.0, 1.0))\n * (0.10 + 1.15 * lamp * nearness) * params.intensity;\n\n // Emissive rings at fixed intervals down the corridor.\n let ring = pow(1.0 - abs(fract(pos.z * 0.5) * 2.0 - 1.0), 20.0);\n col = col + st_palette(1.0) * ring * params.c * 5.0;\n\n // Fog swallows the far end.\n col = col * exp(-travelled * 0.10 * (0.4 + params.d * 2.4));\n\n return st_post(st_tonemap(col), uv);\n}\n";
|
|
19
29
|
readonly voronoi: "// shadertown shared prelude — prepended to every shader at build time.\n// One uniform block is the whole ABI: every shader reads the same struct,\n// so the runtime can swap shaders without changing a binding.\n\nstruct Params {\n resolution : vec2f,\n pointer : vec2f,\n time : f32,\n speed : f32,\n scale : f32,\n intensity : f32,\n vibrancy : f32,\n grain : f32,\n vignette : f32,\n warp : f32,\n a : f32,\n b : f32,\n c : f32,\n d : f32,\n colorA : vec4f,\n colorB : vec4f,\n colorC : vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params : Params;\n\nconst PI : f32 = 3.141592653589793;\nconst TAU : f32 = 6.283185307179586;\n\nfn st_time() -> f32 {\n return params.time * params.speed;\n}\n\n// Centred, aspect-corrected coordinates. y stays in [-0.5, 0.5].\nfn st_centered(uv : vec2f) -> vec2f {\n let aspect = params.resolution.x / max(params.resolution.y, 1.0);\n return vec2f((uv.x - 0.5) * aspect, uv.y - 0.5);\n}\n\nfn st_aspect() -> f32 {\n return params.resolution.x / max(params.resolution.y, 1.0);\n}\n\nfn st_rot(angle : f32) -> mat2x2f {\n let s = sin(angle);\n let c = cos(angle);\n return mat2x2f(c, -s, s, c);\n}\n\n// --- hashing ---------------------------------------------------------------\n\nfn st_hash11(x : f32) -> f32 {\n var p = fract(x * 0.1031);\n p = p * (p + 33.33);\n return fract(p * (p + p));\n}\n\nfn st_hash21(p : vec2f) -> f32 {\n var p3 = fract(vec3f(p.xyx) * 0.1031);\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfn st_hash22(p : vec2f) -> vec2f {\n var p3 = fract(vec3f(p.xyx) * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\nfn st_hash33(p : vec3f) -> vec3f {\n var p3 = fract(p * vec3f(0.1031, 0.1030, 0.0973));\n p3 = p3 + dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// --- noise -----------------------------------------------------------------\n\nfn st_noise2(p : vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let a = st_hash21(i);\n let b = st_hash21(i + vec2f(1.0, 0.0));\n let c = st_hash21(i + vec2f(0.0, 1.0));\n let d = st_hash21(i + vec2f(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfn st_noise3(p : vec3f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n let n000 = st_hash33(i + vec3f(0.0, 0.0, 0.0)).x;\n let n100 = st_hash33(i + vec3f(1.0, 0.0, 0.0)).x;\n let n010 = st_hash33(i + vec3f(0.0, 1.0, 0.0)).x;\n let n110 = st_hash33(i + vec3f(1.0, 1.0, 0.0)).x;\n let n001 = st_hash33(i + vec3f(0.0, 0.0, 1.0)).x;\n let n101 = st_hash33(i + vec3f(1.0, 0.0, 1.0)).x;\n let n011 = st_hash33(i + vec3f(0.0, 1.0, 1.0)).x;\n let n111 = st_hash33(i + vec3f(1.0, 1.0, 1.0)).x;\n let x00 = mix(n000, n100, u.x);\n let x10 = mix(n010, n110, u.x);\n let x01 = mix(n001, n101, u.x);\n let x11 = mix(n011, n111, u.x);\n return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);\n}\n\nfn st_fbm2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.5);\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise2(shifted);\n shifted = rotation * shifted * 2.02;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\nfn st_fbm3(p : vec3f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n for (var i = 0; i < octaves; i = i + 1) {\n value = value + amplitude * st_noise3(shifted);\n shifted = shifted * 2.03;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// Ridged noise — sharp crests instead of soft hills.\nfn st_ridge2(p : vec2f, octaves : i32) -> f32 {\n var value = 0.0;\n var amplitude = 0.5;\n var shifted = p;\n let rotation = st_rot(0.7);\n for (var i = 0; i < octaves; i = i + 1) {\n let n = 1.0 - abs(st_noise2(shifted) * 2.0 - 1.0);\n value = value + amplitude * n * n;\n shifted = rotation * shifted * 2.11;\n amplitude = amplitude * 0.5;\n }\n return value;\n}\n\n// --- colour ----------------------------------------------------------------\n\n// Three-stop ramp built from the user's colours. t is clamped to [0, 1].\nfn st_palette(t : f32) -> vec3f {\n let x = clamp(t, 0.0, 1.0);\n let low = mix(params.colorA.rgb, params.colorB.rgb, smoothstep(0.0, 0.5, x));\n return mix(low, params.colorC.rgb, smoothstep(0.5, 1.0, x));\n}\n\n// Same ramp, but wrapping — good for cycling and polar shaders.\nfn st_paletteCyclic(t : f32) -> vec3f {\n let x = fract(t);\n let tri = 1.0 - abs(x * 2.0 - 1.0);\n return st_palette(tri);\n}\n\nfn st_luma(c : vec3f) -> f32 {\n return dot(c, vec3f(0.2126, 0.7152, 0.0722));\n}\n\nfn st_saturation(c : vec3f, amount : f32) -> vec3f {\n return mix(vec3f(st_luma(c)), c, amount);\n}\n\nfn st_tonemap(c : vec3f) -> vec3f {\n let x = max(c, vec3f(0.0));\n return (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n}\n\n// --- shared post chain -----------------------------------------------------\n// Every shader ends here, so vibrancy / grain / vignette behave identically\n// across the whole catalogue.\nfn st_post(color : vec3f, uv : vec2f) -> vec4f {\n var c = st_saturation(max(color, vec3f(0.0)), params.vibrancy);\n\n if (params.grain > 0.0) {\n let seed = uv * params.resolution + vec2f(params.time * 61.7, params.time * 37.3);\n let n = st_hash21(floor(seed));\n c = c + (n - 0.5) * params.grain;\n }\n\n if (params.vignette > 0.0) {\n let d = length(uv - vec2f(0.5)) * 1.41421356;\n c = c * (1.0 - params.vignette * smoothstep(0.22, 1.0, d));\n }\n\n return vec4f(clamp(c, vec3f(0.0), vec3f(1.0)), 1.0);\n}\n\n// ---------------------------------------------------------------------------\n// voronoi\n// ---------------------------------------------------------------------------\n\n// Voronoi — animated cells with lit edges. Crystalline, hard-edged.\n\nstruct Cell {\n f1 : f32,\n f2 : f32,\n id : vec2f,\n}\n\nfn voronoi(p : vec2f, t : f32, jitter : f32) -> Cell {\n let base = floor(p);\n let frac = fract(p);\n var f1 = 8.0;\n var f2 = 8.0;\n var id = vec2f(0.0);\n\n for (var y = -1; y <= 1; y = y + 1) {\n for (var x = -1; x <= 1; x = x + 1) {\n let neighbour = vec2f(f32(x), f32(y));\n let rnd = st_hash22(base + neighbour);\n let point = neighbour + 0.5 + (sin(t + rnd * TAU) * 0.5) * jitter;\n let d = length(point - frac);\n if (d < f1) {\n f2 = f1;\n f1 = d;\n id = base + neighbour;\n } else if (d < f2) {\n f2 = d;\n }\n }\n }\n return Cell(f1, f2, id);\n}\n\n@fragment fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {\n let p = st_centered(uv) * params.scale * 8.0;\n let t = st_time();\n\n let cell = voronoi(p, t, params.a);\n let edge = cell.f2 - cell.f1;\n\n let tone = st_hash21(cell.id);\n var col = st_palette(fract(tone + t * 0.03 * params.d)) * (0.25 + 0.75 * tone);\n\n // Depth: darken the cell interior, then draw the ridge between cells.\n col = col * mix(1.0, smoothstep(0.0, 0.55, cell.f1), params.c);\n\n let ridge = 1.0 - smoothstep(0.0, params.b, edge);\n col = col + st_palette(1.0) * ridge * params.intensity;\n\n return st_post(col, uv);\n}\n";
|
|
20
30
|
};
|
|
31
|
+
/** Whether a shader invents its picture or reads one from a bound texture. */
|
|
32
|
+
export declare const shaderKinds: {
|
|
33
|
+
readonly aurora: "generative";
|
|
34
|
+
readonly bloom: "source";
|
|
35
|
+
readonly bokeh: "generative";
|
|
36
|
+
readonly caustics: "generative";
|
|
37
|
+
readonly dither: "generative";
|
|
38
|
+
readonly duotone: "source";
|
|
39
|
+
readonly edges: "source";
|
|
40
|
+
readonly "film-grain": "generative";
|
|
41
|
+
readonly flow: "source";
|
|
42
|
+
readonly glass: "source";
|
|
43
|
+
readonly halftone: "generative";
|
|
44
|
+
readonly julia: "generative";
|
|
45
|
+
readonly kaleidoscope: "generative";
|
|
46
|
+
readonly lens: "source";
|
|
47
|
+
readonly "liquid-metal": "generative";
|
|
48
|
+
readonly "mesh-gradient": "generative";
|
|
49
|
+
readonly mosaic: "source";
|
|
50
|
+
readonly "neon-grid": "generative";
|
|
51
|
+
readonly plasma: "generative";
|
|
52
|
+
readonly reveal: "source";
|
|
53
|
+
readonly ripple: "generative";
|
|
54
|
+
readonly shockwave: "source";
|
|
55
|
+
readonly silk: "generative";
|
|
56
|
+
readonly starfield: "generative";
|
|
57
|
+
readonly swirl: "source";
|
|
58
|
+
readonly truchet: "generative";
|
|
59
|
+
readonly tunnel: "generative";
|
|
60
|
+
readonly voronoi: "generative";
|
|
61
|
+
};
|
|
21
62
|
export type GeneratedShaderName = keyof typeof shaderSources;
|
|
22
63
|
//# sourceMappingURL=sources.d.ts.map
|