Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Better cascades config defaults + builder, tweak example configs #7456

Closed
wants to merge 15 commits into from
148 changes: 123 additions & 25 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,30 +227,28 @@ 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 };
danchia marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Controls how cascaded shadow mapping works.
/// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance.
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct CascadeShadowConfig {
/// The (positive) distance to the far boundary of each cascade.
pub bounds: Vec<f32>,
/// 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()
}
}

Expand All @@ -268,31 +266,131 @@ 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,
danchia marked this conversation as resolved.
Show resolved Hide resolved
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think webgl and non-webgl should have the same default settings (apart from num cascades). then things will work the same (but look worse) on webgl by default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... not sure I agree on that. Having a 1km (assuming world units are metres) single-cascade will look horribly bad. I think it's better to reduce the shadow max distance instead and just not have shadows further away. If people want to, they can change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was my intention behind the differing defaults - I felt with only 1 cascade it would be a better tradeoff to just have a shorter shadow distance.

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 level.
///
/// 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.
/// 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
/// 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.
/// 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, 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
}

/// 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!(
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}",
(0.0..self.first_cascade_far_bound).contains(&self.minimum_distance),
danchia marked this conversation as resolved.
Show resolved Hide resolved
"minimum_distance must be in [0.0, first_cascade_far_bound), 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
);
danchia marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -375,7 +473,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,
)
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_pbr/src/render/pbr_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
19 changes: 16 additions & 3 deletions crates/bevy_pbr/src/render/shadows.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,22 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, s
directional_shadow_textures_sampler,
light_local,
depth
);
);
#else
return textureSampleCompareLevel(
directional_shadow_textures,
directional_shadow_textures_sampler,
light_local,
i32((*light).depth_texture_base_index + cascade_index),
depth
);
);
#endif
}

fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>, 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;
}
Expand All @@ -178,3 +178,16 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
}
return shadow;
}

fn cascade_debug_visualization(
output_color: vec3<f32>,
light_id: u32,
view_z: f32,
) -> vec3<f32> {
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<f32>(
(1.0 - overlay_alpha) * output_color.rgb + overlay_alpha * cascade_color
);
}
9 changes: 5 additions & 4 deletions examples/3d/atmospheric_fog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! | `S` | Toggle Directional Light Fog Influence |

use bevy::{
pbr::{CascadeShadowConfig, NotShadowCaster},
pbr::{CascadeShadowConfigBuilder, NotShadowCaster},
prelude::*,
};

Expand Down Expand Up @@ -49,9 +49,10 @@ fn setup_terrain_scene(
asset_server: Res<AssetServer>,
) {
// 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.3)
.maximum_distance(3.0)
.build();

// Sun
commands.spawn(DirectionalLightBundle {
Expand Down
5 changes: 5 additions & 0 deletions examples/3d/fxaa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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.9)
.build(),
..default()
});

Expand Down
9 changes: 6 additions & 3 deletions examples/3d/lighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::f32::consts::PI;

use bevy::{pbr::CascadeShadowConfig, prelude::*};
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};

fn main() {
App::new()
Expand Down Expand Up @@ -198,8 +198,11 @@ 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),
// bounds for better visual quality.
cascade_shadow_config: CascadeShadowConfigBuilder::new()
.first_cascade_far_bound(4.0)
.maximum_distance(10.0)
.build(),
..default()
});

Expand Down
13 changes: 11 additions & 2 deletions examples/3d/load_gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

use std::f32::consts::*;

use bevy::{pbr::CascadeShadowConfig, prelude::*};
use bevy::{
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
prelude::*,
};

fn main() {
App::new()
.insert_resource(AmbientLight {
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)
Expand All @@ -28,7 +32,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
// 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),
// 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(),
..default()
});
commands.spawn(SceneBundle {
Expand Down
6 changes: 5 additions & 1 deletion examples/3d/shadow_caster_receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::f32::consts::PI;

use bevy::{
pbr::{NotShadowCaster, NotShadowReceiver},
pbr::{CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver},
prelude::*,
};

Expand Down Expand Up @@ -109,6 +109,10 @@ fn setup(
PI / 2.,
-PI / 4.,
)),
cascade_shadow_config: CascadeShadowConfigBuilder::new()
.first_cascade_far_bound(7.0)
.maximum_distance(25.0)
.build(),
..default()
});

Expand Down
9 changes: 7 additions & 2 deletions examples/3d/split_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -41,6 +41,11 @@ 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(),
..default()
});

Expand Down
Loading