#import bevy_sprite::mesh2d_view_bindings::globals struct BackgroundMaterial { resolution: vec2, time_offset: f32, } @group(2) @binding(0) var material: BackgroundMaterial; // --- CONFIGURATION --- const RAYS_COUNT: i32 = 32; // Increase to 64 if on PC for smoother quality const RENDER_DISTANCE: f32 = 5.0; // How far rays travel const NEAR_PLANE: f32 = 1.5; // Higher = Zoomed in (Less "Fisheye/Disco") const ROTATION_SPEED: f32 = 0.1; const JITTERING: f32 = 0.05; // Colors const COLOR1: vec3 = vec3(0.1, 0.0, 0.3); // Deep Nebula Purple const COLOR2: vec3 = vec3(0.8, 0.2, 0.5); // Hot Pink Highlights // --- NOISE FUNCTIONS --- fn hash(v: vec3) -> f32 { return fract(sin(dot(v, vec3(11.51721, 67.12511, 9.7561))) * 1551.4172); } fn getNoiseFromVec3(v: vec3) -> f32 { let rootV = floor(v); let f = smoothstep(vec3(0.0), vec3(1.0), fract(v)); let n000 = hash(rootV); let n001 = hash(rootV + vec3(0.0, 0.0, 1.0)); let n010 = hash(rootV + vec3(0.0, 1.0, 0.0)); let n011 = hash(rootV + vec3(0.0, 1.0, 1.0)); let n100 = hash(rootV + vec3(1.0, 0.0, 0.0)); let n101 = hash(rootV + vec3(1.0, 0.0, 1.0)); let n110 = hash(rootV + vec3(1.0, 1.0, 0.0)); let n111 = hash(rootV + vec3(1.0, 1.0, 1.0)); let n = mix(vec4(n000, n010, n100, n110), vec4(n001, n011, n101, n111), f.z); let n_xy = mix(vec2(n.x, n.z), vec2(n.y, n.w), f.y); return mix(n_xy.x, n_xy.y, f.x); } fn volumetricFog(v: vec3, noiseMod: f32) -> f32 { var noise: f32 = 0.0; var alpha: f32 = 1.0; var point: vec3 = v; // 5 Octaves of noise for(var i: i32 = 0; i < 5; i++) { noise += getNoiseFromVec3(point) * alpha; point *= 2.0; alpha *= 0.5; } noise *= 0.575; // Add time-based movement to edges let edge = 0.1 + getNoiseFromVec3(v * 0.5 + vec3(globals.time * 0.03)) * 0.8; // Apply "Beat" modulation (noiseMod) let modNoise = (0.5 - abs(edge * (1.0 + noiseMod * 0.05) - noise)) * 2.0; // Contrast Curve return (smoothstep(0.0, 0.9, modNoise * modNoise) + (1.0 - smoothstep(1.3, 0.6, modNoise))) * 0.2; } fn fogMarch(rayStart: vec3, rayDirection: vec3, disMod: f32) -> vec3 { var stepLength = RENDER_DISTANCE / f32(RAYS_COUNT); var fog = vec3(0.0); var point = rayStart; for(var i: i32 = 0; i < RAYS_COUNT; i++) { point += rayDirection * stepLength; fog += volumetricFog(point, disMod) * mix(COLOR1, COLOR2 * (1.0 + disMod * 0.5), getNoiseFromVec3((point + vec3(12.51, 52.167, 1.146)) * 0.5)) * getNoiseFromVec3(point * 0.2 + 20.0) * 2.0; // "Holes" in fog stepLength *= 1.05; // Exponential step (fewer samples close up) } // "Dither" the end result by the far-plane noise to hide banding let farNoise = pow(getNoiseFromVec3((rayStart + rayDirection * RENDER_DISTANCE)), 2.0); fog = (fog / f32(RAYS_COUNT)) * (farNoise * 3.0 + disMod * 0.5); return fog; } @fragment fn fragment( @builtin(position) position: vec4, @location(0) uv: vec2, ) -> @location(0) vec4 { let time = globals.time + material.time_offset; // 1. Aspect Ratio Correction // Map UVs from 0..1 to -1..1 let centered = (uv - 0.5) * 2.0; // Fix aspect ratio by scaling Y (Width is dominant) let aspect_y = material.resolution.y / material.resolution.x; let final_uv = vec2(centered.x, centered.y * aspect_y); // 2. Camera Rotation Angles let angleY = sin(time * ROTATION_SPEED * 2.0); let angleX = cos(time * 0.712 * ROTATION_SPEED); let angleZ = sin(time * 1.779 * ROTATION_SPEED); // 3. Rotation Matrices - COPIED EXACTLY FROM ORIGINAL GLSL // The original author used a non-standard axis alignment (sin on diagonal). // We construct these column-by-column to match GLSL memory layout. // GLSL: mat3(1, 0, 0, 0, sin, cos, 0, -cos, sin) let rotX = mat3x3( vec3(1.0, 0.0, 0.0), // Col 0 vec3(0.0, sin(angleX), cos(angleX)), // Col 1 vec3(0.0, -cos(angleX), sin(angleX)) // Col 2 ); // GLSL: mat3(sin, cos, 0, -cos, sin, 0, 0, 0, 1) let rotZ = mat3x3( vec3(sin(angleZ), cos(angleZ), 0.0), // Col 0 vec3(-cos(angleZ), sin(angleZ), 0.0), // Col 1 vec3(0.0, 0.0, 1.0) // Col 2 ); // GLSL: mat3(sin, 0, cos, 0, 1, 0, -cos, 0, sin) let rotY = mat3x3( vec3(sin(angleY), 0.0, -cos(angleY)), // Col 0 vec3(0.0, 1.0, 0.0), // Col 1 vec3(cos(angleY), 0.0, sin(angleY)) // Col 2 ); // 4. Ray Construction // Combine rotations let rotation = rotY * rotZ * rotX; // Forward movement (breathing effect) let near_plane_mod = NEAR_PLANE * (1.0 + sin(time * 0.2) * 0.4); // Original Vector: X=Right, Y=Forward (Depth), Z=Vertical let ray_dir_local = normalize(vec3(final_uv.x, near_plane_mod, final_uv.y)); // Apply Rotation let rayDirection = rotation * ray_dir_local; // 5. Camera Position let cameraCenter = vec3( sin(time * 0.2) * 10.0, time * 0.2 * 10.0, cos(time * 0.78 * 0.2 + 2.14) * 10.0 ); // 6. Ray Start with Jitter let jitter = (hash(vec3(final_uv + 4.0, fract(time) + 2.0)) - 0.5) * JITTERING; let rayStart = cameraCenter + rayDirection * jitter; // 7. Render // Fake "Audio Reactivity" using sine wave let beat = smoothstep(0.6, 0.9, pow(sin(time * 4.0) * 0.5 + 0.5, 2.0) * 0.06); var fog = fogMarch(rayStart, rayDirection, beat); // Post-Process fog *= 2.5 * 1.2; // Brightness fog += 0.07 * mix(COLOR1, COLOR2, 0.5); // Base Color // Tone mapping to prevent burnout fog = sqrt(smoothstep(vec3(0.0), vec3(1.5), fog)); return vec4(fog, 1.0); }