// Pass 1: eight plasma sheets, one atlas tile each. Alpha stores linear depth. // Density field lit as a height surface: billowing volumes with a real terminator. precision highp float; uniform vec2 u_res; // size of one tile uniform float u_time; float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } float noise(vec2 p) { vec2 i = floor(p), f = fract(p); f = f*f*(3.0-2.0*f); return mix(mix(hash(i), hash(i+vec2(1.0,0.0)), f.x), mix(hash(i+vec2(0.0,1.0)), hash(i+vec2(1.0,1.0)), f.x), f.y); } float fbm(vec2 p) { float sum = 0.0, amp = 0.5; for (int i = 0; i < 4; i++) { sum += amp * noise(p); p = mat2(1.62, 1.17, -1.17, 1.62) * p; amp *= 0.5; } return sum; } float fbm2(vec2 p) { return 0.5 * noise(p) + 0.25 * noise(mat2(1.62, 1.17, -1.17, 1.62) * p); } const float EXTENT = 3.8; // half-width of a sheet in world units; must match the ice pass void main() { vec2 tile = floor(gl_FragCoord.xy / u_res); float layer = tile.x + tile.y * 4.0; vec2 uv = fract(gl_FragCoord.xy / u_res); vec2 p = (uv - 0.5) * 2.0 * vec2(EXTENT * u_res.x / u_res.y, EXTENT); p *= 0.85 + 0.10 * layer; // far sheets carry finer detail, which reads as distance float angle = hash(vec2(layer, 3.1)) * 6.2831853; p = mat2(cos(angle), sin(angle), -sin(angle), cos(angle)) * p; // Periodic time: every field returns exactly after 30 seconds. float phase = u_time * 6.2831853 / 30.0; vec2 o = vec2(cos(phase), sin(phase)); vec2 seed = vec2(layer * 17.3, layer * 9.1); // Two nested warp stages, the marbled-plasma motion. vec2 q = vec2(fbm(p * 0.50 + seed + o * 0.55), fbm(p * 0.50 + seed + 23.1 - o * 0.55)); vec2 r = vec2(fbm(p * 0.85 + 2.0 * q + seed + vec2(o.y, -o.x) * 0.40), fbm(p * 0.85 + 2.0 * q + seed + 31.7 - vec2(o.y, -o.x) * 0.40)); vec2 w = p * 1.10 + 2.6 * r + seed; // warped coords of the density field // The silhouette is shared by all sheets (slid a little per depth), so the gaps // stay open right through the stack instead of filling in with overlap. float mass = smoothstep(0.520, 0.548, fbm((p + o * 0.30 + vec2(0.10, -0.07) * layer) * 0.62 + r * 1.1 + 13.7)); // Light the density as a height surface: $cheap 2-octave gradient, strong terminator. float h = 0.05; vec2 slope = vec2(fbm2(w + vec2(h, 0.0)) - fbm2(w - vec2(h, 0.0)), fbm2(w + vec2(0.0, h)) - fbm2(w - vec2(0.0, h))) * (10.0 / (2.0 * h)); vec3 nrm = normalize(vec3(-slope, 1.0)); float lit = clamp(dot(nrm, normalize(vec3(-0.38, 0.52, 0.76))), 0.0, 1.0); // Folded planes across the volume, feathered so they never read as a grid. vec2 g = (p + r * 2.0) * 1.4; vec2 cell = floor(g), f = fract(g); float feather = smoothstep(0.0, 0.10, min(min(f.x, 1.0 - f.x), min(f.y, 1.0 - f.y))); float facet = mix(1.0, mix(0.70, 1.25, hash(cell + seed + step(1.0, f.x + f.y) * 37.0)), feather); float shade = 0.05 + 1.10 * lit * lit; // lit tops, near-black undersides float e = mass * facet * shade * 0.085 + mass * (1.0 - mass) * 4.0 * 0.040 // thin torn silhouette + mass * pow(lit, 14.0) * 0.110; // glint where a fold faces the light vec3 col = e * mix(vec3(0.02, 0.42, 0.60), vec3(0.08, 0.64, 0.88), facet * 0.5); // Faint dye in the open gaps so darkness reads as depth, not as a hole. col += smoothstep(0.40, 0.85, fbm(p * 0.30 + seed * 0.4 + o * 0.3)) * vec3(0.0003, 0.0011, 0.0015); gl_FragColor = vec4(col, (0.42 + layer * 0.26) / 2.6); }