89 lines
3.9 KiB
Markdown
89 lines
3.9 KiB
Markdown
# HDR & tone mapping
|
|
|
|
> **Step 20** — Opt-in HDR rendering with tone mapping.
|
|
|
|
## Principle
|
|
|
|
By default, WSG renders **directly to the swapchain** in 8-bit sRGB. This is fine for simple
|
|
scenes, but the moment you want **bloom** or physically plausible intensities, you hit the
|
|
ceiling: 8-bit clamps everything to [0,1] before any post-process can run.
|
|
|
|
When HDR is enabled, the scene is first rendered into an **offscreen float texture**
|
|
(`Rgba16Float`, full window resolution), where values are unbounded (no clamping). The
|
|
**tone mapping** pass then compresses the HDR signal into [0,1] sRGB for the swapchain.
|
|
|
|
```
|
|
Without HDR (default) With HDR (.with_hdr(ToneMapper::Aces))
|
|
───────────────────── ──────────────────────────────────────
|
|
Scene ──────────→ Swapchain Scene ──→ HDR texture (Rgba16Float)
|
|
(8-bit sRGB, clamped) │ (float, unbounded)
|
|
▼
|
|
Tone Mapping (ACES / Reinhard)
|
|
│
|
|
▼
|
|
Swapchain (sRGB)
|
|
```
|
|
|
|
## Enabling
|
|
|
|
```rust
|
|
use wsg_lib::prelude::*;
|
|
|
|
let app = AppBuilder::new()
|
|
.with_hdr(ToneMapper::Aces) // or ToneMapper::Reinhard
|
|
.build()
|
|
.await?;
|
|
```
|
|
|
|
- **Without** `with_hdr`: the pipeline is untouched, **zero cost** (no extra texture, no
|
|
extra pass).
|
|
- **With** `with_hdr`: one extra full-res texture + one fullscreen pass per frame. Negligible
|
|
cost on any discrete GPU; moderate on an integrated one (full-res read+write).
|
|
|
|
## Tone mappers
|
|
|
|
| Curve | Characteristics |
|
|
|-------|----------------|
|
|
| `Aces` | **Default.** Filmic look, good highlight roll-off, slightly desaturated in the shadows. The standard for games and engines. |
|
|
| `Reinhard` | Simple `c / (1 + c)`. Neutral and fast, but highlights "washed out" (the curve saturates quickly). |
|
|
|
|
> The `demo` example starts in **LDR** (no HDR): the sphere's emissive intensity of 3.0 is
|
|
> clamped to 1.0 — it looks "burnt" but no glow. Pressing a key enables HDR+ACES and the
|
|
> highlight rolls off gracefully.
|
|
|
|
## Exposure
|
|
|
|
Runtime-adjustable since Step 22: initialize with `AppBuilder::with_exposure(…)`, adjust with
|
|
`app.set_exposure(…)` (multiplicative, clamped to [0.01, 10.0] — only active when HDR is on).
|
|
See [Emissive + Exposure](../lights/emissive-exposure.md).
|
|
|
|
## Interaction with other features
|
|
|
|
| Feature | Behavior under HDR |
|
|
|---------|-------------------|
|
|
| **Shadows** | Unchanged — the shadow pass still writes depth; the color pass just targets the HDR texture instead of the swapchain. |
|
|
| **Fog** | Applied **before** tone mapping (inside the main pass). The fog color must be chosen in linear space, consistent with the tone curve. |
|
|
| **MSAA** | Compatible — the HDR texture becomes the multisample render target and is resolved before tone mapping. |
|
|
| **Bloom** | **Requires** HDR. Without it, bloom is ignored (warning). |
|
|
|
|
## Cost and non-regression
|
|
|
|
- No HDR (default): nothing is allocated, nothing is run. The swapchain is targeted directly.
|
|
- HDR enabled: one extra `Rgba16Float` texture + one fullscreen pass (tone mapping). If bloom
|
|
is also enabled, three more half-res textures and a few more passes (see the bloom page).
|
|
- The `demo` example shows both paths side by side: the LDR start (emissive clamped) and the
|
|
HDR+ACES mode (highlight roll-off).
|
|
|
|
## Limitations
|
|
|
|
- No **auto-exposure** (histogram-based). Exposure is fixed at build time (the demo hardcodes
|
|
1.0).
|
|
- No **DITHERING** on the output: banding may appear in smooth gradients near black (sRGB 8-bit
|
|
ceiling).
|
|
|
|
## See also
|
|
|
|
- [Shadows](../lights/shadows.md)
|
|
- [GPU-driven rendering](../cameras/gpu-driven.md)
|
|
- [Examples](../examples.md)
|