| 1 | #version 460 core |
| 2 | precision highp float; |
| 3 | |
| 4 | #include <flutter/runtime_effect.glsl> |
| 5 | |
| 6 | // Uniform layout (must match the order .setFloat() is called in Dart): |
| 7 | // 0-1: uSize (vec2) |
| 8 | // 2: uTime |
| 9 | // 3-5: uBaseColor (vec3, outer/base color of the orb) |
| 10 | // 6-8: uGlowColor (vec3, inner glow color) |
| 11 | uniform vec2 uSize; |
| 12 | uniform float uTime; |
| 13 | uniform vec3 uBaseColor; |
| 14 | uniform vec3 uGlowColor; |
| 15 | |
| 16 | out vec4 fragColor; |
| 17 | |
| 18 | // ---- Simplex noise (Ashima Arts, MIT licensed pattern) ---- |
| 19 | vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } |
| 20 | vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } |
| 21 | vec3 permute(vec3 x) { return mod289(((x * 34.0) + 1.0) * x); } |
| 22 | |
| 23 | float snoise(vec2 v) { |
| 24 | const vec4 C = vec4(0.211324865405187, 0.366025403784439, |
| 25 | -0.577350269189626, 0.024390243902439); |
| 26 | vec2 i = floor(v + dot(v, C.yy)); |
| 27 | vec2 x0 = v - i + dot(i, C.xx); |
| 28 | vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); |
| 29 | vec4 x12 = x0.xyxy + C.xxzz; |
| 30 | x12.xy -= i1; |
| 31 | i = mod289(i); |
| 32 | vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) |
| 33 | + i.x + vec3(0.0, i1.x, 1.0)); |
| 34 | vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); |
| 35 | m = m * m; |
| 36 | m = m * m; |
| 37 | vec3 x = 2.0 * fract(p * C.www) - 1.0; |
| 38 | vec3 h = abs(x) - 0.5; |
| 39 | vec3 ox = floor(x + 0.5); |
| 40 | vec3 a0 = x - ox; |
| 41 | m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); |
| 42 | vec3 g; |
| 43 | g.x = a0.x * x0.x + h.x * x0.y; |
| 44 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; |
| 45 | return 130.0 * dot(m, g); |
| 46 | } |
| 47 | |
| 48 | float fbm(vec2 p) { |
| 49 | float v = 0.0; |
| 50 | float amp = 0.5; |
| 51 | for (int i = 0; i < 5; i++) { |
| 52 | v += amp * snoise(p); |
| 53 | p *= 2.0; |
| 54 | amp *= 0.5; |
| 55 | } |
| 56 | return v; |
| 57 | } |
| 58 | |
| 59 | void main() { |
| 60 | vec2 fragCoord = FlutterFragCoord().xy; |
| 61 | vec2 center = uSize * 0.5; |
| 62 | float radiusPx = min(uSize.x, uSize.y) * 0.36; |
| 63 | vec2 uv = (fragCoord - center) / radiusPx; |
| 64 | float dist = length(uv); |
| 65 | |
| 66 | // soft circular falloff -> matches the blurred edge in the reference image |
| 67 | float sphereMask = 1.0 - smoothstep(0.75, 1.05, dist); |
| 68 | |
| 69 | // gentle breathing pulse for the inner glow size |
| 70 | float breathe = 0.5 + 0.5 * sin(uTime * 0.6); |
| 71 | |
| 72 | // slowly rotate coordinates to drive the swirling highlight bands |
| 73 | float angle = uTime * 0.15; |
| 74 | float ca = cos(angle); |
| 75 | float sa = sin(angle); |
| 76 | vec2 ruv = vec2(uv.x * ca - uv.y * sa, uv.x * sa + uv.y * ca); |
| 77 | |
| 78 | // thin swirl streaks (stretched fbm), drifting over time |
| 79 | float streaks = fbm(vec2(ruv.x * 2.0, ruv.y * 6.0) + vec2(uTime * 0.05, 0.0)); |
| 80 | streaks = smoothstep(0.1, 0.9, streaks); |
| 81 | |
| 82 | // inner core glow, offset slightly like the reference, pulsing + drifting |
| 83 | vec2 glowCenter = vec2(-0.15, 0.25) + 0.05 * vec2(cos(uTime * 0.3), sin(uTime * 0.4)); |
| 84 | float glowDist = length(uv - glowCenter); |
| 85 | float innerGlow = exp(-glowDist * glowDist * (2.5 - 0.4 * breathe)); |
| 86 | |
| 87 | // base radial gradient: outer base color -> inner glow color |
| 88 | vec3 color = mix(uBaseColor, uGlowColor, innerGlow); |
| 89 | |
| 90 | // subtle rotating light streaks near the rim |
| 91 | float rim = smoothstep(0.55, 1.0, dist) * (1.0 - smoothstep(0.95, 1.05, dist)); |
| 92 | color += streaks * rim * 0.35; |
| 93 | |
| 94 | // soft white wash toward the edges, matching the blurred-glass look |
| 95 | color = mix(color, vec3(1.0), smoothstep(0.5, 1.05, dist) * 0.6); |
| 96 | |
| 97 | float alpha = sphereMask; |
| 98 | fragColor = vec4(color * alpha, alpha); |
| 99 | } |