# Shadows (shadow mapping) Shadows are **off by default** and are enabled by designating **a single** casting light: ```rust app.scene.set_shadow_caster(Some(index)); // packed index — see the pitfall below app.scene.set_shadow_caster(None); // shadows off (default) ``` Only a **directional or spot** light can cast shadows. A **point** light index disables the shadow pass (cubemap shadows are out of scope). ## ⚠️ The packed-index pitfall `set_shadow_caster` takes the light's index **in the packed array** (directionals first, then point, then spot — recalled in [Lights](lights.md)). **Index 0 is the default +Z directional** pre-loaded by `Lights::new()`, not necessarily your light. Symptom of a wrong index: the shadow camera looks in an unexpected direction and misaligned objects occlude each other (blackened objects, ghost shadows). Two ways to avoid it: 1. **Clear the list before adding yours** — your light becomes index 0: ```rust app.scene.clear_lights(); // removes the default +Z app.scene.add_directional_light(dir, [1.0, 0.98, 0.92], 1.6).unwrap(); app.scene.set_shadow_caster(Some(0)); // now it really is YOUR light ``` This is the technique in [`shadow_test.rs`](../../lib/examples/shadow_test.rs). 2. **Count the indices** — if you keep the default light and add yours, it lands at index 1: ```rust app.scene.add_directional_light(toward_light, [1.0, 0.98, 0.92], 1.5).unwrap(); // → index 1 app.scene.set_shadow_caster(Some(1)); // this is the demo's warm light that casts ``` This is the technique in [`demo.rs`](../../lib/examples/demo.rs). ## How it works (to understand the limits) Each frame, if a caster is active, the engine runs **two passes** (technical details in [FRAME_LOOP](../tech/FRAME_LOOP.md)): 1. **Shadow pass**: the scene is rendered as seen *from the light* (depth-only `shadow_shader.wgsl` shader) into a 1024² `Depth32Float` shadow map (size configurable via `SHADOW_MAP_SIZE`), with a depth bias (slope-scaled + constant) to avoid shadow acne. 2. **Color pass**: the `standard` fragment shader re-projects each fragment into light space and compares its depth against the map via a **3×3 PCF** (softened shadow edges). Things to know: - **Directional light**: the shadow frustum is orthographic, centered on the scene center (`SHADOW_SCENE_CENTER`, radius `SHADOW_SCENE_RADIUS = 5.0` by default). Objects **far from the origin** may fall outside the frustum and stop casting. - **Spot light**: the light's cone naturally bounds the shadow. - Only one light casts at a time (no multi-light shadows). - Shadows only affect meshes rendered by `standard` in lit mode — a renderer in unlit mode (see [Materials & textures](materials.md)) receives none. ## Tuning shadow rendering The constants `SHADOW_MAP_SIZE`, `SHADOW_DEPTH_BIAS`, `SHADOW_SCENE_RADIUS`, `SHADOW_SCENE_CENTER` are exposed in `wsg_lib::utils` (defaults: 1024, 0.006, 5.0, origin). Tuning tips: - **Speckled shadow edges (acne)**: raise the bias. - **Peter-panning** (shadow detached from the object): lower the bias. - **Shadow clipped at the scene edge**: raise the frustum radius (directional). - **Shadows too blurry, want them crisper**: raise the map size. ## Links - [User README](README.md) · [Lights](lights.md) · [Examples](examples.md) - [Root README](../../README.md) · [FRAME_LOOP](../tech/FRAME_LOOP.md)