tedit/shaders/white_balance.frag
2025-04-07 20:08:16 -04:00

158 lines
5.5 KiB
GLSL

#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D InputTexture;
uniform float temperature; // Target Correlated Color Temperature (Kelvin, e.g., 1000-20000)
uniform float tint; // Green-Magenta shift (-100 to +100)
// --- Constants ---
// sRGB Primaries to XYZ (D65)
const mat3 M_RGB_2_XYZ_D65 = mat3(
0.4124564, 0.3575761, 0.1804375,
0.2126729, 0.7151522, 0.0721750,
0.0193339, 0.1191920, 0.9503041
);
// XYZ (D65) to sRGB Primaries
const mat3 M_XYZ_2_RGB_D65 = mat3(
3.2404542, -1.5371385, -0.4985314,
-0.9692660, 1.8760108, 0.0415560,
0.0556434, -0.2040259, 1.0572252
);
// Standard Illuminant D65 XYZ (Normalized Y=1) - Our Target White
const vec3 WHITEPOINT_D65 = vec3(0.95047, 1.00000, 1.08883);
// Bradford CAT Matrices
const mat3 M_BRADFORD = mat3(
0.8951, 0.2664, -0.1614,
-0.7502, 1.7135, 0.0367,
0.0389, -0.0685, 1.0296
);
const mat3 M_BRADFORD_INV = mat3(
0.9869929, -0.1470543, 0.1599627,
0.4323053, 0.5183603, 0.0492912,
-0.0085287, 0.0400428, 0.9684867
);
// --- Helper Functions ---
// Approximate Kelvin Temperature to XYZ Chromaticity (xy) -> XYZ coordinates
// Uses simplified polynomial fits for different temperature ranges.
// Note: More accurate methods often use look-up tables or spectral calculations.
vec3 kelvinToXYZ(float kelvin) {
float temp = clamp(kelvin, 1000.0, 20000.0);
float x, y;
// Calculate xy chromaticity coordinates based on temperature
// Formulas from: http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html (with slight adaptations)
float t = temp;
float t2 = t * t;
float t3 = t2 * t;
// Calculate x coordinate
if (t >= 1000.0 && t <= 4000.0) {
x = -0.2661239e9 / t3 - 0.2343589e6 / t2 + 0.8776956e3 / t + 0.179910;
} else { // t > 4000.0 && t <= 25000.0
x = -3.0258469e9 / t3 + 2.1070379e6 / t2 + 0.2226347e3 / t + 0.240390;
}
// Calculate y coordinate based on x
float x2 = x * x;
float x3 = x2 * x;
if (t >= 1000.0 && t <= 2222.0) {
y = -1.1063814 * x3 - 1.34811020 * x2 + 2.18555832 * x - 0.18709;
} else if (t > 2222.0 && t <= 4000.0) {
y = -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867;
} else { // t > 4000.0 && t <= 25000.0
y = 3.0817580 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;
}
// Convert xyY (Y=1) to XYZ
if (y < 1e-6) return vec3(0.0); // Avoid division by zero
float Y = 1.0;
float X = (x / y) * Y;
float Z = ((1.0 - x - y) / y) * Y;
return vec3(X, Y, Z);
}
// Apply Bradford Chromatic Adaptation Transform
vec3 adaptXYZ(vec3 xyzColor, vec3 sourceWhiteXYZ, vec3 destWhiteXYZ) {
vec3 sourceCone = M_BRADFORD * sourceWhiteXYZ;
vec3 destCone = M_BRADFORD * destWhiteXYZ;
// Avoid division by zero if source cone response is zero
// (shouldn't happen with typical white points but good practice)
if (sourceCone.r < 1e-6 || sourceCone.g < 1e-6 || sourceCone.b < 1e-6) {
return xyzColor; // Return original color if adaptation is impossible
}
vec3 ratio = destCone / sourceCone;
mat3 adaptationMatrix = M_BRADFORD_INV * mat3(ratio.x, 0, 0, 0, ratio.y, 0, 0, 0, ratio.z) * M_BRADFORD;
return adaptationMatrix * xyzColor;
}
// --- Main White Balance Function ---
vec3 applyWhiteBalance(vec3 linearSRGBColor, float tempK, float tintVal) {
// 1. Convert input Linear sRGB (D65) to XYZ D65
vec3 inputXYZ = M_RGB_2_XYZ_D65 * linearSRGBColor;
// 2. Calculate the XYZ white point of the source illuminant (from Kelvin temp)
// This is the white we want to adapt *FROM*
vec3 sourceWhiteXYZ = kelvinToXYZ(tempK);
// 3. Apply Bradford CAT to adapt from sourceWhiteXYZ to D65
vec3 adaptedXYZ = adaptXYZ(inputXYZ, sourceWhiteXYZ, WHITEPOINT_D65);
// 4. Convert adapted XYZ (now relative to D65) back to Linear sRGB
vec3 adaptedLinearSRGB = M_XYZ_2_RGB_D65 * adaptedXYZ;
// 5. Apply Tint adjustment (Post-CAT approximation)
// This shifts color balance along a Green<->Magenta axis.
// We scale RGB components slightly based on the tint value.
// A common method is to affect Green opposite to Red/Blue.
if (abs(tintVal) > 0.01) {
// Map tint -100..100 to a scaling factor, e.g., -0.1..0.1
float tintFactor = tintVal / 1000.0; // Smaller scale factor for subtle tint
// Apply scaling: Increase G for negative tint, Decrease G for positive tint
// Compensate R and B slightly in the opposite direction.
// Coefficients below are heuristic and may need tuning for perceptual feel.
float rScale = 1.0 + tintFactor * 0.5;
float gScale = 1.0 - tintFactor * 1.0;
float bScale = 1.0 + tintFactor * 0.5;
vec3 tintScaleVec = vec3(rScale, gScale, bScale);
// Optional: Normalize scale vector to preserve luminance (roughly)
// Calculate luminance of the scale vector itself
float scaleLum = dot(tintScaleVec, vec3(0.2126, 0.7152, 0.0722));
if (scaleLum > 1e-5) {
tintScaleVec /= scaleLum; // Normalize
}
adaptedLinearSRGB *= tintScaleVec;
}
return adaptedLinearSRGB;
}
void main() {
vec4 texColor = texture(InputTexture, TexCoord);
vec3 linearInputColor = texColor.rgb; // Assuming input texture is already linear sRGB
// Calculate white balanced color
vec3 whiteBalancedColor = applyWhiteBalance(linearInputColor, temperature, tint);
// Ensure output is non-negative
whiteBalancedColor = max(whiteBalancedColor, vec3(0.0));
FragColor = vec4(whiteBalancedColor, texColor.a);
}