#version 330 core out vec4 FragColor; in vec2 TexCoord; uniform sampler2D InputTexture; uniform float saturationValue; // -100 (grayscale) to 100 (double sat) // Perceptual luminance weights (Rec. 709) const vec3 luminanceWeight = vec3(0.2126, 0.7152, 0.0722); vec3 applySaturation(vec3 color, float saturation) { // Get original luminance float lum = dot(color, luminanceWeight); // Skip processing for very dark or very bright pixels if (lum < 0.001 || lum > 0.999) return color; // Non-linear saturation response curve (more natural-looking) float factor; if (saturation >= 0.0) { // Positive saturation with highlight protection factor = 1.0 + (saturation / 100.0) * (1.0 - 0.3 * smoothstep(0.7, 1.0, lum)); } else { // Negative saturation with shadow protection factor = 1.0 + (saturation / 100.0) * (1.0 - 0.1 * smoothstep(0.0, 0.2, lum)); } // Apply saturation while preserving luminance vec3 adjusted = mix(vec3(lum), color, factor); // Ensure we maintain original luminance exactly float newLum = dot(adjusted, luminanceWeight); return adjusted * (lum / max(newLum, 0.001)); } void main() { vec4 color = texture(InputTexture, TexCoord); // Apply saturation color.rgb = applySaturation(color.rgb, saturationValue); // Proper clamping to valid range FragColor = vec4(clamp(color.rgb, 0.0, 1.0), color.a); }