1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! Renderer-wide shadow configuration.
/// Renderer-wide shadow settings. Independent of any individual light;
/// drives atlas sizing, EVSM atlas allocation, the SSCS global toggle,
/// and the cube-pool capacity.
///
/// Changes are picked up on the next call to `Shadows::set_config`.
/// `atlas_size` changes trigger a re-pack at the start of next frame;
/// `max_point_shadows` changes are expensive (full cube-array
/// re-create) and should be applied sparingly.
#[derive(Clone, Debug, PartialEq)]
pub struct ShadowsConfig {
/// Enables the screen-space contact-shadow multiplier on the
/// directional shadow term.
pub sscs_enabled: bool,
/// Number of screen-space ray-march steps for SSCS.
pub sscs_step_count: u32,
/// Width / height (square) of the 2D PCF/PCSS shadow atlas in
/// texels. Must be a power of two.
pub atlas_size: u32,
/// Width / height of the EVSM RGBA16F atlas in texels. Allocated
/// lazily on the first frame an EVSM cascade is requested.
pub evsm_atlas_size: u32,
/// Per-layer dimension (square) of the directional-cascade texture
/// array in texels. One layer per cascade — a 2K layer covers a
/// 4-cascade light in 64 MB (Depth32f). Per-light `resolution`
/// authoring is treated as a hint: a cascade smaller than this is
/// rendered into the top-left sub-rect of its layer; a cascade
/// larger than this is clamped to the layer size. Per-layer
/// render-attachment views let throttled cascades skip the depth
/// pass without disturbing other cascades.
pub cascade_resolution: u32,
/// Maximum simultaneously-active directional cascade layers in the
/// texture array. With up to 4 cascades per directional light, 16
/// layers covers four directional shadow casters — far more than
/// the scene usually has, but cheap (`cascade_resolution²` × 4 B
/// per layer).
pub cascade_array_max_layers: u32,
/// Depth-warp exponent for EVSM. Higher values give better contact
/// hardening at the cost of overflow risk in `RGBA16F`.
pub evsm_exponent: f32,
/// Half-width of the separable Gaussian blur applied to the EVSM
/// moments, in texels.
pub evsm_blur_radius: u32,
/// Maximum number of point lights that can cast shadows
/// simultaneously. Sets the cube-array slice count.
pub max_point_shadows: u32,
/// Per-face cube shadow map resolution in texels (square). Memory
/// cost is `4 · res² · 6 · max_point_shadows` bytes (Depth32f). The
/// default (`1024`) costs ~24 MB at `max_point_shadows = 8` — sane
/// for desktops; mobile-class browsers may prefer `512` (6 MB) or
/// `256` (1.5 MB). Must be a power of two ≥ 64.
///
/// Changing this at runtime re-allocates the cube pool and triggers
/// a bind-group recreate; do it sparingly.
pub point_shadow_resolution: u32,
/// Tints each directional cascade range so the splits are visible
/// in the editor. Drives a debug bitmask flag in the opaque pass.
pub debug_cascade_colors: bool,
}
impl Default for ShadowsConfig {
fn default() -> Self {
Self {
sscs_enabled: false,
sscs_step_count: 16,
atlas_size: 4096,
evsm_atlas_size: 2048,
cascade_resolution: 2048,
cascade_array_max_layers: 16,
// 10 is the AAA-canon EVSM exponent for fp16 — gives a
// smooth contact-hardening curve with comfortable
// half-float headroom. 20 (the prior default) was at the
// top of the fp16 range and the resulting Chebyshev curve
// was so sharp it rendered like a binary mask. See
// `EVSM_EXPONENT_MAX_FP16` for the hard cap.
evsm_exponent: 10.0,
// 6 gives a clearly soft far cascade. Lower values
// (3 was the prior default) leave EVSM visually similar
// to PCF for typical caster sizes.
evsm_blur_radius: 6,
max_point_shadows: 8,
point_shadow_resolution: 1024,
debug_cascade_colors: false,
}
}
}
impl ShadowsConfig {
/// Hard upper safe limit for `evsm_exponent` under `RGBA16F`
/// moment storage. The moments `exp(c · z)` are evaluated for
/// `z ∈ [-1, 1]`, so the largest stored value is `exp(c) ≈ 5·10⁸`
/// at `c = 20` — already at the very top of the half-float range.
/// Pushing higher silently saturates and produces near-binary
/// (hard-edged) Chebyshev visibility, which defeats the whole
/// point of EVSM. AAA tunings sit near `c ≈ 10` for fp16.
pub const EVSM_EXPONENT_MAX_FP16: f32 = 18.0;
}