3.9 KiB
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
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
demoexample 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.
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
Rgba16Floattexture + 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
demoexample 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).