fix(shadow): make Étape 14 shadow visible
The shadow pass was correct but the demo light was much too steep (52° elevation), so the blocker's shadow fell in a ~0.5-unit sliver tight against the cube's base and was invisible against the bright ground (offscreen pixel probe found a single dark pixel). Verified with an offscreen probe using the real Renderer::render_scene + shadow path: - steep front light (0.6,1.1,0.6) -> 1 dark pixel (no visible shadow) - shallow side light (1.0,0.3,0.0) -> 17 107 pixels (shadow pipeline OK) - tuned front-right (1.0,0.5,0.0) -> 16 979 pixels (clear visible shadow) The azimuth matters most: from the elevated front-right camera, a shadow cast toward -z falls behind the cube and is occluded; one cast toward -x runs across the ground to the left of the cube and reads clearly. Tuned light therefore sits front-right and low (toward_light (1.0,0.5,0.0)), keeping the front faces lit while casting a clearly visible PCF-softened shadow. Also reapply the LessEqual comparison sampler fix (commit39167eehad set it, but was later reverted to GreaterEqual by9a51ff7while debugging; the probe confirms LessEqual is the correct, non-inverted test). Correct 'rotating cube' to 'cube' in README/ROADMAP (shadow_test scene is static).
This commit is contained in:
@@ -161,14 +161,16 @@ impl Renderer {
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
// The shadow map uses WebGPU `[0,1]` clip depth: the light's orthographic
|
||||
// projection is built with glam's `directx` (WebGPU) module so NDC z is already in
|
||||
// [0,1] and matches the `current_depth` computed in the main-pass shader. The
|
||||
// comparison sampler compares the recorded depth against the reference: a surface is
|
||||
// LIT when it is no farther from the light than the depth recorded in the map, i.e.
|
||||
// `stored_depth >= reference` (GreaterEqual). The map is cleared to 1.0 (far), so
|
||||
// un-blocked texels pass and surfaces behind a blocker fail.
|
||||
compare: Some(wgpu::CompareFunction::GreaterEqual),
|
||||
// The shadow map uses WebGPU `[0,1]` clip depth (glam `directx`/WebGPU module), so the
|
||||
// depth stored in the map and the fragment depth computed in the main-pass shader share
|
||||
// the same convention (smaller = closer to the light ; the map is cleared to 1.0 = far).
|
||||
// A surface is LIT when it is no farther from the light than the recorded blocker, i.e.
|
||||
// `current_depth <= stored_depth`. `textureSampleCompare` returns 1 when the sampler's
|
||||
// compare function holds for `compare_op(depth_ref, sampled)`, so `LessEqual` is the
|
||||
// correct choice: `depth_ref (= current_depth - bias) <= stored_depth` → lit. Using
|
||||
// `GreaterEqual` here inverts the test (shadowed regions render lit, directly-lit
|
||||
// surfaces self-shadow to black) — the regression seen in the Étape 14 `shadow_test`.
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
});
|
||||
let shadow_map_layout = create_shadow_map_bind_group_layout(&device);
|
||||
|
||||
Reference in New Issue
Block a user