feat: add shadow demo with directional light shadow mapping and 3x3 PCF

- Add Mat4::orthographic() to voltex_math for light projection
- Fix pbr_demo and multi_light_demo to provide shadow bind group (group 3)
  required by updated PBR pipeline (dummy shadow with size=0 disables it)
- Create shadow_demo with two-pass rendering: shadow depth pass using
  orthographic light projection, then PBR color pass with shadow sampling
- Scene: ground plane, 3 spheres, 2 cubes with directional light

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 21:09:27 +09:00
parent 8f962368e9
commit 5f962f376e
7 changed files with 726 additions and 1 deletions

View File

@@ -161,6 +161,19 @@ impl Mat4 {
}
}
/// Orthographic projection (wgpu NDC: z [0,1])
pub fn orthographic(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Self {
let rml = right - left;
let tmb = top - bottom;
let fmn = far - near;
Self::from_cols(
[2.0 / rml, 0.0, 0.0, 0.0],
[0.0, 2.0 / tmb, 0.0, 0.0],
[0.0, 0.0, -1.0 / fmn, 0.0],
[-(right + left) / rml, -(top + bottom) / tmb, -near / fmn, 1.0],
)
}
/// Return the transpose of this matrix.
pub fn transpose(&self) -> Self {
let c = &self.cols;
@@ -314,7 +327,18 @@ mod tests {
}
}
// 8. as_slice — identity diagonal
// 8. Orthographic projection
#[test]
fn test_orthographic() {
let proj = Mat4::orthographic(-10.0, 10.0, -10.0, 10.0, 0.1, 100.0);
// Center point should map to (0, 0, ~0)
let p = proj * Vec4::new(0.0, 0.0, -0.1, 1.0);
let ndc = Vec3::new(p.x / p.w, p.y / p.w, p.z / p.w);
assert!(approx_eq(ndc.x, 0.0));
assert!(approx_eq(ndc.y, 0.0));
}
// 9. as_slice — identity diagonal
#[test]
fn test_as_slice() {
let slice = Mat4::IDENTITY.as_slice();