diff --git a/assets/shaders/sky.wgsl b/assets/shaders/sky.wgsl new file mode 100644 index 0000000..fd8d2d4 --- /dev/null +++ b/assets/shaders/sky.wgsl @@ -0,0 +1,49 @@ +#import bevy_sprite::mesh2d_vertex_output::VertexOutput +#import bevy_sprite::mesh2d_view_bindings::globals + +fn hash(p: vec2) -> f32 { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); +} + +fn noise(p: vec2) -> f32 { + let i = floor(p); + let f = fract(p); + let u = f * f * (3.0 - 2.0 * f); + return mix( + mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), + mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), + u.y, + ); +} + +fn fbm(p: vec2) -> f32 { + var value = 0.0; + var amplitude = 0.5; + var q = p; + for (var i = 0; i < 5; i++) { + value += amplitude * noise(q); + q *= 2.0; + amplitude *= 0.5; + } + return value; +} + +@fragment +fn fragment(mesh: VertexOutput) -> @location(0) vec4 { + let t = globals.time; + // uv.y is 0 at the top of the quad + let height = 1.0 - mesh.uv.y; + + let horizon = vec3(0.98, 0.85, 0.72); + let zenith = vec3(0.45, 0.68, 0.92); + var color = mix(horizon, zenith, smoothstep(0.0, 1.0, height)); + + // Two layers of slowly drifting clouds + let p = mesh.world_position.xy / 300.0; + let clouds = fbm(p + vec2(t * 0.03, 0.0)) * 0.6 + + fbm(p * 2.0 + vec2(t * 0.05, t * 0.01)) * 0.4; + let cover = smoothstep(0.45, 0.75, clouds); + color = mix(color, vec3(1.0), cover * 0.8); + + return vec4(color, 1.0); +} diff --git a/src/boids.rs b/src/boids.rs index 8a4390f..f04ff8a 100644 --- a/src/boids.rs +++ b/src/boids.rs @@ -51,7 +51,7 @@ fn setup( Vec2::new(-3.0, 3.0), Vec2::new(-3.0, -3.0), ))), - material: MeshMaterial2d(materials.add(Color::WHITE)), + material: MeshMaterial2d(materials.add(Color::BLACK)), }; spawn_boids( &mut commands, diff --git a/src/main.rs b/src/main.rs index b9eb092..e0e0d26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod boids; mod config; mod pointer; +mod sky; mod spatial_grid; mod ui; @@ -22,6 +23,7 @@ fn main() { .add_plugins(( config::ConfigPlugin, pointer::PointerPlugin, + sky::SkyPlugin, boids::BoidsPlugin, ui::ConfigUiPlugin, )) diff --git a/src/sky.rs b/src/sky.rs new file mode 100644 index 0000000..10e664b --- /dev/null +++ b/src/sky.rs @@ -0,0 +1,51 @@ +use bevy::{ + prelude::*, + render::render_resource::AsBindGroup, + shader::ShaderRef, + sprite_render::{Material2d, Material2dPlugin}, + window::PrimaryWindow, +}; + +pub struct SkyPlugin; + +impl Plugin for SkyPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(Material2dPlugin::::default()) + .add_systems(Startup, setup_sky) + .add_systems(Update, fit_sky_to_window); + } +} + +/// Animated sky, driven by the global shader time. +#[derive(Asset, TypePath, AsBindGroup, Clone)] +struct SkyMaterial {} + +impl Material2d for SkyMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/sky.wgsl".into() + } +} + +#[derive(Component)] +struct Sky; + +fn setup_sky( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(( + Sky, + Mesh2d(meshes.add(Rectangle::new(1.0, 1.0))), + MeshMaterial2d(materials.add(SkyMaterial {})), + // Behind the boids + Transform::from_xyz(0.0, 0.0, -10.0), + )); +} + +fn fit_sky_to_window( + window: Single<&Window, (With, Changed)>, + mut sky: Single<&mut Transform, With>, +) { + sky.scale = window.size().extend(1.0); +}