Reinhard and ACES Tone Mapping in GLSL
Tone mapping is the process of compressing High Dynamic Range (HDR) color values into the standard [0.0, 1.0] Low Dynamic Range (LDR) required by standard displays. In modern real-time graphics pipelines using GLSL, operators such as Reinhard and the Academy Color Encoding System (ACES) filmic curve apply mathematical functions to fragment colors to gracefully roll off highlights, prevent harsh clamping, and maintain visual fidelity across varying exposure levels. This article explains how Reinhard and ACES curve operators compress dynamic range and how they are implemented directly in GLSL shaders.
The Dynamic Range Problem
In physically based rendering, scene luminance values often exceed
1.0. Without tone mapping, any color component greater than
1.0 is clamped by the display buffer to pure white,
eliminating detail in highlights such as skies, reflections, and direct
light sources. Dynamic range compression maps an infinite range \([0, \infty)\) down to \([0, 1]\) smoothly.
Reinhard Tone Mapping
The Reinhard operator is an asymptotic compression function based on photographic techniques. Its simplest formulation divides the input color by itself plus one:
\[\text{Color}_{\text{out}} = \frac{\text{Color}_{\text{in}}}{1.0 + \text{Color}_{\text{in}}}\]
Mechanism of Compression
As \(\text{Color}_{\text{in}}\) approaches zero, the denominator is dominated by \(1.0\), leaving values near linear scaling. As \(\text{Color}_{\text{in}}\) grows toward infinity, the \(+1.0\) term becomes negligible, and the fraction asymptotically approaches \(1.0\). This prevents clipping regardless of how bright the incoming light is.
GLSL Implementation
vec3 toneMapReinhard(vec3 color) {
return color / (color + vec3(1.0));
}An extended version incorporates a maximum luminance parameter (\(L_{\text{white}}\)) to allow very bright areas to burn out to true white:
vec3 toneMapReinhardExtended(vec3 color, float maxWhite) {
vec3 numerator = color * (vec3(1.0) + (color / (maxWhite * maxWhite)));
return numerator / (vec3(1.0) + color);
}Limitations
While computationally cheap, basic Reinhard mapping compresses dynamic range uniformly. This often desaturates bright colors and washes out contrast in mid-tones, giving the final image a flat, muddy appearance.
ACES Tone Mapping
The Academy Color Encoding System (ACES) uses an S-shaped curve (composed of a "toe", "linear section", and "shoulder") designed for film and television production. Unlike Reinhard, ACES enhances contrast in the mid-tones and darkens the shadows while preserving color saturation in high-exposure regions.
Mechanism of Compression
The ACES curve manages dynamic range through three distinct regions:
- The Toe: Curves upward from zero, lifting dark values non-linearly to retain shadow detail while adding contrast.
- The Mid-tones: Acts largely linearly, preserving the natural color relationships and perceived contrast of standard exposures.
- The Shoulder: Rolls off bright highlights smoothly as they approach \(1.0\), preventing saturation loss and color shifting (e.g., preventing intensely bright reds from skewing abruptly to yellow before turning white).
For real-time applications, Krzysztof Narkowicz's fitted approximation of the ACES curve is widely used due to its balance of fidelity and performance.
GLSL Implementation
vec3 toneMapACES(vec3 x) {
const float a = 2.51;
const float b = 0.03;
const float c = 2.43;
const float d = 0.59;
const float e = 0.14;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
}In this rational polynomial fit:
- The numerator quadratic \((a \cdot x + b)\) scales the intensity.
- The denominator quadratic \((c \cdot x + d) + e\) dictates the rate of compression for high values.
clampensures that rounding inaccuracies near the boundaries do not spill outside the displayable \([0.0, 1.0]\) bounds.
Comparison in GLSL Execution
When choosing between Reinhard and ACES in a GLSL pipeline:
- Mathematical Complexity: Reinhard requires a single vector addition and division. ACES requires several fused multiply-adds and a vector division, slightly increasing instruction count, though this difference is negligible on modern hardware.
- Visual Output: Reinhard flattens dynamic range evenly, causing bright regions to appear gray or desaturated. ACES mimics photographic film, providing an automatic boost to perceived contrast and maintaining vivid colors within blown-out specular highlights.
Both operators must be applied at the end of the post-processing chain, after all linear-space lighting and effects (like bloom) have been calculated, and immediately prior to converting the color to sRGB gamma space.