Skip to content

Commit 52f0617

Browse files
danchiasuperdump
andcommitted
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 <[email protected]>
1 parent 5ee57ff commit 52f0617

File tree

11 files changed

+200
-44
lines changed

11 files changed

+200
-44
lines changed

crates/bevy_pbr/src/light.rs

Lines changed: 115 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -226,31 +226,37 @@ pub struct DirectionalLightShadowMap {
226226

227227
impl Default for DirectionalLightShadowMap {
228228
fn default() -> Self {
229-
#[cfg(feature = "webgl")]
230-
return Self { size: 1024 };
231-
#[cfg(not(feature = "webgl"))]
232-
return Self { size: 2048 };
229+
Self { size: 2048 }
233230
}
234231
}
235232

236233
/// Controls how cascaded shadow mapping works.
234+
/// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance.
235+
///
236+
/// ```
237+
/// # use bevy_pbr::CascadeShadowConfig;
238+
/// # use bevy_pbr::CascadeShadowConfigBuilder;
239+
/// # use bevy_utils::default;
240+
/// #
241+
/// let config: CascadeShadowConfig = CascadeShadowConfigBuilder {
242+
/// maximum_distance: 100.0,
243+
/// ..default()
244+
/// }.into();
245+
/// ```
237246
#[derive(Component, Clone, Debug, Reflect)]
238247
#[reflect(Component)]
239248
pub struct CascadeShadowConfig {
240249
/// The (positive) distance to the far boundary of each cascade.
241250
pub bounds: Vec<f32>,
242251
/// The proportion of overlap each cascade has with the previous cascade.
243252
pub overlap_proportion: f32,
253+
/// The (positive) distance to the near boundary of the first cascade.
254+
pub minimum_distance: f32,
244255
}
245256

246257
impl Default for CascadeShadowConfig {
247258
fn default() -> Self {
248-
if cfg!(feature = "webgl") {
249-
// Currently only support one cascade in webgl.
250-
Self::new(1, 5.0, 100.0, 0.2)
251-
} else {
252-
Self::new(4, 5.0, 1000.0, 0.2)
253-
}
259+
CascadeShadowConfigBuilder::default().into()
254260
}
255261
}
256262

@@ -268,31 +274,112 @@ fn calculate_cascade_bounds(
268274
.collect()
269275
}
270276

271-
impl CascadeShadowConfig {
272-
/// Returns a cascade config for `num_cascades` cascades, with the first cascade
273-
/// having far bound `nearest_bound` and the last cascade having far bound `shadow_maximum_distance`.
274-
/// In-between cascades will be exponentially spaced.
275-
pub fn new(
276-
num_cascades: usize,
277-
nearest_bound: f32,
278-
shadow_maximum_distance: f32,
279-
overlap_proportion: f32,
280-
) -> Self {
277+
/// Builder for [`CascadeShadowConfig`].
278+
pub struct CascadeShadowConfigBuilder {
279+
/// The number of shadow cascades.
280+
/// More cascades increases shadow quality by mitigating perspective aliasing - a phenomenom where areas
281+
/// nearer the camera are covered by fewer shadow map texels than areas further from the camera, causing
282+
/// blocky looking shadows.
283+
///
284+
/// This does come at the cost increased rendering overhead, however this overhead is still less
285+
/// than if you were to use fewer cascades and much larger shadow map textures to achieve the
286+
/// same quality level.
287+
///
288+
/// In case rendered geometry covers a relatively narrow and static depth relative to camera, it may
289+
/// make more sense to use fewer cascades and a higher resolution shadow map texture as perspective aliasing
290+
/// is not as much an issue. Be sure to adjust `minimum_distance` and `maximum_distance` appropriately.
291+
pub num_cascades: usize,
292+
/// The minimum shadow distance, which can help improve the texel resolution of the first cascade.
293+
/// Areas nearer to the camera than this will likely receive no shadows.
294+
///
295+
/// NOTE: Due to implementation details, this usually does not impact shadow quality as much as
296+
/// `first_cascade_far_bound` and `maximum_distance`. At many view frustum field-of-views, the
297+
/// texel resolution of the first cascade is dominated by the width / height of the view frustum plane
298+
/// at `first_cascade_far_bound` rather than the depth of the frustum from `minimum_distance` to
299+
/// `first_cascade_far_bound`.
300+
pub minimum_distance: f32,
301+
/// The maximum shadow distance.
302+
/// Areas further from the camera than this will likely receive no shadows.
303+
pub maximum_distance: f32,
304+
/// Sets the far bound of the first cascade, relative to the view origin.
305+
/// In-between cascades will be exponentially spaced relative to the maximum shadow distance.
306+
/// NOTE: This is ignored if there is only one cascade, the maximum distance takes precedence.
307+
pub first_cascade_far_bound: f32,
308+
/// Sets the overlap proportion between cascades.
309+
/// The overlap is used to make the transition from one cascade's shadow map to the next
310+
/// less abrupt by blending between both shadow maps.
311+
pub overlap_proportion: f32,
312+
}
313+
314+
impl CascadeShadowConfigBuilder {
315+
/// Returns the cascade config as specified by this builder.
316+
pub fn build(&self) -> CascadeShadowConfig {
281317
assert!(
282-
num_cascades > 0,
283-
"num_cascades must be positive, but was {num_cascades}",
318+
self.num_cascades > 0,
319+
"num_cascades must be positive, but was {}",
320+
self.num_cascades
284321
);
285322
assert!(
286-
(0.0..1.0).contains(&overlap_proportion),
287-
"overlap_proportion must be in [0.0, 1.0) but was {overlap_proportion}",
323+
self.minimum_distance >= 0.0,
324+
"maximum_distance must be non-negative, but was {}",
325+
self.minimum_distance
288326
);
289-
Self {
290-
bounds: calculate_cascade_bounds(num_cascades, nearest_bound, shadow_maximum_distance),
291-
overlap_proportion,
327+
assert!(
328+
self.num_cascades == 1 || self.minimum_distance < self.first_cascade_far_bound,
329+
"minimum_distance must be less than first_cascade_far_bound, but was {}",
330+
self.minimum_distance
331+
);
332+
assert!(
333+
self.maximum_distance > self.minimum_distance,
334+
"maximum_distance must be greater than minimum_distance, but was {}",
335+
self.maximum_distance
336+
);
337+
assert!(
338+
(0.0..1.0).contains(&self.overlap_proportion),
339+
"overlap_proportion must be in [0.0, 1.0) but was {}",
340+
self.overlap_proportion
341+
);
342+
CascadeShadowConfig {
343+
bounds: calculate_cascade_bounds(
344+
self.num_cascades,
345+
self.first_cascade_far_bound,
346+
self.maximum_distance,
347+
),
348+
overlap_proportion: self.overlap_proportion,
349+
minimum_distance: self.minimum_distance,
292350
}
293351
}
294352
}
295353

354+
impl Default for CascadeShadowConfigBuilder {
355+
fn default() -> Self {
356+
if cfg!(feature = "webgl") {
357+
// Currently only support one cascade in webgl.
358+
Self {
359+
num_cascades: 1,
360+
minimum_distance: 0.1,
361+
maximum_distance: 100.0,
362+
first_cascade_far_bound: 5.0,
363+
overlap_proportion: 0.2,
364+
}
365+
} else {
366+
Self {
367+
num_cascades: 4,
368+
minimum_distance: 0.1,
369+
maximum_distance: 1000.0,
370+
first_cascade_far_bound: 5.0,
371+
overlap_proportion: 0.2,
372+
}
373+
}
374+
}
375+
}
376+
377+
impl From<CascadeShadowConfigBuilder> for CascadeShadowConfig {
378+
fn from(builder: CascadeShadowConfigBuilder) -> Self {
379+
builder.build()
380+
}
381+
}
382+
296383
#[derive(Component, Clone, Debug, Default, Reflect)]
297384
#[reflect(Component)]
298385
pub struct Cascades {
@@ -375,7 +462,7 @@ pub fn update_directional_light_cascades(
375462
(1.0 - cascades_config.overlap_proportion)
376463
* -cascades_config.bounds[idx - 1]
377464
} else {
378-
0.0
465+
-cascades_config.minimum_distance
379466
},
380467
-far_bound,
381468
)

crates/bevy_pbr/src/render/pbr_functions.wgsl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,10 @@ fn pbr(
226226
&& (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
227227
shadow = fetch_directional_shadow(i, in.world_position, in.world_normal, view_z);
228228
}
229-
let light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color);
229+
var light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color);
230+
#ifdef DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES
231+
light_contrib = cascade_debug_visualization(light_contrib, i, view_z);
232+
#endif
230233
light_accum = light_accum + light_contrib * shadow;
231234
}
232235

crates/bevy_pbr/src/render/shadows.wgsl

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,22 +144,22 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, s
144144
directional_shadow_textures_sampler,
145145
light_local,
146146
depth
147-
);
147+
);
148148
#else
149149
return textureSampleCompareLevel(
150150
directional_shadow_textures,
151151
directional_shadow_textures_sampler,
152152
light_local,
153153
i32((*light).depth_texture_base_index + cascade_index),
154154
depth
155-
);
155+
);
156156
#endif
157157
}
158158

159159
fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>, view_z: f32) -> f32 {
160160
let light = &lights.directional_lights[light_id];
161161
let cascade_index = get_cascade_index(light_id, view_z);
162-
162+
163163
if (cascade_index >= (*light).num_cascades) {
164164
return 1.0;
165165
}
@@ -178,3 +178,16 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
178178
}
179179
return shadow;
180180
}
181+
182+
fn cascade_debug_visualization(
183+
output_color: vec3<f32>,
184+
light_id: u32,
185+
view_z: f32,
186+
) -> vec3<f32> {
187+
let overlay_alpha = 0.95;
188+
let cascade_index = get_cascade_index(light_id, view_z);
189+
let cascade_color = hsv2rgb(f32(cascade_index) / f32(#{MAX_CASCADES_PER_LIGHT}u + 1u), 1.0, 0.5);
190+
return vec3<f32>(
191+
(1.0 - overlay_alpha) * output_color.rgb + overlay_alpha * cascade_color
192+
);
193+
}

examples/3d/atmospheric_fog.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! | `S` | Toggle Directional Light Fog Influence |
99
1010
use bevy::{
11-
pbr::{CascadeShadowConfig, NotShadowCaster},
11+
pbr::{CascadeShadowConfigBuilder, NotShadowCaster},
1212
prelude::*,
1313
};
1414

@@ -49,9 +49,12 @@ fn setup_terrain_scene(
4949
asset_server: Res<AssetServer>,
5050
) {
5151
// Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km)
52-
// For WebGL we only support 1 cascade level for now
53-
let cascade_shadow_config =
54-
CascadeShadowConfig::new(if cfg!(feature = "webgl") { 1 } else { 4 }, 0.5, 2.5, 0.2);
52+
let cascade_shadow_config = CascadeShadowConfigBuilder {
53+
first_cascade_far_bound: 0.3,
54+
maximum_distance: 3.0,
55+
..default()
56+
}
57+
.build();
5558

5659
// Sun
5760
commands.spawn(DirectionalLightBundle {

examples/3d/fxaa.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::f32::consts::PI;
44

55
use bevy::{
66
core_pipeline::fxaa::{Fxaa, Sensitivity},
7+
pbr::CascadeShadowConfigBuilder,
78
prelude::*,
89
render::{
910
render_resource::{Extent3d, SamplerDescriptor, TextureDimension, TextureFormat},
@@ -81,6 +82,12 @@ fn setup(
8182
PI * -0.15,
8283
PI * -0.15,
8384
)),
85+
cascade_shadow_config: CascadeShadowConfigBuilder {
86+
maximum_distance: 3.0,
87+
first_cascade_far_bound: 0.9,
88+
..default()
89+
}
90+
.into(),
8491
..default()
8592
});
8693

examples/3d/lighting.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use std::f32::consts::PI;
55

6-
use bevy::{pbr::CascadeShadowConfig, prelude::*};
6+
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};
77

88
fn main() {
99
App::new()
@@ -198,8 +198,13 @@ fn setup(
198198
},
199199
// The default cascade config is designed to handle large scenes.
200200
// As this example has a much smaller world, we can tighten the shadow
201-
// far bound for better visual quality.
202-
cascade_shadow_config: CascadeShadowConfig::new(4, 5.0, 30.0, 0.2),
201+
// bounds for better visual quality.
202+
cascade_shadow_config: CascadeShadowConfigBuilder {
203+
first_cascade_far_bound: 4.0,
204+
maximum_distance: 10.0,
205+
..default()
206+
}
207+
.into(),
203208
..default()
204209
});
205210

examples/3d/load_gltf.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
33
use std::f32::consts::*;
44

5-
use bevy::{pbr::CascadeShadowConfig, prelude::*};
5+
use bevy::{
6+
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
7+
prelude::*,
8+
};
69

710
fn main() {
811
App::new()
912
.insert_resource(AmbientLight {
1013
color: Color::WHITE,
1114
brightness: 1.0 / 5.0f32,
1215
})
16+
.insert_resource(DirectionalLightShadowMap { size: 4096 })
1317
.add_plugins(DefaultPlugins)
1418
.add_startup_system(setup)
1519
.add_system(animate_light_direction)
@@ -28,7 +32,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
2832
},
2933
// This is a relatively small scene, so use tighter shadow
3034
// cascade bounds than the default for better quality.
31-
cascade_shadow_config: CascadeShadowConfig::new(1, 1.1, 1.5, 0.3),
35+
// We also adjusted the shadow map to be larger since we're
36+
// only using a single cascade.
37+
cascade_shadow_config: CascadeShadowConfigBuilder {
38+
num_cascades: 1,
39+
maximum_distance: 1.6,
40+
..default()
41+
}
42+
.into(),
3243
..default()
3344
});
3445
commands.spawn(SceneBundle {

examples/3d/shadow_caster_receiver.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::f32::consts::PI;
44

55
use bevy::{
6-
pbr::{NotShadowCaster, NotShadowReceiver},
6+
pbr::{CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver},
77
prelude::*,
88
};
99

@@ -109,6 +109,12 @@ fn setup(
109109
PI / 2.,
110110
-PI / 4.,
111111
)),
112+
cascade_shadow_config: CascadeShadowConfigBuilder {
113+
first_cascade_far_bound: 7.0,
114+
maximum_distance: 25.0,
115+
..default()
116+
}
117+
.into(),
112118
..default()
113119
});
114120

examples/3d/split_screen.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use std::f32::consts::PI;
44

55
use bevy::{
6-
core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport,
7-
window::WindowResized,
6+
core_pipeline::clear_color::ClearColorConfig, pbr::CascadeShadowConfigBuilder, prelude::*,
7+
render::camera::Viewport, window::WindowResized,
88
};
99

1010
fn main() {
@@ -41,6 +41,13 @@ fn setup(
4141
shadows_enabled: true,
4242
..default()
4343
},
44+
cascade_shadow_config: CascadeShadowConfigBuilder {
45+
num_cascades: 2,
46+
first_cascade_far_bound: 200.0,
47+
maximum_distance: 280.0,
48+
..default()
49+
}
50+
.into(),
4451
..default()
4552
});
4653

0 commit comments

Comments
 (0)