|
| 1 | +// Audio-reactive ribbon/heightfield aesthetic rendered in a single fullscreen pass. |
| 2 | +// Inspired by smooth velvet waves with golden accents. |
| 3 | + |
| 4 | +struct VsOut { |
| 5 | + @builtin(position) pos: vec4<f32>, |
| 6 | + @location(0) uv: vec2<f32>, |
| 7 | +}; |
| 8 | + |
| 9 | +struct Voice { |
| 10 | + // xyz position (x,z used), w = pulse (0..1.5) |
| 11 | + pos_pulse: vec4<f32>, |
| 12 | + // rgb base, a unused |
| 13 | + color: vec4<f32>, |
| 14 | +}; |
| 15 | + |
| 16 | +struct WaveUniforms { |
| 17 | + resolution: vec2<f32>, |
| 18 | + time: f32, |
| 19 | + ambient: f32, |
| 20 | + voices: array<Voice, 3>, |
| 21 | + swirl_uv: vec2<f32>, |
| 22 | + swirl_strength: f32, |
| 23 | + swirl_active: f32, |
| 24 | +}; |
| 25 | + |
| 26 | +@group(0) @binding(0) var<uniform> u: WaveUniforms; |
| 27 | + |
| 28 | +@vertex |
| 29 | +fn vs_fullscreen(@builtin(vertex_index) vid: u32) -> VsOut { |
| 30 | + var pos = array<vec2<f32>, 3>( |
| 31 | + vec2<f32>(-1.0, -3.0), |
| 32 | + vec2<f32>(-1.0, 1.0), |
| 33 | + vec2<f32>(3.0, 1.0), |
| 34 | + ); |
| 35 | + var uv = array<vec2<f32>, 3>( |
| 36 | + vec2<f32>(0.0, 2.0), |
| 37 | + vec2<f32>(0.0, 0.0), |
| 38 | + vec2<f32>(2.0, 0.0), |
| 39 | + ); |
| 40 | + var out: VsOut; |
| 41 | + out.pos = vec4<f32>(pos[vid], 0.0, 1.0); |
| 42 | + out.uv = uv[vid]; |
| 43 | + return out; |
| 44 | +} |
| 45 | + |
| 46 | +fn hash2(p: vec2<f32>) -> f32 { |
| 47 | + let h = dot(p, vec2<f32>(127.1, 311.7)); |
| 48 | + return fract(sin(h) * 43758.5453123); |
| 49 | +} |
| 50 | + |
| 51 | +fn fbm(p: vec2<f32>) -> f32 { |
| 52 | + var a = 0.0; |
| 53 | + var b = 0.5; |
| 54 | + var f = p; |
| 55 | + for (var i = 0; i < 5; i = i + 1) { |
| 56 | + a += b * sin(f.x) * cos(f.y); |
| 57 | + f *= 2.17; |
| 58 | + b *= 0.55; |
| 59 | + } |
| 60 | + return a; |
| 61 | +} |
| 62 | + |
| 63 | +// --- Wireframe helpers and layered waves version --- |
| 64 | +fn sd_segment(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 { |
| 65 | + let pa = p - a; |
| 66 | + let ba = b - a; |
| 67 | + let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); |
| 68 | + return length(pa - ba * h); |
| 69 | +} |
| 70 | + |
| 71 | +fn wireframe_bg(p: vec2<f32>) -> f32 { |
| 72 | + var d = 1e9; |
| 73 | + let centers = array<vec2<f32>, 3>( |
| 74 | + vec2<f32>(0.62, 0.28), |
| 75 | + vec2<f32>(0.80, 0.44), |
| 76 | + vec2<f32>(0.70, 0.72), |
| 77 | + ); |
| 78 | + let radii = array<f32, 3>(0.28, 0.18, 0.12); |
| 79 | + for (var i = 0; i < 3; i = i + 1) { |
| 80 | + d = min(d, abs(length(p - centers[i]) - radii[i])); |
| 81 | + } |
| 82 | + d = min(d, sd_segment(p, vec2<f32>(0.55, 0.20), vec2<f32>(0.85, 0.50))); |
| 83 | + d = min(d, sd_segment(p, vec2<f32>(0.62, 0.28), vec2<f32>(0.80, 0.44))); |
| 84 | + d = min(d, sd_segment(p, vec2<f32>(0.72, 0.70), vec2<f32>(0.86, 0.52))); |
| 85 | + return d; |
| 86 | +} |
| 87 | + |
| 88 | +@fragment |
| 89 | +fn fs_waves(inp: VsOut) -> @location(0) vec4<f32> { |
| 90 | + let uv = inp.uv; |
| 91 | + let aspect = u.resolution.x / max(u.resolution.y, 1.0); |
| 92 | + let cuv0 = (uv - 0.5) * vec2<f32>(aspect, 1.0); |
| 93 | + let t = u.time; |
| 94 | + |
| 95 | + // Background wireframe |
| 96 | + let gold = vec3<f32>(1.00, 0.86, 0.46); |
| 97 | + let d = wireframe_bg(uv); |
| 98 | + var col = vec3<f32>(0.04, 0.055, 0.10); |
| 99 | + let line = smoothstep(0.012, 0.002, d); |
| 100 | + // Make wireframe more visible; it will bloom subtly |
| 101 | + col += gold * line * (0.30 + 0.60 * u.ambient); |
| 102 | + |
| 103 | + // Three layered waves with parallax |
| 104 | + for (var L = 0; L < 3; L = L + 1) { |
| 105 | + let depth = f32(L); |
| 106 | + let par = mix(0.65, 1.25, depth / 2.0); |
| 107 | + var cuv = cuv0 * par + vec2<f32>(0.0, -0.10 * depth); |
| 108 | + // Swirl displacement around active pointer |
| 109 | + if u.swirl_active > 0.5 { |
| 110 | + let c = (u.swirl_uv - 0.5) * vec2<f32>(aspect, 1.0) * par; |
| 111 | + let v = cuv - c; |
| 112 | + let r = length(v); |
| 113 | + let ang = u.swirl_strength * 2.5 * exp(-1.8 * r); |
| 114 | + let cs = cos(ang); |
| 115 | + let sn = sin(ang); |
| 116 | + let rot = vec2<f32>(v.x * cs - v.y * sn, v.x * sn + v.y * cs); |
| 117 | + cuv = c + rot; |
| 118 | + } |
| 119 | + // Displace coordinates by nearby voices so dragging clearly affects visuals |
| 120 | + var disp = vec2<f32>(0.0); |
| 121 | + for (var i = 0; i < 3; i = i + 1) { |
| 122 | + let v = u.voices[i]; |
| 123 | + let p = vec2<f32>(v.pos_pulse.x, v.pos_pulse.z) * 0.33; |
| 124 | + let d = distance(cuv, p); |
| 125 | + let dir = normalize(cuv - p); |
| 126 | + let pulse = clamp(v.pos_pulse.w, 0.0, 1.5); |
| 127 | + let str = (0.12 + 0.45 * pulse) * exp(-1.8 * d); |
| 128 | + disp += dir * str; |
| 129 | + } |
| 130 | + cuv += disp; |
| 131 | + let tt = t * (0.30 + 0.08 * depth); |
| 132 | + let amp = mix(1.0, 2.2, depth / 2.0); |
| 133 | + var h = 0.0; |
| 134 | + // Broader ribbons along X with mild Y slant |
| 135 | + h += amp * (1.05 * sin((6.0 + 1.0 * depth) * cuv.x - 1.2 * tt)); |
| 136 | + h += amp * (0.65 * sin((9.0 + 1.5 * depth) * cuv.x + 0.8 * tt + 0.7 * cuv.y)); |
| 137 | + // Envelope so center has more curvature |
| 138 | + h *= (1.0 - 0.25 * abs(cuv.y)); |
| 139 | + // Add velvet noise |
| 140 | + h += 0.35 * fbm(cuv * 2.4 + vec2<f32>(0.22 * tt, -0.16 * tt)); |
| 141 | + for (var i = 0; i < 3; i = i + 1) { |
| 142 | + let v = u.voices[i]; |
| 143 | + let p = vec2<f32>(v.pos_pulse.x, v.pos_pulse.z) * 0.33; |
| 144 | + let dd = distance(cuv, p); |
| 145 | + let pulse = clamp(v.pos_pulse.w, 0.0, 1.5); |
| 146 | + h += (0.65 + 0.9 * pulse) * exp(-2.2 * dd) * sin(14.0 * dd - 2.0 * tt); |
| 147 | + h += 0.22 * (1.0 / (1.0 + 6.0 * dd)) * sin(7.0 * (cuv.x - p.x) + 1.5 * tt); |
| 148 | + } |
| 149 | + let e = 0.002; |
| 150 | + let hx = h - (0.55 * sin(6.0 * (cuv.x - e) - 1.4 * tt) + 0.35 * sin(10.0 * (cuv.x - e) + 0.9 * tt + 0.8 * cuv.y) + 0.25 * fbm((cuv - vec2<f32>(e, 0.0)) * 2.5 + vec2<f32>(0.2 * tt, -0.15 * tt))); |
| 151 | + let hy = h - (0.55 * sin(6.0 * cuv.x - 1.4 * tt) + 0.35 * sin(10.0 * cuv.x + 0.9 * tt + 0.8 * (cuv.y - e)) + 0.25 * fbm((cuv - vec2<f32>(0.0, e)) * 2.5 + vec2<f32>(0.2 * tt, -0.15 * tt))); |
| 152 | + let n = normalize(vec3<f32>(hx, hy, e)); |
| 153 | + let l1 = normalize(vec3<f32>(-0.4, 0.3, 0.85)); |
| 154 | + let l2 = normalize(vec3<f32>(0.6, -0.2, 0.75)); |
| 155 | + let diff = 0.65 * max(dot(n, l1), 0.0) + 0.35 * max(dot(n, l2), 0.0); |
| 156 | + let base = mix(vec3<f32>(0.03, 0.04, 0.08), vec3<f32>(0.12, 0.14, 0.26), diff + 0.15 * u.ambient); |
| 157 | + let cool = vec3<f32>(0.18, 0.45, 1.05); |
| 158 | + let warm = vec3<f32>(1.08, 0.86, 0.40); |
| 159 | + let k = clamp(0.5 + 1.1 * h, 0.0, 1.0); |
| 160 | + var lay = base + mix(cool * 0.45, warm * 0.55, k); |
| 161 | + let stripes = smoothstep(0.45, 0.5, abs(fract(h * 8.0) - 0.5)); |
| 162 | + lay += (1.0 - stripes) * gold * (0.18 + 0.30 * u.ambient); |
| 163 | + let view = vec3<f32>(0.0, 0.0, 1.0); |
| 164 | + let h1 = normalize(l1 + view); |
| 165 | + lay += vec3<f32>(1.0) * (0.18 * pow(max(dot(n, h1), 0.0), 72.0)); |
| 166 | + // Emissive crest glow captured by bloom |
| 167 | + let crest = smoothstep(0.84, 0.98, k); |
| 168 | + lay += gold * crest * (0.75 + 1.4 * u.ambient); |
| 169 | + // Visible voice focus points |
| 170 | + for (var i = 0; i < 3; i = i + 1) { |
| 171 | + let p = vec2<f32>(u.voices[i].pos_pulse.x, u.voices[i].pos_pulse.z) * 0.33; |
| 172 | + let dd = distance(cuv, p); |
| 173 | + lay += gold * exp(-40.0 * dd * dd) * 0.45; |
| 174 | + } |
| 175 | + let a = mix(0.55, 0.28, depth / 2.0); |
| 176 | + col = col * (1.0 - a) + lay * a; |
| 177 | + } |
| 178 | + |
| 179 | + let s = hash2(cuv0 * 600.0 + t); |
| 180 | + col += (step(0.992, s) * (s - 0.992) * 240.0) * gold * (0.35 + 0.55 * u.ambient); |
| 181 | + |
| 182 | + return vec4<f32>(col, 1.0); |
| 183 | +} |
| 184 | + |
| 185 | + |
0 commit comments