Khronos PBR Neutral Tone Mapping in GLSL
This article provides an overview and technical breakdown of the Khronos PBR Neutral tone mapping operator, followed by its complete, production-ready GLSL implementation. Developed by the Khronos 3D Commerce working group, this tone mapper is designed to preserve base color fidelity, prevent hue shifting, and smoothly compress high-dynamic-range highlights to white without altering physically based materials under standard studio lighting conditions.
Mathematical Formulation
Traditional photographic tone mappers such as ACES or Reinhard apply non-linear curves across the entire luminance range, altering midtone contrast, shifting hues, and skewing PBR material colors. The Khronos PBR Neutral operator solves this by acting as an identity function for values up to approximately 0.8, reserving compression exclusively for the highlight shoulder and smoothly blending overexposed peaks to white.
The operator operates in linear sRGB color space and is structured into three primary stages:
- Dark Offset Compensation: A quadratic curve shifts the lowest channel values to smooth out the black point transition. For minimum channel values \(x < 0.08\), an offset of \(x - 6.25x^2\) is removed; for values above \(0.08\), a constant offset of \(0.04\) is subtracted.
- Shoulder Compression: If the peak color channel exceeds the threshold of \(0.76\) (\(0.8 - 0.04\)), a rational compression curve asymptotically bounds the maximum value to \(1.0\): \[\text{newPeak} = 1.0 - \frac{d^2}{\text{peak} + d - \text{startCompression}}\] where \(d = 1.0 - \text{startCompression}\). All channels are then scaled uniformly by \(\frac{\text{newPeak}}{\text{peak}}\) to maintain the exact chroma and hue.
- Highlight Desaturation: To simulate natural sensor desaturation at extreme dynamic ranges, the color is blended toward pure white (\(\text{newPeak}\)) using a rational falloff factor \(g\): \[g = 1.0 - \frac{1.0}{\text{desaturation} \cdot (\text{peak} - \text{newPeak}) + 1.0}\]
GLSL Implementation
Below is the standard, standalone GLSL implementation of the Khronos PBR Neutral tone mapping function:
// Input: linear sRGB color (unclamped HDR)
// Output: tone-mapped linear sRGB color in the range [0.0, 1.0]
vec3 PBRNeutralToneMapping(vec3 color) {
const float startCompression = 0.8 - 0.04;
const float desaturation = 0.15;
// Step 1: Smooth dark-end offset
float x = min(color.r, min(color.g, color.b));
float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
color -= offset;
// Step 2: Peak luminance compression
float peak = max(color.r, max(color.g, color.b));
if (peak < startCompression) {
return max(color, vec3(0.0));
}
const float d = 1.0 - startCompression;
float newPeak = 1.0 - (d * d) / (peak + d - startCompression);
color *= newPeak / peak;
// Step 3: Highlight desaturation towards white
float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
return mix(color, vec3(newPeak), g);
}Integration Details
- Color Space: The input
colormust be in linear sRGB space. Standard transfer functions (sRGB EOTF or standard gamma \(2.2\)) should be applied after this tone mapping step prior to outputting to the framebuffer. - Clamping: The function gracefully bounds the output to \([0.0, 1.0]\) for positive inputs. Negative inputs produced by preceding lighting or post-processing stages should be clamped to zero before processing.
- Identity Region: For any color where the maximum component is below \(0.8\), the operation is visually linear, ensuring that textures authored using glTF PBR materials match their albedo references under neutral illumination.