diff --git a/assets/shaders/sprite.wgsl b/assets/shaders/sprite.wgsl new file mode 100644 index 0000000..76f8761 --- /dev/null +++ b/assets/shaders/sprite.wgsl @@ -0,0 +1,44 @@ +#import bevy_pbr::{ + pbr_fragment::pbr_input_from_standard_material, + pbr_functions::alpha_discard, +} + +#ifdef PREPASS_PIPELINE +#import bevy_pbr::{ + prepass_io::{VertexOutput, FragmentOutput}, + pbr_deferred_functions::deferred_output, +} +#else +#import bevy_pbr::{ + forward_io::{VertexOutput, FragmentOutput}, + pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing}, +} +#endif + +@fragment +fn fragment( + in: VertexOutput, + @builtin(front_facing) is_front: bool, +) -> FragmentOutput { + // generate a PbrInput struct from the StandardMaterial bindings + var pbr_input = pbr_input_from_standard_material(in, is_front); + + if (pbr_input.material.base_color.a < 0.5) { + discard; + } + +#ifdef PREPASS_PIPELINE + // in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader. + let out = deferred_output(in, pbr_input); +#else + var out: FragmentOutput; + // apply lighting + out.color = apply_pbr_lighting(pbr_input); + + // apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr) + // note this does not include fullscreen postprocessing effects like bloom. + out.color = main_pass_post_lighting_processing(pbr_input, out.color); +#endif + + return out; +} \ No newline at end of file diff --git a/assets/shaders/sprite_prepass.wgsl b/assets/shaders/sprite_prepass.wgsl new file mode 100644 index 0000000..dc00930 --- /dev/null +++ b/assets/shaders/sprite_prepass.wgsl @@ -0,0 +1,88 @@ +#import bevy_pbr::{ + prepass_bindings, + mesh_functions, + prepass_io::{Vertex, VertexOutput, FragmentOutput}, + skinning, + morph, + mesh_view_bindings::{view, previous_view_proj}, + rgb9e5::vec3_to_rgb9e5_, + pbr_prepass_functions::prepass_alpha_discard, + pbr_prepass_functions, + pbr_bindings::material, + pbr_deferred_functions, + pbr_types::{ + STANDARD_MATERIAL_FLAGS_UNLIT_BIT, +STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT + }, + pbr_functions, + prepass_io, + pbr_types +} +#import bevy_render::instance_index::get_instance_index +#import bevy_shader_utils::simplex_noise_3d::simplex_noise_3d + +@group(1) @binding(101) var base_teture: texture_2d; +@group(1) @binding(102) var base_teture_sampler: sampler; + + + +// basically all of this prepass shader is just copy/pasted from +// the bevy pbr prepass shader. +@fragment +fn fragment( + in: VertexOutput, + @builtin(front_facing) is_front: bool, +) -> FragmentOutput { + pbr_prepass_functions::prepass_alpha_discard(in); + + var out: FragmentOutput; + +#ifdef DEPTH_CLAMP_ORTHO + out.frag_depth = in.clip_position_unclamped.z; +#endif // DEPTH_CLAMP_ORTHO + +#ifdef NORMAL_PREPASS + // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit + + if (material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u { + let double_sided = (material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u; + + let world_normal = pbr_functions::prepare_world_normal( + in.world_normal, + double_sided, + is_front, + ); + + let normal = pbr_functions::apply_normal_mapping( + material.flags, + world_normal, + double_sided, + is_front, +#ifdef VERTEX_TANGENTS +#ifdef STANDARDMATERIAL_NORMAL_MAP + in.world_tangent, +#endif // STANDARDMATERIAL_NORMAL_MAP +#endif // VERTEX_TANGENTS +#ifdef VERTEX_UVS + in.uv, +#endif // VERTEX_UVS + view.mip_bias, + ); + + out.normal = vec4(normal * 0.5 + vec3(0.5), 1.0); + } else { + out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0); + } + + + +#endif // NORMAL_PREPASS + +#ifdef MOTION_VECTOR_PREPASS + out.motion_vector = pbr_prepass_functions::calculate_motion_vector(in.world_position, in.previous_world_position); +#endif + + if textureSample(base_teture, base_teture_sampler, in.uv).a < 0.5 { discard; }; + + return out; +} \ No newline at end of file diff --git a/src/common_storage.rs b/src/common_storage.rs index b64f48d..7d103ac 100644 --- a/src/common_storage.rs +++ b/src/common_storage.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; #[derive(Resource)] pub struct CommonStorage { - pub plane : Handle + pub plane: Handle, } pub struct CommonStoragePlugin; @@ -13,13 +13,13 @@ impl Plugin for CommonStoragePlugin { } } -pub fn init_common_storage( - mut commands : Commands, - mut meshes : ResMut>, -) { +pub fn init_common_storage(mut commands: Commands, mut meshes: ResMut>) { let storage = CommonStorage { - plane : meshes.add(Mesh::from(shape::Plane { size: 1.0, ..default() })) + plane: meshes.add(Mesh::from(shape::Plane { + size: 1.0, + ..default() + })), }; commands.insert_resource(storage); -} \ No newline at end of file +} diff --git a/src/debug_diagnostic.rs b/src/debug_diagnostic.rs index 6c8c459..b9bfdf0 100644 --- a/src/debug_diagnostic.rs +++ b/src/debug_diagnostic.rs @@ -9,7 +9,6 @@ impl Plugin for DiagnosticPlugin { (setup_diagnostic_panel, apply_deferred, setup_counter), ) .add_systems(Update, (fps_counting,)) - .add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::default()); } } diff --git a/src/lib.rs b/src/lib.rs index 4796b4d..17eb283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,14 @@ #![allow(clippy::type_complexity)] +pub mod common_storage; pub mod debug_diagnostic; pub mod physics; pub mod player; +pub mod safe_area; pub mod sheep; +pub mod sprite_material; pub mod test_level; -pub mod safe_area; pub mod torch; -pub mod common_storage; use std::f32::consts::PI; @@ -44,10 +45,12 @@ impl Plugin for GamePlugin { } app.add_plugins(( - player::PlayerPlugin, - physics::PhysicsPlugin, - common_storage::CommonStoragePlugin, + player::PlayerPlugin, + physics::PhysicsPlugin, + common_storage::CommonStoragePlugin, torch::TorchPlugin, + safe_area::SafeAreaPlugin, + sprite_material::SpriteMaterialPlugin, )); app.add_systems(Startup, (test_level::setup, sheep::setup)); @@ -65,4 +68,3 @@ impl Plugin for GamePlugin { pub fn get_sprite_rotation() -> Quat { Quat::from_euler(EulerRot::XYZ, PI / 2.0 - PI / 4.0, 0.0, 0.0) } - diff --git a/src/player.rs b/src/player.rs index cb30556..9b9c319 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,6 +1,10 @@ -use bevy::{input::mouse::MouseWheel, prelude::*, window::PrimaryWindow}; +use bevy::{input::mouse::MouseWheel, pbr::ExtendedMaterial, prelude::*, window::PrimaryWindow}; -use crate::{get_sprite_rotation, physics::Velocity}; +use crate::{ + get_sprite_rotation, + physics::Velocity, + sprite_material::{SpriteExtension, SpriteMaterial}, +}; const DOG_PATH: &str = "test/dog.png"; @@ -72,22 +76,28 @@ fn spawn_player_by_event( asset_server: Res, mut meshes: ResMut>, mut materials: ResMut>, + mut sprite_material: ResMut>, ) { for event in event_reader.read() { let plane = meshes.add(Mesh::from(shape::Plane { size: 1.0, ..default() })); - let material = materials.add(StandardMaterial { - base_color_texture: Some(asset_server.load(DOG_PATH)), - alpha_mode: AlphaMode::Blend, - ..default() + let material = sprite_material.add(SpriteMaterial { + base: StandardMaterial { + base_color_texture: Some(asset_server.load(DOG_PATH)), + alpha_mode: AlphaMode::Opaque, + ..default() + }, + extension: SpriteExtension { + base_teture: Some(asset_server.load(DOG_PATH)), + }, }); info!("Spawn player at {:?}", event.position); commands.spawn(( - PbrBundle { + MaterialMeshBundle { mesh: plane.clone(), material: material.clone(), transform: Transform::from_translation(event.position + Vec3::new(0.0, 2.5, 0.0)) diff --git a/src/safe_area.rs b/src/safe_area.rs index a06f6f5..5700ac1 100644 --- a/src/safe_area.rs +++ b/src/safe_area.rs @@ -1 +1,35 @@ -//safe area description and logic \ No newline at end of file +//safe area description and logic + +use std::f32::consts::PI; + +use bevy::prelude::*; + +pub struct SafeAreaPlugin; + +impl Plugin for SafeAreaPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Update, draw_safe_area); + } +} + +#[derive(Component)] +pub enum SafeArea { + Rect { pos: Vec2, size: Vec2 }, + Ellipse { pos1: Vec2, pos2: Vec2, radius: f32 }, +} + +fn draw_safe_area(mut gizmos: Gizmos, query: Query<&SafeArea>) { + for safe_area in query.iter() { + match safe_area { + SafeArea::Rect { pos, size } => { + gizmos.rect( + Vec3::new(pos.x, 0.001, pos.y), + Quat::from_euler(EulerRot::XYZ, PI / 2.0, 0.0, 0.0), + *size, + Color::GREEN, + ); + } + SafeArea::Ellipse { pos1, pos2, radius } => {} + } + } +} diff --git a/src/sheep.rs b/src/sheep.rs index 1e4f97b..c429419 100644 --- a/src/sheep.rs +++ b/src/sheep.rs @@ -1,9 +1,9 @@ use std::f32::consts::PI; -use bevy::prelude::*; +use bevy::{pbr::ExtendedMaterial, prelude::*}; use rand::Rng; -use crate::{physics::Velocity, player::Bark}; +use crate::{physics::Velocity, player::Bark, sprite_material::SpriteExtension}; const SHEEP_PATH: &str = "test/sheep.png"; @@ -92,6 +92,7 @@ pub fn setup( mut meshes: ResMut>, mut materials: ResMut>, asset_server: Res, + mut sprite_material: ResMut>>, ) { let square = meshes.add( shape::Plane { @@ -103,11 +104,22 @@ pub fn setup( let sheep_texture: Handle = asset_server.load(SHEEP_PATH); let sheep_material = materials.add(StandardMaterial { - base_color_texture: Some(sheep_texture), + base_color_texture: Some(sheep_texture.clone()), alpha_mode: AlphaMode::Blend, ..default() }); + let sheep_sprite_material = sprite_material.add(ExtendedMaterial { + base: StandardMaterial { + base_color_texture: Some(sheep_texture.clone()), + alpha_mode: AlphaMode::Opaque, + ..default() + }, + extension: SpriteExtension { + base_teture: Some(sheep_texture.clone()), + }, + }); + //spawn sheeps let r = 50.0; let mut rng = rand::thread_rng(); @@ -124,9 +136,9 @@ pub fn setup( } commands.spawn(( - PbrBundle { + MaterialMeshBundle { mesh: square.clone(), - material: sheep_material.clone(), + material: sheep_sprite_material.clone(), transform: Transform::from_xyz(pos.x, pos.y + 3.0, pos.z) .with_rotation(Quat::from_euler( EulerRot::XYZ, diff --git a/src/sprite_material.rs b/src/sprite_material.rs new file mode 100644 index 0000000..5eca0aa --- /dev/null +++ b/src/sprite_material.rs @@ -0,0 +1,40 @@ +//just for testing. Gizmo works very bad with AlphaMode::Blend. Its alternative shader with discard on depth step + +use bevy::{ + pbr::{ExtendedMaterial, MaterialExtension}, + prelude::*, + render::render_resource::{AsBindGroup, ShaderRef}, +}; + +pub type SpriteMaterial = ExtendedMaterial; + +pub struct SpriteMaterialPlugin; + +impl Plugin for SpriteMaterialPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(MaterialPlugin::< + ExtendedMaterial, + >::default()); + } +} + +#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] +pub struct SpriteExtension { + #[texture(101)] + #[sampler(102)] + pub base_teture: Option>, +} + +impl MaterialExtension for SpriteExtension { + fn fragment_shader() -> ShaderRef { + "shaders/sprite.wgsl".into() + } + + fn deferred_fragment_shader() -> ShaderRef { + "shaders/sprite.wgsl".into() + } + + fn prepass_fragment_shader() -> ShaderRef { + "shaders/sprite_prepass.wgsl".into() + } +} diff --git a/src/test_level.rs b/src/test_level.rs index c7e42cd..caff7a2 100644 --- a/src/test_level.rs +++ b/src/test_level.rs @@ -2,7 +2,7 @@ use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; use rand::prelude::*; use std::f32::consts::PI; -use crate::{player::SpawnPlayer, torch::SpawnTorch}; +use crate::{player::SpawnPlayer, safe_area::SafeArea, torch::SpawnTorch}; const TREE_PATH: &str = "test/pine.png"; @@ -12,7 +12,7 @@ pub fn setup( mut materials: ResMut>, asset_server: Res, mut spawn_player_event: EventWriter, - mut spawn_torch: EventWriter + mut spawn_torch: EventWriter, ) { commands.spawn(Camera3dBundle { transform: Transform::from_xyz(0.0, 100.0, 100.0).looking_at(Vec3::ZERO, Vec3::Y), @@ -110,14 +110,13 @@ pub fn setup( let num_of_torchs = 4; for _ in 0..num_of_torchs { - let pos = Vec3::new( - rng.gen_range(-r..r), - 0.0, - rng.gen_range(-r..r), - ); - - spawn_torch.send(SpawnTorch { - position: pos, - }); + let pos = Vec3::new(rng.gen_range(-r..r), 0.0, rng.gen_range(-r..r)); + + spawn_torch.send(SpawnTorch { position: pos }); } + + commands.spawn(SafeArea::Rect { + pos: Vec2::ZERO, + size: Vec2::new(r, r), + }); } diff --git a/src/torch.rs b/src/torch.rs index 066d99b..634621b 100644 --- a/src/torch.rs +++ b/src/torch.rs @@ -8,9 +8,7 @@ pub struct TorchPlugin; impl Plugin for TorchPlugin { fn build(&self, app: &mut App) { - app - .add_event::() - + app.add_event::() .add_systems(Startup, setup_material) .add_systems(Update, spawn_torch); } @@ -36,7 +34,7 @@ pub struct TorchMaterial(pub Handle); fn setup_material( mut commands: Commands, mut materials: ResMut>, - asset_server : Res, + asset_server: Res, ) { commands.insert_resource(TorchMaterial(materials.add(StandardMaterial { base_color_texture: Some(asset_server.load(TORCH_PATH)), @@ -49,9 +47,8 @@ fn setup_material( fn spawn_torch( mut commands: Commands, mut events: EventReader, - common_storage : Res, + common_storage: Res, torch_material: Res, - ) { for event in events.read() { let torch = Torch { @@ -62,27 +59,34 @@ fn spawn_torch( radius: 50.0, }; - commands.spawn(( - torch, - PbrBundle { - transform: Transform::from_translation(event.position + Vec3::new(0.0, 2.5, 0.0)).with_rotation(get_sprite_rotation()).with_scale(Vec3::new(1.0, 1.0, 7.0)), - material: torch_material.0.clone(), - mesh: common_storage.plane.clone(), - ..default() - } - )).with_children(|parent| { - parent.spawn(PointLightBundle { - point_light: PointLight { - color: Color::ORANGE, - intensity: 10000.0, - range: 300.0, - shadows_enabled: true, + commands + .spawn(( + torch, + PbrBundle { + transform: Transform::from_translation( + event.position + Vec3::new(0.0, 2.5, 0.0), + ) + .with_rotation(get_sprite_rotation()) + .with_scale(Vec3::new(1.0, 1.0, 7.0)), + material: torch_material.0.clone(), + mesh: common_storage.plane.clone(), ..default() }, - transform: Transform::from_translation(Vec3::new(0.0, 2.0, -0.4)), - ..default() + )) + .with_children(|parent| { + parent.spawn(PointLightBundle { + point_light: PointLight { + color: Color::ORANGE, + intensity: 10000.0, + range: 300.0, + radius: 100.0, + shadows_enabled: false, + ..default() + }, + transform: Transform::from_translation(Vec3::new(0.0, 2.0, -0.4)), + ..default() + }); }); - }); } events.clear(); -} \ No newline at end of file +}