Étape 14: add shadow mapping (directional light, Phase 4.2)
Implement shadow mapping for directional lights: - Scene::set_shadow_caster(Option<usize>) selects the shadow-casting light by packed frame-array index (None disables; point lights rejected at render). - Lights::get(index) resolves a packed index across the directional/point/spot lists. - Renderer allocates a shadow depth map, comparison sampler, group-3 bind groups, shadow uniform buffer and shadow pipeline; render_scene does a depth-only shadow pass before the main pass; compute_shadow_light_view_proj builds an orthographic light-space frustum from the scene radius. - standard_shader: shadow_light_index/light_view_proj/shadow_params uniforms, @group(3) depth map + comparison sampler, 3x3 PCF compute_shadow(). - shadow_shader: path/vertex shader with attribute layout matching the shared vertex buffer (only position consumed). - shadow_test example: directional shadow caster casts a PCF-softened shadow onto a ground slab; documented in examples README.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
//! Dedicated test for **shadow mapping** (Étape 14, Phase 4.2).
|
||||
//!
|
||||
//! A single **directional** light is configured as the shadow caster
|
||||
//! (`Scene::set_shadow_caster(Some(0))`). The cube sits on a large thin ground
|
||||
//! slab, so its silhouette is projected as a crisp PCF-softened shadow. With a
|
||||
//! small ambient term the shadow is clearly visible and the light/shadow
|
||||
//! directions are easy to read:
|
||||
//!
|
||||
//! 1. the **blocker** (cube) casts a directional shadow that stretches along
|
||||
//! the ground opposite the light direction,
|
||||
//! 2. the shadow edge is **softened** by 3×3 PCF (no hard jagged border),
|
||||
//! 3. the lit faces are bright while the shadowed ground stays near-ambient,
|
||||
//! proving the depth comparison is applied per-pixel.
|
||||
//!
|
||||
//! Run with: `cargo run -p wsg-lib --example shadow_test`
|
||||
use glam::{Vec3};
|
||||
use wsg_lib::resources::{Camera, Geometry};
|
||||
use wsg_lib::utils::WsgError;
|
||||
|
||||
/// Shadow handler: a fixed scene (ground slab + cube blocker) lit by one
|
||||
/// shadow-casting directional light.
|
||||
struct ShadowTest;
|
||||
|
||||
/// Axis-aligned box geometry (24 vertices / 36 indices, per-face normals + uvs).
|
||||
fn box_geometry(hx: f32, hy: f32, hz: f32) -> Geometry {
|
||||
let faces: [([f32; 3], [[f32; 3]; 4]); 6] = [
|
||||
(
|
||||
[0.0, 0.0, 1.0],
|
||||
[[-hx, -hy, hz], [hx, -hy, hz], [hx, hy, hz], [-hx, hy, hz]],
|
||||
), // +Z
|
||||
(
|
||||
[0.0, 0.0, -1.0],
|
||||
[[hx, -hy, -hz], [-hx, -hy, -hz], [-hx, hy, -hz], [hx, hy, -hz]],
|
||||
), // -Z
|
||||
(
|
||||
[1.0, 0.0, 0.0],
|
||||
[[hx, -hy, -hz], [hx, hy, -hz], [hx, hy, hz], [hx, -hy, hz]],
|
||||
), // +X
|
||||
(
|
||||
[-1.0, 0.0, 0.0],
|
||||
[[-hx, -hy, hz], [-hx, hy, hz], [-hx, hy, -hz], [-hx, -hy, -hz]],
|
||||
), // -X
|
||||
(
|
||||
[0.0, 1.0, 0.0],
|
||||
[[-hx, hy, -hz], [hx, hy, -hz], [hx, hy, hz], [-hx, hy, hz]],
|
||||
), // +Y
|
||||
(
|
||||
[0.0, -1.0, 0.0],
|
||||
[[-hx, -hy, hz], [hx, -hy, hz], [hx, -hy, -hz], [-hx, -hy, -hz]],
|
||||
), // -Y
|
||||
];
|
||||
|
||||
let mut positions = Vec::with_capacity(24);
|
||||
let mut normals = Vec::with_capacity(24);
|
||||
let mut uvs = Vec::with_capacity(24);
|
||||
let quad_uvs = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
|
||||
for (normal, corners) in faces {
|
||||
for (i, corner) in corners.iter().enumerate() {
|
||||
positions.push(*corner);
|
||||
normals.push(normal);
|
||||
uvs.push(quad_uvs[i]);
|
||||
}
|
||||
}
|
||||
let mut indices = Vec::with_capacity(36);
|
||||
for face in 0..6u16 {
|
||||
let b = face * 4;
|
||||
indices.extend_from_slice(&[b, b + 1, b + 2, b, b + 2, b + 3]);
|
||||
}
|
||||
Geometry::new(positions)
|
||||
.with_normals(normals)
|
||||
.with_uvs(uvs)
|
||||
.with_indices(indices)
|
||||
}
|
||||
|
||||
impl wsg_lib::AppHandler for ShadowTest {
|
||||
fn setup(&mut self, app: &mut wsg_lib::App) {
|
||||
app.scene
|
||||
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
||||
.unwrap();
|
||||
app.scene.add_material_shader("mat", "standard").unwrap();
|
||||
|
||||
// Ground slab (thin, wide) lying with its top at y = 0.
|
||||
app.scene
|
||||
.create_mesh("ground_mesh", box_geometry(5.0, 0.05, 5.0), Some("mat"))
|
||||
.unwrap();
|
||||
app.scene
|
||||
.add_entity_with_transform(
|
||||
"ground",
|
||||
"ground_mesh",
|
||||
wsg_lib::math::Transform::identity(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Blocker cube centred at the origin, standing on the ground (bottom at y = 0).
|
||||
app.scene
|
||||
.create_mesh("cube_mesh", box_geometry(0.5, 0.5, 0.5), Some("mat"))
|
||||
.unwrap();
|
||||
let mut cube_tf = wsg_lib::math::Transform::identity();
|
||||
cube_tf.translation = Vec3::new(0.0, 0.5, 0.0);
|
||||
app.scene
|
||||
.add_entity_with_transform("cube", "cube_mesh", cube_tf)
|
||||
.unwrap();
|
||||
|
||||
// One directional light only: replace the default list.
|
||||
app.scene.clear_lights();
|
||||
// Direction "from surface toward the light", i.e. the light source sits up and to
|
||||
// the -x -z side, so the shadow is cast toward +x +z (toward the camera).
|
||||
let toward_light = Vec3::new(-0.6, 1.1, -0.6).normalize();
|
||||
app.scene
|
||||
.add_directional_light(toward_light, [1.0, 0.98, 0.92], 1.6)
|
||||
.unwrap();
|
||||
|
||||
// Make this directional light (packed index 0) the shadow caster.
|
||||
app.scene.set_shadow_caster(Some(0));
|
||||
|
||||
// Small ambient so the shadowed side of the ground stays readable.
|
||||
app.scene.set_ambient([0.12, 0.12, 0.14]);
|
||||
|
||||
// Slightly elevated view so both the cube and its ground shadow are framed.
|
||||
app.scene
|
||||
.set_camera(Camera::new(Vec3::new(3.4, 2.6, 3.4), Vec3::ZERO, Vec3::Y));
|
||||
}
|
||||
}
|
||||
|
||||
#[pollster::main]
|
||||
async fn main() -> Result<(), WsgError> {
|
||||
let app = wsg_lib::app::AppBuilder::new()
|
||||
.title("WSG Shadow Test")
|
||||
.build()
|
||||
.await?;
|
||||
app.run(ShadowTest)
|
||||
}
|
||||
Reference in New Issue
Block a user