Files
wsg/lib/examples/shadow_test.rs
T
Jérôme Bousquié ab3f056dbb primitive meshes
2026-09-24 14:25:44 +02:00

153 lines
5.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Dedicated test for **shadow mapping**.
//!
//! 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. The light sits at the camera's
//! front-right and low-ish, so its shadow runs clearly across the ground to
//! the left of the cube and is easy to see,
//! 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::core::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::core::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": the light sits up and to the +x side
// (the camera's right), at a lowish elevation. Its shadow is then cast toward -x,
// running clearly across the ground to the left of the cube. A steeper or more
// frontal light would push the shadow tight against the cube's base or behind it,
// where it is occluded by the cube from this elevated front-right view.
let toward_light = Vec3::new(1.0, 0.5, 0.0).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)
}