From cfa7fb9529dee6b1bf7fe13eea0500f9f3702fba Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Wed, 1 Feb 2023 00:08:19 -0800 Subject: [PATCH 01/14] Better cascades config builder, tweak example configs --- crates/bevy_pbr/src/light.rs | 132 +++++++++++++++++++++----- examples/3d/atmospheric_fog.rs | 9 +- examples/3d/fxaa.rs | 5 + examples/3d/lighting.rs | 7 +- examples/3d/load_gltf.rs | 8 +- examples/3d/shadow_caster_receiver.rs | 5 +- examples/3d/split_screen.rs | 7 +- examples/animation/animated_fox.rs | 4 + examples/stress_tests/many_foxes.rs | 5 + 9 files changed, 147 insertions(+), 35 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index d1d9f134814a8..5c8e5992aa9fe 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -234,6 +234,7 @@ impl Default for DirectionalLightShadowMap { } /// Controls how cascaded shadow mapping works. +/// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance. #[derive(Component, Clone, Debug, Reflect)] #[reflect(Component)] pub struct CascadeShadowConfig { @@ -241,16 +242,13 @@ pub struct CascadeShadowConfig { pub bounds: Vec, /// The proportion of overlap each cascade has with the previous cascade. pub overlap_proportion: f32, + /// The (positive) distance to the near boundary of the first cascade. + pub minimum_distance: f32, } impl Default for CascadeShadowConfig { fn default() -> Self { - if cfg!(feature = "webgl") { - // Currently only support one cascade in webgl. - Self::new(1, 5.0, 100.0, 0.2) - } else { - Self::new(4, 5.0, 1000.0, 0.2) - } + CascadeShadowConfigBuilder::new().build() } } @@ -268,31 +266,117 @@ fn calculate_cascade_bounds( .collect() } -impl CascadeShadowConfig { - /// Returns a cascade config for `num_cascades` cascades, with the first cascade - /// having far bound `nearest_bound` and the last cascade having far bound `shadow_maximum_distance`. - /// In-between cascades will be exponentially spaced. - pub fn new( - num_cascades: usize, - nearest_bound: f32, - shadow_maximum_distance: f32, - overlap_proportion: f32, - ) -> Self { +/// Builder for [`CascadeShadowConfig`]. +pub struct CascadeShadowConfigBuilder { + num_cascades: usize, + minimum_distance: f32, + maximum_distance: f32, + first_cascade_far_bound: f32, + overlap_proportion: f32, +} + +impl CascadeShadowConfigBuilder { + /// Constructs a new builder. + pub fn new() -> Self { + if cfg!(feature = "webgl") { + // Currently only support one cascade in webgl. + Self { + num_cascades: 1, + minimum_distance: 0.1, + maximum_distance: 100.0, + first_cascade_far_bound: 5.0, + overlap_proportion: 0.2, + } + } else { + Self { + num_cascades: 4, + minimum_distance: 0.1, + maximum_distance: 1000.0, + first_cascade_far_bound: 5.0, + overlap_proportion: 0.2, + } + } + } + + /// Sets the number of shadow cascades. + /// More cascades increases shadow quality by mitigating perspective aliasing - a phenomenom where areas + /// nearer the camera are covered by fewer shadow map texels than areas further from the camera, causing + /// blocky looking shadows. + /// + /// This does come at the cost increased rendering overhead, however this overhead is still less + /// than if you were to use fewer cascades and much larger shadow map textures to achieve the + /// same quality leveland much larger shadow map textures to achieve the same quality level + /// + /// In cases rendered geometry covers a relatively narrow and static depth relative to camera, it may + /// make more sense to use fewer cascades and a higher resolution shadow map texture as perspective aliasing + /// is not as much an issue. Be sure to adjust `minimum_distance` and `maximum_distance` appropriately. + pub fn num_cascades(mut self, n: usize) -> Self { + self.num_cascades = n; + self + } + + /// Sets the minimum shadow distance. + /// Areas nearer to the camera than this will likely receive no shadows. + pub fn minimum_distance(mut self, d: f32) -> Self { + self.minimum_distance = d; + self + } + + /// Sets the maximum shadow distance. + /// Areas further from the camera than this will likely receive no shadows. + pub fn maximum_distance(mut self, d: f32) -> Self { + self.maximum_distance = d; + self + } + + /// Sets the far bound of the first cascade. + /// In-between cascades will be exponentially spaced relative to the maximum shadow distance. + /// NOTE: This is ignored if there is only one cascade, the maximum distance takes precedence. + pub fn first_cascade_far_bound(mut self, bound: f32) -> Self { + self.first_cascade_far_bound = bound; + self + } + + /// Returns the cascade config as specified by this builder. + pub fn build(&self) -> CascadeShadowConfig { assert!( - num_cascades > 0, - "num_cascades must be positive, but was {num_cascades}", + self.num_cascades > 0, + "num_cascades must be positive, but was {}", + self.num_cascades ); assert!( - (0.0..1.0).contains(&overlap_proportion), - "overlap_proportion must be in [0.0, 1.0) but was {overlap_proportion}", + self.minimum_distance >= 0.0, + "minimum_distance must be non-negative, but was {}", + self.minimum_distance ); - Self { - bounds: calculate_cascade_bounds(num_cascades, nearest_bound, shadow_maximum_distance), - overlap_proportion, + assert!( + self.maximum_distance >= 0.0, + "maximum_distance must be non-negative, but was {}", + self.maximum_distance + ); + assert!( + (0.0..1.0).contains(&self.overlap_proportion), + "overlap_proportion must be in [0.0, 1.0) but was {}", + self.overlap_proportion + ); + CascadeShadowConfig { + bounds: calculate_cascade_bounds( + self.num_cascades, + self.first_cascade_far_bound, + self.maximum_distance, + ), + overlap_proportion: self.overlap_proportion, + minimum_distance: self.minimum_distance, } } } +impl Default for CascadeShadowConfigBuilder { + fn default() -> Self { + Self::new() + } +} + #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct Cascades { @@ -375,7 +459,7 @@ pub fn update_directional_light_cascades( (1.0 - cascades_config.overlap_proportion) * -cascades_config.bounds[idx - 1] } else { - 0.0 + -cascades_config.minimum_distance }, -far_bound, ) diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index d1024e83724c3..b33d665f511f5 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -8,7 +8,7 @@ //! | `S` | Toggle Directional Light Fog Influence | use bevy::{ - pbr::{CascadeShadowConfig, NotShadowCaster}, + pbr::{CascadeShadowConfigBuilder, NotShadowCaster}, prelude::*, }; @@ -49,9 +49,10 @@ fn setup_terrain_scene( asset_server: Res, ) { // Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km) - // For WebGL we only support 1 cascade level for now - let cascade_shadow_config = - CascadeShadowConfig::new(if cfg!(feature = "webgl") { 1 } else { 4 }, 0.5, 2.5, 0.2); + let cascade_shadow_config = CascadeShadowConfigBuilder::new() + .first_cascade_far_bound(0.5) + .maximum_distance(2.0) + .build(); // Sun commands.spawn(DirectionalLightBundle { diff --git a/examples/3d/fxaa.rs b/examples/3d/fxaa.rs index cc09d08ab41ef..47cf6bbc07e57 100644 --- a/examples/3d/fxaa.rs +++ b/examples/3d/fxaa.rs @@ -4,6 +4,7 @@ use std::f32::consts::PI; use bevy::{ core_pipeline::fxaa::{Fxaa, Sensitivity}, + pbr::CascadeShadowConfigBuilder, prelude::*, render::{ render_resource::{Extent3d, SamplerDescriptor, TextureDimension, TextureFormat}, @@ -81,6 +82,10 @@ fn setup( PI * -0.15, PI * -0.15, )), + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .maximum_distance(3.0) + .first_cascade_far_bound(0.7) + .build(), ..default() }); diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index eef200b79cb36..744a04ef0635c 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -3,7 +3,7 @@ use std::f32::consts::PI; -use bevy::{pbr::CascadeShadowConfig, prelude::*}; +use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; fn main() { App::new() @@ -199,7 +199,10 @@ fn setup( // The default cascade config is designed to handle large scenes. // As this example has a much smaller world, we can tighten the shadow // far bound for better visual quality. - cascade_shadow_config: CascadeShadowConfig::new(4, 5.0, 30.0, 0.2), + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .first_cascade_far_bound(5.0) + .maximum_distance(30.0) + .build(), ..default() }); diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index 0e59304e173d6..5f9372cf63f72 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -2,7 +2,7 @@ use std::f32::consts::*; -use bevy::{pbr::CascadeShadowConfig, prelude::*}; +use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; fn main() { App::new() @@ -28,7 +28,11 @@ fn setup(mut commands: Commands, asset_server: Res) { }, // This is a relatively small scene, so use tighter shadow // cascade bounds than the default for better quality. - cascade_shadow_config: CascadeShadowConfig::new(1, 1.1, 1.5, 0.3), + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .num_cascades(1) + .minimum_distance(0.5) + .maximum_distance(1.0) + .build(), ..default() }); commands.spawn(SceneBundle { diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index 3488ed48b383e..eb5be600c4173 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -3,7 +3,7 @@ use std::f32::consts::PI; use bevy::{ - pbr::{NotShadowCaster, NotShadowReceiver}, + pbr::{CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver}, prelude::*, }; @@ -109,6 +109,9 @@ fn setup( PI / 2., -PI / 4., )), + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .maximum_distance(30.0) + .build(), ..default() }); diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 2ff5b299e7cc7..9332d9d91d524 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -3,8 +3,8 @@ use std::f32::consts::PI; use bevy::{ - core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport, - window::WindowResized, + core_pipeline::clear_color::ClearColorConfig, pbr::CascadeShadowConfigBuilder, prelude::*, + render::camera::Viewport, window::WindowResized, }; fn main() { @@ -41,6 +41,9 @@ fn setup( shadows_enabled: true, ..default() }, + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .maximum_distance(300.0) + .build(), ..default() }); diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 1f1232d79d03a..25ed7c54a26d2 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -3,6 +3,7 @@ use std::f32::consts::PI; use std::time::Duration; +use bevy::pbr::CascadeShadowConfigBuilder; use bevy::prelude::*; fn main() { @@ -55,6 +56,9 @@ fn setup( shadows_enabled: true, ..default() }, + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .maximum_distance(400.0) + .build(), ..default() }); diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index f4d2f631e2170..cb35365952154 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -6,6 +6,7 @@ use std::time::Duration; use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + pbr::CascadeShadowConfigBuilder, prelude::*, window::{PresentMode, WindowPlugin}, }; @@ -172,6 +173,10 @@ fn setup( shadows_enabled: true, ..default() }, + cascade_shadow_config: CascadeShadowConfigBuilder::new() + .first_cascade_far_bound(1.2 * radius) + .maximum_distance(2.0 * radius) + .build(), ..default() }); From fca88a4f58075883cd9dc4da1329a9aa8993659d Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Wed, 1 Feb 2023 08:59:12 -0800 Subject: [PATCH 02/14] add overlap_proportion setter --- crates/bevy_pbr/src/light.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 5c8e5992aa9fe..fc2c410034b76 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -337,6 +337,14 @@ impl CascadeShadowConfigBuilder { self } + /// Sets the overlap proportion between cascades. + /// The overlap is used to make the transition from one cascade's shadow map to the next + /// less abrupt by blending between both shadow maps. + pub fn overlap_proportion(mut self, p: f32) -> Self { + self.overlap_proportion = p; + self + } + /// Returns the cascade config as specified by this builder. pub fn build(&self) -> CascadeShadowConfig { assert!( From ba93750e86ad57fa694683a9fa80a30b76471995 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Wed, 1 Feb 2023 09:13:12 -0800 Subject: [PATCH 03/14] Add validation to minimum_distance --- crates/bevy_pbr/src/light.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index fc2c410034b76..60da3a4bc7e58 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -353,8 +353,8 @@ impl CascadeShadowConfigBuilder { self.num_cascades ); assert!( - self.minimum_distance >= 0.0, - "minimum_distance must be non-negative, but was {}", + (0.0..self.first_cascade_far_bound).contains(&self.minimum_distance), + "minimum_distance must be in [0.0, first_cascade_far_bound), but was {}", self.minimum_distance ); assert!( From c316d5ae4d45314a51d497833b61ed59014af6a8 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Wed, 1 Feb 2023 10:43:46 -0800 Subject: [PATCH 04/14] Apply suggestions from code review Co-authored-by: Robert Swain --- crates/bevy_pbr/src/light.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 60da3a4bc7e58..cdaf6fb179dcc 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -305,9 +305,9 @@ impl CascadeShadowConfigBuilder { /// /// This does come at the cost increased rendering overhead, however this overhead is still less /// than if you were to use fewer cascades and much larger shadow map textures to achieve the - /// same quality leveland much larger shadow map textures to achieve the same quality level + /// same quality level. /// - /// In cases rendered geometry covers a relatively narrow and static depth relative to camera, it may + /// In case rendered geometry covers a relatively narrow and static depth relative to camera, it may /// make more sense to use fewer cascades and a higher resolution shadow map texture as perspective aliasing /// is not as much an issue. Be sure to adjust `minimum_distance` and `maximum_distance` appropriately. pub fn num_cascades(mut self, n: usize) -> Self { From 6ce7a8e3f9bf4296f5a87ad825e2b002409f9cd7 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Wed, 1 Feb 2023 20:09:23 +0100 Subject: [PATCH 05/14] bevy_pbr: Add cascade debug visualization --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 5 ++++- crates/bevy_pbr/src/render/shadows.wgsl | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 57b5162028b47..c33cea15be2fa 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -226,7 +226,10 @@ fn pbr( && (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = fetch_directional_shadow(i, in.world_position, in.world_normal, view_z); } - let light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + var light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); +#ifdef DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES + light_contrib = cascade_debug_visualization(light_contrib, i, view_z); +#endif light_accum = light_accum + light_contrib * shadow; } diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index a3f1f79a97f2d..f74733c90051e 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -144,7 +144,7 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4, s directional_shadow_textures_sampler, light_local, depth - ); + ); #else return textureSampleCompareLevel( directional_shadow_textures, @@ -152,14 +152,14 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4, s light_local, i32((*light).depth_texture_base_index + cascade_index), depth - ); + ); #endif } fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3, view_z: f32) -> f32 { let light = &lights.directional_lights[light_id]; let cascade_index = get_cascade_index(light_id, view_z); - + if (cascade_index >= (*light).num_cascades) { return 1.0; } @@ -178,3 +178,16 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_nor } return shadow; } + +fn cascade_debug_visualization( + output_color: vec3, + light_id: u32, + view_z: f32, +) -> vec3 { + let overlay_alpha = 0.95; + let cascade_index = get_cascade_index(light_id, view_z); + let cascade_color = hsv2rgb(f32(cascade_index) / f32(#{MAX_CASCADES_PER_LIGHT}u + 1u), 1.0, 0.5); + return vec3( + (1.0 - overlay_alpha) * output_color.rgb + overlay_alpha * cascade_color + ); +} From bc36c745628be6d8cdc6ec1a54887228de746b4f Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 00:07:49 -0800 Subject: [PATCH 06/14] tweak bounds --- examples/3d/atmospheric_fog.rs | 4 ++-- examples/3d/fxaa.rs | 2 +- examples/3d/lighting.rs | 6 +++--- examples/3d/load_gltf.rs | 2 +- examples/3d/shadow_caster_receiver.rs | 3 ++- examples/3d/split_screen.rs | 4 +++- examples/animation/animated_fox.rs | 1 + examples/stress_tests/many_foxes.rs | 4 ++-- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index b33d665f511f5..9d5d53e5c3d3e 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -50,8 +50,8 @@ fn setup_terrain_scene( ) { // Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km) let cascade_shadow_config = CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(0.5) - .maximum_distance(2.0) + .first_cascade_far_bound(0.3) + .maximum_distance(3.0) .build(); // Sun diff --git a/examples/3d/fxaa.rs b/examples/3d/fxaa.rs index 47cf6bbc07e57..3634167257c67 100644 --- a/examples/3d/fxaa.rs +++ b/examples/3d/fxaa.rs @@ -84,7 +84,7 @@ fn setup( )), cascade_shadow_config: CascadeShadowConfigBuilder::new() .maximum_distance(3.0) - .first_cascade_far_bound(0.7) + .first_cascade_far_bound(0.9) .build(), ..default() }); diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index 744a04ef0635c..4941c88b127a9 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -198,10 +198,10 @@ fn setup( }, // The default cascade config is designed to handle large scenes. // As this example has a much smaller world, we can tighten the shadow - // far bound for better visual quality. + // bounds for better visual quality. cascade_shadow_config: CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(5.0) - .maximum_distance(30.0) + .first_cascade_far_bound(4.0) + .maximum_distance(10.0) .build(), ..default() }); diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index 5f9372cf63f72..16f2c71d1358c 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -31,7 +31,7 @@ fn setup(mut commands: Commands, asset_server: Res) { cascade_shadow_config: CascadeShadowConfigBuilder::new() .num_cascades(1) .minimum_distance(0.5) - .maximum_distance(1.0) + .maximum_distance(0.6) .build(), ..default() }); diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index eb5be600c4173..748117731bb31 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -110,7 +110,8 @@ fn setup( -PI / 4., )), cascade_shadow_config: CascadeShadowConfigBuilder::new() - .maximum_distance(30.0) + .first_cascade_far_bound(7.0) + .maximum_distance(25.0) .build(), ..default() }); diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 9332d9d91d524..30dbf15102ec6 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -42,7 +42,9 @@ fn setup( ..default() }, cascade_shadow_config: CascadeShadowConfigBuilder::new() - .maximum_distance(300.0) + .num_cascades(2) + .first_cascade_far_bound(200.0) + .maximum_distance(280.0) .build(), ..default() }); diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 25ed7c54a26d2..88afc5dba14ec 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -57,6 +57,7 @@ fn setup( ..default() }, cascade_shadow_config: CascadeShadowConfigBuilder::new() + .first_cascade_far_bound(200.0) .maximum_distance(400.0) .build(), ..default() diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index cb35365952154..0c5a35b02d4c0 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -174,8 +174,8 @@ fn setup( ..default() }, cascade_shadow_config: CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(1.2 * radius) - .maximum_distance(2.0 * radius) + .first_cascade_far_bound(0.9 * radius) + .maximum_distance(2.8 * radius) .build(), ..default() }); From 5dedc2b8578c5066006cad10cfb29bfa449a1867 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 00:10:12 -0800 Subject: [PATCH 07/14] builder text --- crates/bevy_pbr/src/light.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index cdaf6fb179dcc..ca5a59043ad1a 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -317,6 +317,8 @@ impl CascadeShadowConfigBuilder { /// Sets the minimum shadow distance. /// Areas nearer to the camera than this will likely receive no shadows. + /// NOTE: Due to implementation details, this usually does not impact shadow quality as much as + /// [`first_cascade_far_bound`] and [`maximum_distance`]. pub fn minimum_distance(mut self, d: f32) -> Self { self.minimum_distance = d; self @@ -329,7 +331,7 @@ impl CascadeShadowConfigBuilder { self } - /// Sets the far bound of the first cascade. + /// Sets the far bound of the first cascade, relative to the view origin. /// In-between cascades will be exponentially spaced relative to the maximum shadow distance. /// NOTE: This is ignored if there is only one cascade, the maximum distance takes precedence. pub fn first_cascade_far_bound(mut self, bound: f32) -> Self { From c7ea0d2b7336fd9946768970db88100af12a97b8 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 01:51:19 -0800 Subject: [PATCH 08/14] fix doc --- crates/bevy_pbr/src/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index ca5a59043ad1a..be196abb550ce 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -318,7 +318,7 @@ impl CascadeShadowConfigBuilder { /// Sets the minimum shadow distance. /// Areas nearer to the camera than this will likely receive no shadows. /// NOTE: Due to implementation details, this usually does not impact shadow quality as much as - /// [`first_cascade_far_bound`] and [`maximum_distance`]. + /// `first_cascade_far_bound` and `maximum_distance`. pub fn minimum_distance(mut self, d: f32) -> Self { self.minimum_distance = d; self From db389ef1535785908c15777329d3628969ab438b Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 11:01:31 -0800 Subject: [PATCH 09/14] adjust webgl shadowmap default --- crates/bevy_pbr/src/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index be196abb550ce..3224ddd072790 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -227,7 +227,7 @@ pub struct DirectionalLightShadowMap { impl Default for DirectionalLightShadowMap { fn default() -> Self { #[cfg(feature = "webgl")] - return Self { size: 1024 }; + return Self { size: 2048 }; #[cfg(not(feature = "webgl"))] return Self { size: 2048 }; } From 9e5d7aa04f96b821eaa2a86045fa54d8ad63622c Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 11:06:21 -0800 Subject: [PATCH 10/14] tweak load_gltf --- examples/3d/load_gltf.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index 16f2c71d1358c..ee2eb378efbd9 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -2,7 +2,7 @@ use std::f32::consts::*; -use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; +use bevy::{pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, prelude::*}; fn main() { App::new() @@ -10,6 +10,7 @@ fn main() { color: Color::WHITE, brightness: 1.0 / 5.0f32, }) + .insert_resource(DirectionalLightShadowMap { size: 4096 }) .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(animate_light_direction) @@ -28,10 +29,11 @@ fn setup(mut commands: Commands, asset_server: Res) { }, // This is a relatively small scene, so use tighter shadow // cascade bounds than the default for better quality. + // We also adjusted the shadow map to be larger since we're + // only using a single cascade. cascade_shadow_config: CascadeShadowConfigBuilder::new() .num_cascades(1) - .minimum_distance(0.5) - .maximum_distance(0.6) + .maximum_distance(1.6) .build(), ..default() }); From bfd6c966ccafd5b3ceef77cdf87ec407120bc599 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 11:12:45 -0800 Subject: [PATCH 11/14] more comments --- crates/bevy_pbr/src/light.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 3224ddd072790..b576ca448455c 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -315,10 +315,14 @@ impl CascadeShadowConfigBuilder { self } - /// Sets the minimum shadow distance. + /// Sets the minimum shadow distance, which can help improve the texel resolution of the first cascade. /// Areas nearer to the camera than this will likely receive no shadows. + /// /// NOTE: Due to implementation details, this usually does not impact shadow quality as much as - /// `first_cascade_far_bound` and `maximum_distance`. + /// `first_cascade_far_bound` and `maximum_distance`. At many view frustum field-of-views, the + /// texel resolution of the first cascade is dominated by the width / height of the view frustum plane + /// at `first_cascade_far_bound` rather than the depth of the frustum from `minimum_distance` to + /// `first_cascade_far_bound`. pub fn minimum_distance(mut self, d: f32) -> Self { self.minimum_distance = d; self From 5219b6ddf1c3d4b329fd8aee656338e869cbd382 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 11:17:29 -0800 Subject: [PATCH 12/14] cargo fmt --- crates/bevy_pbr/src/light.rs | 2 +- examples/3d/load_gltf.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index b576ca448455c..df25e96652522 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -317,7 +317,7 @@ impl CascadeShadowConfigBuilder { /// Sets the minimum shadow distance, which can help improve the texel resolution of the first cascade. /// Areas nearer to the camera than this will likely receive no shadows. - /// + /// /// NOTE: Due to implementation details, this usually does not impact shadow quality as much as /// `first_cascade_far_bound` and `maximum_distance`. At many view frustum field-of-views, the /// texel resolution of the first cascade is dominated by the width / height of the view frustum plane diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index ee2eb378efbd9..9790549a501dd 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -2,7 +2,10 @@ use std::f32::consts::*; -use bevy::{pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, prelude::*}; +use bevy::{ + pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, + prelude::*, +}; fn main() { App::new() From e4d77f685d8f8076a981110e6eb75ba147f8b120 Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 23:22:59 -0800 Subject: [PATCH 13/14] remove builder-style pattern, tighten asserts --- crates/bevy_pbr/src/light.rs | 121 ++++++++++++-------------- examples/3d/atmospheric_fog.rs | 10 ++- examples/3d/fxaa.rs | 10 ++- examples/3d/lighting.rs | 10 ++- examples/3d/load_gltf.rs | 10 ++- examples/3d/shadow_caster_receiver.rs | 10 ++- examples/3d/split_screen.rs | 12 +-- examples/animation/animated_fox.rs | 10 ++- examples/stress_tests/many_foxes.rs | 10 ++- 9 files changed, 104 insertions(+), 99 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index df25e96652522..638ce09e2d94e 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -226,15 +226,23 @@ pub struct DirectionalLightShadowMap { impl Default for DirectionalLightShadowMap { fn default() -> Self { - #[cfg(feature = "webgl")] - return Self { size: 2048 }; - #[cfg(not(feature = "webgl"))] return Self { size: 2048 }; } } /// Controls how cascaded shadow mapping works. /// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance. +/// +/// ``` +/// # use bevy_pbr::CascadeShadowConfig; +/// # use bevy_pbr::CascadeShadowConfigBuilder; +/// # use bevy_utils::default; +/// # +/// let config: CascadeShadowConfig = CascadeShadowConfigBuilder { +/// maximum_distance: 100.0, +/// ..default() +/// }.into(); +/// ``` #[derive(Component, Clone, Debug, Reflect)] #[reflect(Component)] pub struct CascadeShadowConfig { @@ -248,7 +256,7 @@ pub struct CascadeShadowConfig { impl Default for CascadeShadowConfig { fn default() -> Self { - CascadeShadowConfigBuilder::new().build() + CascadeShadowConfigBuilder::default().into() } } @@ -268,37 +276,7 @@ fn calculate_cascade_bounds( /// Builder for [`CascadeShadowConfig`]. pub struct CascadeShadowConfigBuilder { - num_cascades: usize, - minimum_distance: f32, - maximum_distance: f32, - first_cascade_far_bound: f32, - overlap_proportion: f32, -} - -impl CascadeShadowConfigBuilder { - /// Constructs a new builder. - pub fn new() -> Self { - if cfg!(feature = "webgl") { - // Currently only support one cascade in webgl. - Self { - num_cascades: 1, - minimum_distance: 0.1, - maximum_distance: 100.0, - first_cascade_far_bound: 5.0, - overlap_proportion: 0.2, - } - } else { - Self { - num_cascades: 4, - minimum_distance: 0.1, - maximum_distance: 1000.0, - first_cascade_far_bound: 5.0, - overlap_proportion: 0.2, - } - } - } - - /// Sets the number of shadow cascades. + /// The number of shadow cascades. /// More cascades increases shadow quality by mitigating perspective aliasing - a phenomenom where areas /// nearer the camera are covered by fewer shadow map texels than areas further from the camera, causing /// blocky looking shadows. @@ -310,12 +288,8 @@ impl CascadeShadowConfigBuilder { /// In case rendered geometry covers a relatively narrow and static depth relative to camera, it may /// make more sense to use fewer cascades and a higher resolution shadow map texture as perspective aliasing /// is not as much an issue. Be sure to adjust `minimum_distance` and `maximum_distance` appropriately. - pub fn num_cascades(mut self, n: usize) -> Self { - self.num_cascades = n; - self - } - - /// Sets the minimum shadow distance, which can help improve the texel resolution of the first cascade. + pub num_cascades: usize, + /// The minimum shadow distance, which can help improve the texel resolution of the first cascade. /// Areas nearer to the camera than this will likely receive no shadows. /// /// NOTE: Due to implementation details, this usually does not impact shadow quality as much as @@ -323,34 +297,21 @@ impl CascadeShadowConfigBuilder { /// texel resolution of the first cascade is dominated by the width / height of the view frustum plane /// at `first_cascade_far_bound` rather than the depth of the frustum from `minimum_distance` to /// `first_cascade_far_bound`. - pub fn minimum_distance(mut self, d: f32) -> Self { - self.minimum_distance = d; - self - } - - /// Sets the maximum shadow distance. + pub minimum_distance: f32, + /// The maximum shadow distance. /// Areas further from the camera than this will likely receive no shadows. - pub fn maximum_distance(mut self, d: f32) -> Self { - self.maximum_distance = d; - self - } - + pub maximum_distance: f32, /// Sets the far bound of the first cascade, relative to the view origin. /// In-between cascades will be exponentially spaced relative to the maximum shadow distance. /// NOTE: This is ignored if there is only one cascade, the maximum distance takes precedence. - pub fn first_cascade_far_bound(mut self, bound: f32) -> Self { - self.first_cascade_far_bound = bound; - self - } - + pub first_cascade_far_bound: f32, /// Sets the overlap proportion between cascades. /// The overlap is used to make the transition from one cascade's shadow map to the next /// less abrupt by blending between both shadow maps. - pub fn overlap_proportion(mut self, p: f32) -> Self { - self.overlap_proportion = p; - self - } + pub overlap_proportion: f32, +} +impl CascadeShadowConfigBuilder { /// Returns the cascade config as specified by this builder. pub fn build(&self) -> CascadeShadowConfig { assert!( @@ -359,13 +320,18 @@ impl CascadeShadowConfigBuilder { self.num_cascades ); assert!( - (0.0..self.first_cascade_far_bound).contains(&self.minimum_distance), - "minimum_distance must be in [0.0, first_cascade_far_bound), but was {}", + self.minimum_distance >= 0.0, + "maximum_distance must be non-negative, but was {}", self.minimum_distance ); assert!( - self.maximum_distance >= 0.0, - "maximum_distance must be non-negative, but was {}", + self.num_cascades == 1 || self.minimum_distance < self.first_cascade_far_bound, + "minimum_distance must be less than first_cascade_far_bound, but was {}", + self.minimum_distance + ); + assert!( + self.maximum_distance > self.minimum_distance, + "maximum_distance must be greater than minimum_distance, but was {}", self.maximum_distance ); assert!( @@ -387,7 +353,30 @@ impl CascadeShadowConfigBuilder { impl Default for CascadeShadowConfigBuilder { fn default() -> Self { - Self::new() + if cfg!(feature = "webgl") { + // Currently only support one cascade in webgl. + Self { + num_cascades: 1, + minimum_distance: 0.1, + maximum_distance: 100.0, + first_cascade_far_bound: 5.0, + overlap_proportion: 0.2, + } + } else { + Self { + num_cascades: 4, + minimum_distance: 0.1, + maximum_distance: 1000.0, + first_cascade_far_bound: 5.0, + overlap_proportion: 0.2, + } + } + } +} + +impl From for CascadeShadowConfig { + fn from(builder: CascadeShadowConfigBuilder) -> Self { + builder.build() } } diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index 9d5d53e5c3d3e..5766bc7201e3b 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -49,10 +49,12 @@ fn setup_terrain_scene( asset_server: Res, ) { // Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km) - let cascade_shadow_config = CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(0.3) - .maximum_distance(3.0) - .build(); + let cascade_shadow_config = CascadeShadowConfigBuilder { + first_cascade_far_bound: 0.3, + maximum_distance: 3.0, + ..default() + } + .build(); // Sun commands.spawn(DirectionalLightBundle { diff --git a/examples/3d/fxaa.rs b/examples/3d/fxaa.rs index 3634167257c67..279f4c18a583b 100644 --- a/examples/3d/fxaa.rs +++ b/examples/3d/fxaa.rs @@ -82,10 +82,12 @@ fn setup( PI * -0.15, PI * -0.15, )), - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .maximum_distance(3.0) - .first_cascade_far_bound(0.9) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + maximum_distance: 3.0, + first_cascade_far_bound: 0.9, + ..default() + } + .into(), ..default() }); diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index 4941c88b127a9..ef382ddb32b58 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -199,10 +199,12 @@ fn setup( // The default cascade config is designed to handle large scenes. // As this example has a much smaller world, we can tighten the shadow // bounds for better visual quality. - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(4.0) - .maximum_distance(10.0) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + first_cascade_far_bound: 4.0, + maximum_distance: 10.0, + ..default() + } + .into(), ..default() }); diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index 9790549a501dd..3123599d9cb3c 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -34,10 +34,12 @@ fn setup(mut commands: Commands, asset_server: Res) { // cascade bounds than the default for better quality. // We also adjusted the shadow map to be larger since we're // only using a single cascade. - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .num_cascades(1) - .maximum_distance(1.6) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + num_cascades: 1, + maximum_distance: 1.6, + ..default() + } + .into(), ..default() }); commands.spawn(SceneBundle { diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index 748117731bb31..0216acb01638a 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -109,10 +109,12 @@ fn setup( PI / 2., -PI / 4., )), - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(7.0) - .maximum_distance(25.0) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + first_cascade_far_bound: 7.0, + maximum_distance: 25.0, + ..default() + } + .into(), ..default() }); diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 30dbf15102ec6..9a32318ada676 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -41,11 +41,13 @@ fn setup( shadows_enabled: true, ..default() }, - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .num_cascades(2) - .first_cascade_far_bound(200.0) - .maximum_distance(280.0) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + num_cascades: 2, + first_cascade_far_bound: 200.0, + maximum_distance: 280.0, + ..default() + } + .into(), ..default() }); diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 88afc5dba14ec..7b564c12e06da 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -56,10 +56,12 @@ fn setup( shadows_enabled: true, ..default() }, - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(200.0) - .maximum_distance(400.0) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + first_cascade_far_bound: 200.0, + maximum_distance: 400.0, + ..default() + } + .into(), ..default() }); diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 0c5a35b02d4c0..bf58aa522de8c 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -173,10 +173,12 @@ fn setup( shadows_enabled: true, ..default() }, - cascade_shadow_config: CascadeShadowConfigBuilder::new() - .first_cascade_far_bound(0.9 * radius) - .maximum_distance(2.8 * radius) - .build(), + cascade_shadow_config: CascadeShadowConfigBuilder { + first_cascade_far_bound: 0.9 * radius, + maximum_distance: 2.8 * radius, + ..default() + } + .into(), ..default() }); From 5433afcaff718c748ee46498cdfd9b3bc5082b7f Mon Sep 17 00:00:00 2001 From: Daniel Chia Date: Sat, 4 Feb 2023 23:34:56 -0800 Subject: [PATCH 14/14] clippy --- crates/bevy_pbr/src/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 638ce09e2d94e..c71445b0ee9e4 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -226,7 +226,7 @@ pub struct DirectionalLightShadowMap { impl Default for DirectionalLightShadowMap { fn default() -> Self { - return Self { size: 2048 }; + Self { size: 2048 } } }