From b7f0ee5b3bcade1f8f0928974aaf5567ab8117b4 Mon Sep 17 00:00:00 2001 From: mramirez Date: Sat, 27 Jul 2024 23:54:31 -0400 Subject: [PATCH] added background shader --- assets/shaders/ground.wgsl | 91 +++++++++++++++++++++++++++ src/game.rs | 6 ++ src/game/ground.rs | 123 +++++++++++++++++++++++++++++++++++++ src/screen/playing.rs | 4 ++ 4 files changed, 224 insertions(+) create mode 100644 assets/shaders/ground.wgsl create mode 100644 src/game/ground.rs diff --git a/assets/shaders/ground.wgsl b/assets/shaders/ground.wgsl new file mode 100644 index 0000000..1314195 --- /dev/null +++ b/assets/shaders/ground.wgsl @@ -0,0 +1,91 @@ +#import bevy_sprite::mesh2d_vertex_output::VertexOutput + +struct GroundShader { + camera_x: f32, + camera_y: f32, + random: f32, + time: f32, +}; + +@group(2) @binding(100) +var input: GroundShader; + +const ZOOM:f32 = 180.0; +const USE_BEAT_INPUT: bool = false; +const ANIMATE_SHADER: bool = false; +const GRID_LINE_COLOR: vec4f = vec4f(0.0); +const SPACE_COLOR_ALPHA: f32 = 0.3; +const CAMERA_OFFSET: f32 = 0.001953 / 2.0; // This number works well for tiling but I haven't figured out why yet, DON'T CHANGE IT +const GRID_RATIO:f32 = 10.; + +const CONTRAST_FACTOR: f32 = 1.5; +const SATURATION_FACTOR: f32 = 0.5; +const BRIGHTNESS: f32 = 0.5; +const BACKGROUND_COLOR: vec3f = vec3(0.2, 0.0, 0.4); +const COLOR_1: vec3f = vec3(0.0, 0.5, 0.0); // GREEN +const COLOR_2: vec3f = vec3(0.0, 0.5, 1.0); // BLUE +const COLOR_3: vec3f = vec3(1.0, 1.0, 0.0); // YELLOW +const COLOR_4: vec3f = vec3(1.0, 0.0, 0.0); // RED + +@fragment +fn fragment(in: VertexOutput) -> @location(0) vec4 { + let rand = max(select(1.0, input.random, USE_BEAT_INPUT), 0.0001); + var camera_x = input.camera_x * CAMERA_OFFSET; + var camera_y = input.camera_y * -CAMERA_OFFSET; + + let uv = (in.uv.xy + vec2(camera_x, camera_y)) * ZOOM; + + if is_line(uv) { + return GRID_LINE_COLOR; + } + + let square = floor(uv * GRID_RATIO * 0.1); + var tile_color = get_color_from_vec2f(square / rand); + + let average_color = average_rgb(tile_color, BACKGROUND_COLOR); + let contrasted_color = blend_towards(tile_color, average_color, CONTRAST_FACTOR); + let saturated_color = blend_towards(contrasted_color, rgb_to_grayscale(contrasted_color), SATURATION_FACTOR); + let brightened_color = blend_towards(saturated_color, vec3f(0.0), BRIGHTNESS); + + let final_color = brightened_color * select(1.0, cos(input.time), ANIMATE_SHADER); + + return vec4(final_color, SPACE_COLOR_ALPHA); +} + +fn average_rgb(color1: vec3, color2: vec3) -> vec3 { + return (color1 + color2) / 2.0; +} + +fn blend_towards(color: vec3, toward_color: vec3, factor: f32) -> vec3 { + return mix(color, toward_color, factor); +} + +fn rgb_to_grayscale(color: vec3) -> vec3 { + let grayscale_value = dot(color, vec3(0.299, 0.587, 0.114)); + return vec3(grayscale_value, grayscale_value, grayscale_value); +} + +fn get_color_from_vec2f(v: vec2) -> vec3 { + let index = hash_vec2f(v); + return get_color(index); +} + +fn get_color(index: u32) -> vec3 { + switch (index) { + case 1u: { return COLOR_1; } + case 2u: { return COLOR_2; } + case 3u: { return COLOR_3; } + case 4u: { return COLOR_4; } + default: { return BACKGROUND_COLOR; } + } +} + +fn hash_vec2f(v: vec2) -> u32 { + let pattern_size = 1000.0; + return u32(sin(cos(v.x) * tan(v.y)) * pattern_size) % 10; +} + +fn is_line(uv: vec2)-> bool { + let i = step(fract(uv), vec2(1.0/GRID_RATIO)); + return ((1.0-i.x) * (1.0-i.y)) < 0.5; +} \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index d2e61cf..224c1c1 100644 --- a/src/game.rs +++ b/src/game.rs @@ -5,6 +5,7 @@ pub mod audio; pub mod card; pub mod cleanup; pub mod combat; +pub mod ground; pub mod spotlight; pub mod sprite; pub mod wave; @@ -28,6 +29,7 @@ pub(super) fn plugin(app: &mut App) { card::plugin, cleanup::plugin, combat::plugin, + ground::plugin, spotlight::plugin, sprite::plugin, wave::plugin, @@ -41,6 +43,7 @@ pub struct GameRoot { pub enemies: Entity, pub projectiles: Entity, pub vfx: Entity, + pub background: Entity, } impl Configure for GameRoot { @@ -57,12 +60,14 @@ impl FromWorld for GameRoot { let enemies = world.spawn_with(root("Enemies")).id(); let projectiles = world.spawn_with(root("Projectiles")).id(); let vfx = world.spawn_with(root("Vfx")).id(); + let background = world.spawn_with(root("Background")).id(); Self { players, enemies, projectiles, vfx, + background, } } } @@ -72,6 +77,7 @@ fn clear_game_root(mut commands: Commands, game_root: Res) { commands.entity(game_root.enemies).despawn_descendants(); commands.entity(game_root.projectiles).despawn_descendants(); commands.entity(game_root.vfx).despawn_descendants(); + commands.entity(game_root.background).despawn_descendants(); } fn root(name: impl Into>) -> impl EntityCommand { diff --git a/src/game/ground.rs b/src/game/ground.rs new file mode 100644 index 0000000..dec5fcd --- /dev/null +++ b/src/game/ground.rs @@ -0,0 +1,123 @@ +use bevy::prelude::*; +use bevy::reflect::TypePath; +use bevy::render::render_resource::AsBindGroup; +use bevy::render::render_resource::ShaderRef; +use bevy::render::render_resource::ShaderType; +use bevy::sprite::Material2d; +use bevy::sprite::Material2dPlugin; +use bevy::sprite::MaterialMesh2dBundle; +use pyri_state::prelude::*; + +use crate::core::UpdateSet; +use crate::game::audio::music::on_full_beat; +use crate::screen::Screen; +use crate::util::prelude::*; + +pub(super) fn plugin(app: &mut App) { + app.configure::(); +} + +const GROUND_Z_INDEX: f32 = -10.0; +const GROUND_MESH_SIZE: f32 = 1024.0; +const GROUND_SNAP_INTERVAL: f32 = GROUND_MESH_SIZE / 4.0; +const GROUND_SNAP: Vec2 = Vec2::splat(GROUND_SNAP_INTERVAL); + +#[derive(Resource)] +pub struct Ground { + material: Handle, + mesh: Handle, +} + +impl Configure for Ground { + fn configure(app: &mut App) { + app.add_plugins(Material2dPlugin::::default()); + app.init_resource::(); + + app.add_systems( + Update, + Screen::Playing.on_update(( + update_background, + update_background_beat + .in_set(UpdateSet::Update) + .run_if(on_full_beat(8)), + )), + ); + } +} + +impl FromWorld for Ground { + fn from_world(world: &mut World) -> Self { + let material = world + .resource_mut::>() + .add(GroundMaterial::default()); + let mesh = world + .resource_mut::>() + .add(Rectangle::default()); + + Self { material, mesh } + } +} + +#[derive(Default, AsBindGroup, Asset, TypePath, Debug, Clone)] +pub struct GroundMaterial { + #[uniform(100)] + uniforms: Uniforms, +} + +#[derive(ShaderType, Default, Clone, Debug)] +struct Uniforms { + pub camera_x: f32, + pub camera_y: f32, + pub random: f32, + pub time: f32, +} + +impl Material2d for GroundMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/ground.wgsl".into() + } +} + +pub fn ground(mut entity: EntityWorldMut) { + let ground = r!(entity.world().get_resource::()); + let material = ground.material.clone(); + let mesh = ground.mesh.clone(); + + entity.insert(( + Name::new("Background"), + MaterialMesh2dBundle { + mesh: mesh.into(), + transform: Transform::from_translation(Vec2::ZERO.extend(GROUND_Z_INDEX)) + .with_scale(Vec3::splat(GROUND_MESH_SIZE)), + material, + ..default() + }, + )); +} + +fn update_background( + mut ground_material: ResMut>, + camera_query: Query<&Transform, (With, Without>)>, + mut ground_query: Query<&mut Transform, With>>, + time: Res