Files
chain-reaction/assets/shaders/background.wgsl
Gabriel Kaszewski 666df8f892 feat: Implement background shader and visual effects
- Added a new WGSL shader for background rendering with volumetric fog effects.
- Created components for game entities including Circle, Velocity, Explosion, and ChainReaction.
- Introduced game state management with GameState enum and LevelState resource.
- Implemented event system for explosion requests and circle destruction notifications.
- Developed plugins for game logic, input handling, movement, reactions, scoring, and UI.
- Configured game settings through GameConfig resource with adjustable parameters.
- Enhanced UI with configuration options and game over screens.
- Integrated spatial grid for efficient collision detection and explosion handling.
2026-01-04 04:42:43 +01:00

169 lines
5.9 KiB
WebGPU Shading Language

#import bevy_sprite::mesh2d_view_bindings::globals
struct BackgroundMaterial {
resolution: vec2<f32>,
time_offset: f32,
}
@group(2) @binding(0) var<uniform> 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<f32> = vec3<f32>(0.1, 0.0, 0.3); // Deep Nebula Purple
const COLOR2: vec3<f32> = vec3<f32>(0.8, 0.2, 0.5); // Hot Pink Highlights
// --- NOISE FUNCTIONS ---
fn hash(v: vec3<f32>) -> f32 {
return fract(sin(dot(v, vec3<f32>(11.51721, 67.12511, 9.7561))) * 1551.4172);
}
fn getNoiseFromVec3(v: vec3<f32>) -> f32 {
let rootV = floor(v);
let f = smoothstep(vec3<f32>(0.0), vec3<f32>(1.0), fract(v));
let n000 = hash(rootV);
let n001 = hash(rootV + vec3<f32>(0.0, 0.0, 1.0));
let n010 = hash(rootV + vec3<f32>(0.0, 1.0, 0.0));
let n011 = hash(rootV + vec3<f32>(0.0, 1.0, 1.0));
let n100 = hash(rootV + vec3<f32>(1.0, 0.0, 0.0));
let n101 = hash(rootV + vec3<f32>(1.0, 0.0, 1.0));
let n110 = hash(rootV + vec3<f32>(1.0, 1.0, 0.0));
let n111 = hash(rootV + vec3<f32>(1.0, 1.0, 1.0));
let n = mix(vec4<f32>(n000, n010, n100, n110), vec4<f32>(n001, n011, n101, n111), f.z);
let n_xy = mix(vec2<f32>(n.x, n.z), vec2<f32>(n.y, n.w), f.y);
return mix(n_xy.x, n_xy.y, f.x);
}
fn volumetricFog(v: vec3<f32>, noiseMod: f32) -> f32 {
var noise: f32 = 0.0;
var alpha: f32 = 1.0;
var point: vec3<f32> = 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<f32>(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<f32>, rayDirection: vec3<f32>, disMod: f32) -> vec3<f32> {
var stepLength = RENDER_DISTANCE / f32(RAYS_COUNT);
var fog = vec3<f32>(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<f32>(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<f32>,
@location(0) uv: vec2<f32>,
) -> @location(0) vec4<f32> {
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<f32>(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<f32>(
vec3<f32>(1.0, 0.0, 0.0), // Col 0
vec3<f32>(0.0, sin(angleX), cos(angleX)), // Col 1
vec3<f32>(0.0, -cos(angleX), sin(angleX)) // Col 2
);
// GLSL: mat3(sin, cos, 0, -cos, sin, 0, 0, 0, 1)
let rotZ = mat3x3<f32>(
vec3<f32>(sin(angleZ), cos(angleZ), 0.0), // Col 0
vec3<f32>(-cos(angleZ), sin(angleZ), 0.0), // Col 1
vec3<f32>(0.0, 0.0, 1.0) // Col 2
);
// GLSL: mat3(sin, 0, cos, 0, 1, 0, -cos, 0, sin)
let rotY = mat3x3<f32>(
vec3<f32>(sin(angleY), 0.0, -cos(angleY)), // Col 0
vec3<f32>(0.0, 1.0, 0.0), // Col 1
vec3<f32>(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<f32>(final_uv.x, near_plane_mod, final_uv.y));
// Apply Rotation
let rayDirection = rotation * ray_dir_local;
// 5. Camera Position
let cameraCenter = vec3<f32>(
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<f32>(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<f32>(0.0), vec3<f32>(1.5), fog));
return vec4<f32>(fog, 1.0);
}