eng doc
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# 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/lights/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/effects/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](../meshes/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)
|
||||
Reference in New Issue
Block a user