GLSL Color Saturation in Linear Color Space
This article provides the standard formula and GLSL implementation for adjusting color saturation directly within a linear color space. It covers the mathematical principles behind linear luminance calculation, the interpolation formula, and the appropriate GLSL code to achieve physically correct results in modern rendering pipelines.
The Linear Saturation Formula
Adjusting color saturation involves calculating the perceived luminance (grayscale value) of the input color and then performing a linear interpolation between that grayscale value and the original color vector.
In linear RGB (such as linear sRGB or Rec. 709), luminance is calculated as a weighted dot product using standard ITU-R BT.709 coefficients:
\[\text{Luminance} = (0.2126 \times R) + (0.7152 \times G) + (0.0722 \times B)\]
Once the scalar luminance value is obtained, the saturation formula is defined as:
\[\text{Result} = \text{Luminance} + \text{Saturation} \times (\text{Color} - \text{Luminance})\]
In this formula:
- A
Saturationvalue of0.0produces a completely desaturated grayscale image. - A
Saturationvalue of1.0leaves the original color unchanged. - Values greater than
1.0boost the saturation.
GLSL Implementation
In GLSL, this operation is implemented using dot to
calculate luminance and mix to perform the linear
interpolation:
vec3 adjustSaturation(vec3 color, float saturation) {
// ITU-R BT.709 coefficients for linear color space
const vec3 luminanceWeights = vec3(0.2126, 0.7152, 0.0722);
// Calculate the grayscale intensity (luminance)
float luminance = dot(color, luminanceWeights);
// Interpolate between the grayscale value and original color
return mix(vec3(luminance), color, saturation);
}Key Considerations for Linear Space
- Coefficient Selection: Avoid using legacy NTSC
weights (
vec3(0.299, 0.587, 0.114)), as those apply to gamma-encoded (sRGB) signals. Linear space requires standard Rec. 709 weights (vec3(0.2126, 0.7152, 0.0722)), or Rec. 2020 weights (vec3(0.2627, 0.6780, 0.0593)) if working in wide color gamut. - Negative Values and Clamping: Increasing saturation
past
1.0can push individual channels into negative values or excessively high ranges if colors are near boundary values. If your pipeline does not support high dynamic range (HDR) or out-of-gamut values, consider wrapping the return value in amax(..., 0.0)orclamp().