Files
wsg/docs/user/lights.md
T
Jérôme Bousquié 5ae978da23 doc
2026-09-21 10:07:32 +02:00

85 lines
3.3 KiB
Markdown

# Lights
Lights are **scene-global**: a single list is packed into the frame uniforms every frame, and
**all** entities receive their lighting (per-material lights are out of the current scope).
## Model
- Bounded capacity: **`MAX_LIGHTS = 8`** lights in total (directional + point + spot
combined). Adding beyond that returns an error.
- **Default**: one white directional light along **+Z** (from the surface point toward the
light) + white ambient. This default exactly reproduces the historical single-light
rendering — your scene "just works" with no configuration.
- Ambient (`set_ambient`) is a global hemispherical term, independent of the lights.
## Adding lights
```rust
use glam::Vec3;
// Directional: `dir` points FROM the surface point TOWARD the light.
app.scene
.add_directional_light(Vec3::new(1.0, 1.2, 1.0).normalize(), [1.0, 0.98, 0.92], 1.5)
.unwrap();
// Point: world position, tint, intensity, attenuation radius (linear down to 0).
app.scene
.add_point_light(Vec3::new(0.5, 1.6, 1.8), [1.0, 0.7, 0.3], 1.2, 6.0)
.unwrap();
// Spot: position, cone axis (FROM the light TOWARD the scene), tint, intensity, radius,
// half-angle in radians (penumbra smoothed at the edge).
app.scene.add_spot_light(
Vec3::new(-2.5, 2.2, 1.0), // position
Vec3::new(2.5, -2.2, -1.0).normalize(), // axis, toward the scene
[0.3, 1.0, 0.5], // green tint
1.4, 8.0, 0.45, // intensity, radius, half-angle (~26°)
).unwrap();
```
These three calls are the ones in the [`demo`](../../lib/examples/demo.rs) example;
[`cube.rs`](../../lib/examples/cube.rs) shows a point + a spot on top of the default
directional, and [`spot_test.rs`](../../lib/examples/spot_test.rs) isolates a single spot
(ambient nearly zero).
Global settings:
| Method | Effect |
|---------|--------|
| `set_ambient([r, g, b])` | hemispherical ambient color (default white) |
| `clear_lights()` | empties the list — only ambient will light the scene (useful for a flat look without switching to unlit) |
| `set_lights(Lights)` | replaces the whole list (batch reset) |
| `lights()` | reads the current list |
## ⚠️ Packed indices (important for shadows)
Lights are stacked in the GPU array **by type, in order**:
```
index 0 .. n_dir-1 : directional
index n_dir .. +n_point-1 : point
index … .. +n_spot-1 : spot
```
Two consequences:
1. **Index 0 is the default +Z directional** (the one `Lights::new()` pre-loads),
not your first added light. This is a classic pitfall — see
[Shadows](shadows.md).
2. If you want **your** light to be the only one (and thus at index 0), clear the list
first: `app.scene.clear_lights();` then `add_*_light(…)` (this is the technique in
[`shadow_test.rs`](../../lib/examples/shadow_test.rs)).
## Intensities and tints
- `color` is an RGB in `[0..1]`; `intensity` is an unbounded multiplier.
- Local lights (point/spot) attenuate **linearly** — intensity drops to zero at `radius`.
Beyond the radius, the light contributes nothing.
- The `standard` shader accumulates ambient + all lights (no mutual occlusion between
lights; the spot cone culling happens at the fragment).
## Links
- [User README](README.md) · [Shadows](shadows.md) · [Materials & textures](materials.md)
- [Root README](../../README.md) · [ARCHI_RENDU](../tech/ARCHI_RENDU.md)