Better cascades config defaults + builder, tweak example configs (#7456)
# Objective - Improve ergonomics / documentation of cascaded shadow maps - Allow for the customization of the nearest shadowing distance. - Fixes #7393 - Fixes #7362 ## Solution - Introduce `CascadeShadowConfigBuilder` - Tweak various example cascade settings for better quality. --- ## Changelog - Made examples look nicer under cascaded shadow maps. - Introduce `CascadeShadowConfigBuilder` to help with creating `CascadeShadowConfig` ## Migration Guide - Configure settings for cascaded shadow maps for directional lights using the newly introduced `CascadeShadowConfigBuilder`. Co-authored-by: Robert Swain <robert.swain@gmail.com>
This commit is contained in:
parent
5ee57ff4ba
commit
52f06175dd
@ -226,14 +226,23 @@ pub struct DirectionalLightShadowMap {
|
|||||||
|
|
||||||
impl Default for DirectionalLightShadowMap {
|
impl Default for DirectionalLightShadowMap {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#[cfg(feature = "webgl")]
|
Self { size: 2048 }
|
||||||
return Self { size: 1024 };
|
|
||||||
#[cfg(not(feature = "webgl"))]
|
|
||||||
return Self { size: 2048 };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls how cascaded shadow mapping works.
|
/// 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)]
|
#[derive(Component, Clone, Debug, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct CascadeShadowConfig {
|
pub struct CascadeShadowConfig {
|
||||||
@ -241,16 +250,13 @@ pub struct CascadeShadowConfig {
|
|||||||
pub bounds: Vec<f32>,
|
pub bounds: Vec<f32>,
|
||||||
/// The proportion of overlap each cascade has with the previous cascade.
|
/// The proportion of overlap each cascade has with the previous cascade.
|
||||||
pub overlap_proportion: f32,
|
pub overlap_proportion: f32,
|
||||||
|
/// The (positive) distance to the near boundary of the first cascade.
|
||||||
|
pub minimum_distance: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CascadeShadowConfig {
|
impl Default for CascadeShadowConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
if cfg!(feature = "webgl") {
|
CascadeShadowConfigBuilder::default().into()
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,28 +274,109 @@ fn calculate_cascade_bounds(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CascadeShadowConfig {
|
/// Builder for [`CascadeShadowConfig`].
|
||||||
/// Returns a cascade config for `num_cascades` cascades, with the first cascade
|
pub struct CascadeShadowConfigBuilder {
|
||||||
/// having far bound `nearest_bound` and the last cascade having far bound `shadow_maximum_distance`.
|
/// The number of shadow cascades.
|
||||||
/// In-between cascades will be exponentially spaced.
|
/// More cascades increases shadow quality by mitigating perspective aliasing - a phenomenom where areas
|
||||||
pub fn new(
|
/// nearer the camera are covered by fewer shadow map texels than areas further from the camera, causing
|
||||||
num_cascades: usize,
|
/// blocky looking shadows.
|
||||||
nearest_bound: f32,
|
///
|
||||||
shadow_maximum_distance: f32,
|
/// This does come at the cost increased rendering overhead, however this overhead is still less
|
||||||
overlap_proportion: f32,
|
/// than if you were to use fewer cascades and much larger shadow map textures to achieve the
|
||||||
) -> Self {
|
/// same quality level.
|
||||||
assert!(
|
///
|
||||||
num_cascades > 0,
|
/// In case rendered geometry covers a relatively narrow and static depth relative to camera, it may
|
||||||
"num_cascades must be positive, but was {num_cascades}",
|
/// 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.
|
||||||
assert!(
|
pub num_cascades: usize,
|
||||||
(0.0..1.0).contains(&overlap_proportion),
|
/// The minimum shadow distance, which can help improve the texel resolution of the first cascade.
|
||||||
"overlap_proportion must be in [0.0, 1.0) but was {overlap_proportion}",
|
/// Areas nearer to the camera than this will likely receive no shadows.
|
||||||
);
|
///
|
||||||
Self {
|
/// NOTE: Due to implementation details, this usually does not impact shadow quality as much as
|
||||||
bounds: calculate_cascade_bounds(num_cascades, nearest_bound, shadow_maximum_distance),
|
/// `first_cascade_far_bound` and `maximum_distance`. At many view frustum field-of-views, the
|
||||||
overlap_proportion,
|
/// 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 minimum_distance: f32,
|
||||||
|
/// The maximum shadow distance.
|
||||||
|
/// Areas further from the camera than this will likely receive no shadows.
|
||||||
|
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 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 overlap_proportion: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CascadeShadowConfigBuilder {
|
||||||
|
/// Returns the cascade config as specified by this builder.
|
||||||
|
pub fn build(&self) -> CascadeShadowConfig {
|
||||||
|
assert!(
|
||||||
|
self.num_cascades > 0,
|
||||||
|
"num_cascades must be positive, but was {}",
|
||||||
|
self.num_cascades
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
self.minimum_distance >= 0.0,
|
||||||
|
"maximum_distance must be non-negative, but was {}",
|
||||||
|
self.minimum_distance
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
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!(
|
||||||
|
(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 {
|
||||||
|
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<CascadeShadowConfigBuilder> for CascadeShadowConfig {
|
||||||
|
fn from(builder: CascadeShadowConfigBuilder) -> Self {
|
||||||
|
builder.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +462,7 @@ pub fn update_directional_light_cascades(
|
|||||||
(1.0 - cascades_config.overlap_proportion)
|
(1.0 - cascades_config.overlap_proportion)
|
||||||
* -cascades_config.bounds[idx - 1]
|
* -cascades_config.bounds[idx - 1]
|
||||||
} else {
|
} else {
|
||||||
0.0
|
-cascades_config.minimum_distance
|
||||||
},
|
},
|
||||||
-far_bound,
|
-far_bound,
|
||||||
)
|
)
|
||||||
|
@ -226,7 +226,10 @@ fn pbr(
|
|||||||
&& (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
&& (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);
|
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;
|
light_accum = light_accum + light_contrib * shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,3 +178,16 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
|
|||||||
}
|
}
|
||||||
return shadow;
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
//! | `S` | Toggle Directional Light Fog Influence |
|
//! | `S` | Toggle Directional Light Fog Influence |
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
pbr::{CascadeShadowConfig, NotShadowCaster},
|
pbr::{CascadeShadowConfigBuilder, NotShadowCaster},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,9 +49,12 @@ fn setup_terrain_scene(
|
|||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
// Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km)
|
// 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 = CascadeShadowConfigBuilder {
|
||||||
let cascade_shadow_config =
|
first_cascade_far_bound: 0.3,
|
||||||
CascadeShadowConfig::new(if cfg!(feature = "webgl") { 1 } else { 4 }, 0.5, 2.5, 0.2);
|
maximum_distance: 3.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.build();
|
||||||
|
|
||||||
// Sun
|
// Sun
|
||||||
commands.spawn(DirectionalLightBundle {
|
commands.spawn(DirectionalLightBundle {
|
||||||
|
@ -4,6 +4,7 @@ use std::f32::consts::PI;
|
|||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
core_pipeline::fxaa::{Fxaa, Sensitivity},
|
core_pipeline::fxaa::{Fxaa, Sensitivity},
|
||||||
|
pbr::CascadeShadowConfigBuilder,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::{
|
render::{
|
||||||
render_resource::{Extent3d, SamplerDescriptor, TextureDimension, TextureFormat},
|
render_resource::{Extent3d, SamplerDescriptor, TextureDimension, TextureFormat},
|
||||||
@ -81,6 +82,12 @@ fn setup(
|
|||||||
PI * -0.15,
|
PI * -0.15,
|
||||||
PI * -0.15,
|
PI * -0.15,
|
||||||
)),
|
)),
|
||||||
|
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||||
|
maximum_distance: 3.0,
|
||||||
|
first_cascade_far_bound: 0.9,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{pbr::CascadeShadowConfig, prelude::*};
|
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
@ -198,8 +198,13 @@ fn setup(
|
|||||||
},
|
},
|
||||||
// The default cascade config is designed to handle large scenes.
|
// The default cascade config is designed to handle large scenes.
|
||||||
// As this example has a much smaller world, we can tighten the shadow
|
// 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: CascadeShadowConfig::new(4, 5.0, 30.0, 0.2),
|
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 4.0,
|
||||||
|
maximum_distance: 10.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use std::f32::consts::*;
|
use std::f32::consts::*;
|
||||||
|
|
||||||
use bevy::{pbr::CascadeShadowConfig, prelude::*};
|
use bevy::{
|
||||||
|
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
@ -10,6 +13,7 @@ fn main() {
|
|||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
brightness: 1.0 / 5.0f32,
|
brightness: 1.0 / 5.0f32,
|
||||||
})
|
})
|
||||||
|
.insert_resource(DirectionalLightShadowMap { size: 4096 })
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
.add_system(animate_light_direction)
|
.add_system(animate_light_direction)
|
||||||
@ -28,7 +32,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
},
|
},
|
||||||
// This is a relatively small scene, so use tighter shadow
|
// This is a relatively small scene, so use tighter shadow
|
||||||
// cascade bounds than the default for better quality.
|
// 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 {
|
||||||
|
num_cascades: 1,
|
||||||
|
maximum_distance: 1.6,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(SceneBundle {
|
commands.spawn(SceneBundle {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
pbr::{NotShadowCaster, NotShadowReceiver},
|
pbr::{CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,6 +109,12 @@ fn setup(
|
|||||||
PI / 2.,
|
PI / 2.,
|
||||||
-PI / 4.,
|
-PI / 4.,
|
||||||
)),
|
)),
|
||||||
|
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 7.0,
|
||||||
|
maximum_distance: 25.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport,
|
core_pipeline::clear_color::ClearColorConfig, pbr::CascadeShadowConfigBuilder, prelude::*,
|
||||||
window::WindowResized,
|
render::camera::Viewport, window::WindowResized,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -41,6 +41,13 @@ fn setup(
|
|||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||||
|
num_cascades: 2,
|
||||||
|
first_cascade_far_bound: 200.0,
|
||||||
|
maximum_distance: 280.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::pbr::CascadeShadowConfigBuilder;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -55,6 +56,12 @@ fn setup(
|
|||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 200.0,
|
||||||
|
maximum_distance: 400.0,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
|
pbr::CascadeShadowConfigBuilder,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::{PresentMode, WindowPlugin},
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
@ -172,6 +173,12 @@ fn setup(
|
|||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||||
|
first_cascade_far_bound: 0.9 * radius,
|
||||||
|
maximum_distance: 2.8 * radius,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user