@@ -226,31 +226,37 @@ pub struct DirectionalLightShadowMap {
226226
227227impl 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 ) ]
239248pub 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
246257impl 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 ) ]
298385pub 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 )
0 commit comments