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 {
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "webgl")]
|
||||
return Self { size: 1024 };
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
return Self { size: 2048 };
|
||||
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 {
|
||||
@ -241,16 +250,13 @@ pub struct CascadeShadowConfig {
|
||||
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::default().into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,31 +274,112 @@ 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 {
|
||||
/// 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 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
|
||||
/// `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 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!(
|
||||
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,
|
||||
"maximum_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.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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct Cascades {
|
||||
@ -375,7 +462,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,
|
||||
)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ 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,
|
||||
@ -152,14 +152,14 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, s
|
||||
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;
|
||||
}
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
//! | `S` | Toggle Directional Light Fog Influence |
|
||||
|
||||
use bevy::{
|
||||
pbr::{CascadeShadowConfig, NotShadowCaster},
|
||||
pbr::{CascadeShadowConfigBuilder, NotShadowCaster},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
@ -49,9 +49,12 @@ 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 {
|
||||
first_cascade_far_bound: 0.3,
|
||||
maximum_distance: 3.0,
|
||||
..default()
|
||||
}
|
||||
.build();
|
||||
|
||||
// Sun
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
|
@ -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,12 @@ fn setup(
|
||||
PI * -0.15,
|
||||
PI * -0.15,
|
||||
)),
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
maximum_distance: 3.0,
|
||||
first_cascade_far_bound: 0.9,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{pbr::CascadeShadowConfig, prelude::*};
|
||||
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -198,8 +198,13 @@ 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 {
|
||||
first_cascade_far_bound: 4.0,
|
||||
maximum_distance: 10.0,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
use std::f32::consts::*;
|
||||
|
||||
use bevy::{pbr::CascadeShadowConfig, prelude::*};
|
||||
use bevy::{
|
||||
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -10,6 +13,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,7 +32,14 @@ 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 {
|
||||
num_cascades: 1,
|
||||
maximum_distance: 1.6,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(SceneBundle {
|
||||
|
@ -3,7 +3,7 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
pbr::{NotShadowCaster, NotShadowReceiver},
|
||||
pbr::{CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
@ -109,6 +109,12 @@ fn setup(
|
||||
PI / 2.,
|
||||
-PI / 4.,
|
||||
)),
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
first_cascade_far_bound: 7.0,
|
||||
maximum_distance: 25.0,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -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,13 @@ fn setup(
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
num_cascades: 2,
|
||||
first_cascade_far_bound: 200.0,
|
||||
maximum_distance: 280.0,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -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,12 @@ fn setup(
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
first_cascade_far_bound: 200.0,
|
||||
maximum_distance: 400.0,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ use std::time::Duration;
|
||||
|
||||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
pbr::CascadeShadowConfigBuilder,
|
||||
prelude::*,
|
||||
window::{PresentMode, WindowPlugin},
|
||||
};
|
||||
@ -172,6 +173,12 @@ fn setup(
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
first_cascade_far_bound: 0.9 * radius,
|
||||
maximum_distance: 2.8 * radius,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user