-
Why Tone Mapping?
- Path tracing computes HDR radiance values (can be 0 to thousands of nits)
- Displays can only show LDR values in [0, 1] (or [0, 1000 nits for HDR displays)
- Tone mapping compresses the HDR range to the display range
- Also applies a “filmic” look — mimics how film responds to light
-
Exposure
- First step: scale radiance by exposure
exposed = radiance * exposure
exposure = 2^EV where EV is exposure value (stops)
- Auto-exposure: compute average luminance of the scene, adjust exposure
avg_lum = exp(average(log(luminance(pixel) + 0.001)))
exposure = key_value / avg_lum where key_value ≈ 0.18 (18% gray)
-
Reinhard Tone Mapping
- Simple, classic operator
L_out = L_in / (1 + L_in)
- Extended Reinhard (preserves white point):
L_out = L_in * (1 + L_in / L_white²) / (1 + L_in)
L_white — luminance that maps to pure white
- Pros: simple, never clips
- Cons: desaturates highlights, not filmic
-
ACES Filmic Tone Mapping
- Academy Color Encoding System — industry standard for film
- Approximation by Krzysztof Narkowicz (widely used in games):
- Better approximation by Stephen Hill (more accurate to full ACES):
- Applies an input transform (RRT) and output transform (ODT)
- Requires color space conversion to ACEScg first
- Pros: filmic look, good highlight rolloff, industry standard
- Cons: slightly desaturates, not physically accurate
-
AgX Tone Mapping
- Troy Sobotka 2022 — used in Blender since 3.x
- Designed to handle highly saturated colors without hue shifts
- Better than ACES for path-traced content (less “orange and teal” look)
-
Gamma Correction
- After tone mapping: apply gamma correction for display
- sRGB gamma: approximately
pow(x, 1/2.2) but with a linear segment near 0
- Exact sRGB transfer function:
- Always work in linear space, convert to sRGB only at final output
- Common mistake: applying gamma twice (once in tone mapper, once in display)
-
Color Space Pipeline
- Correct pipeline for path tracing:
-
- Compute radiance in linear scene-referred space (ACEScg or sRGB linear)
-
- Apply exposure
-
- Apply tone mapping operator
-
- Apply gamma correction (linear → sRGB)
-
- Output to display
- Textures: load as sRGB (gamma-encoded), convert to linear before use
- In Vulkan: use
VK_FORMAT_R8G8B8A8_SRGB — GPU auto-converts on sample
-
Luminance
- Perceptual luminance from RGB:
lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722))
- These weights are the Rec. 709 luminance coefficients
- Used for: auto-exposure, tone mapping, denoiser guidance