@@ -226,31 +226,37 @@ pub struct DirectionalLightShadowMap {
226
226
227
227
impl Default for DirectionalLightShadowMap {
228
228
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 }
233
230
}
234
231
}
235
232
236
233
/// 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
+ /// ```
237
246
#[ derive( Component , Clone , Debug , Reflect ) ]
238
247
#[ reflect( Component ) ]
239
248
pub struct CascadeShadowConfig {
240
249
/// The (positive) distance to the far boundary of each cascade.
241
250
pub bounds : Vec < f32 > ,
242
251
/// The proportion of overlap each cascade has with the previous cascade.
243
252
pub overlap_proportion : f32 ,
253
+ /// The (positive) distance to the near boundary of the first cascade.
254
+ pub minimum_distance : f32 ,
244
255
}
245
256
246
257
impl Default for CascadeShadowConfig {
247
258
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 ( )
254
260
}
255
261
}
256
262
@@ -268,31 +274,112 @@ fn calculate_cascade_bounds(
268
274
. collect ( )
269
275
}
270
276
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 {
281
317
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
284
321
) ;
285
322
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
288
326
) ;
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 ,
292
350
}
293
351
}
294
352
}
295
353
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
+
296
383
#[ derive( Component , Clone , Debug , Default , Reflect ) ]
297
384
#[ reflect( Component ) ]
298
385
pub struct Cascades {
@@ -375,7 +462,7 @@ pub fn update_directional_light_cascades(
375
462
( 1.0 - cascades_config. overlap_proportion )
376
463
* -cascades_config. bounds [ idx - 1 ]
377
464
} else {
378
- 0.0
465
+ -cascades_config . minimum_distance
379
466
} ,
380
467
-far_bound,
381
468
)
0 commit comments