2023-01-12 15:00:14 -05:00

383 lines
12 KiB
GLSL

#define log10(x) log(x) / log(10.0)
struct ColorCorrection {
float saturation;
float vibrance;
vec3 lum;
float contrast;
float contrastMidpoint;
vec3 gain;
vec3 lift;
vec3 InvGamma;
} m;
float sigmoid_shaper(float x) { // Sigmoid function in the range 0 to 1 spanning -2 to +2.
float t = max(1.0 - abs(0.5 * x), 0.0);
float y = 1.0 + sign(x) * (1.0 - t * t);
return 0.5 * y;
}
float rgb_2_saturation(vec3 rgb) {
float minrgb = min(min(rgb.r, rgb.g), rgb.b);
float maxrgb = max(max(rgb.r, rgb.g), rgb.b);
return (max(maxrgb, 1e-10) - max(minrgb, 1e-10)) / max(maxrgb, 1e-2);
}
float rgb_2_yc(vec3 rgb) { // Converts RGB to a luminance proxy, here called YC. YC is ~ Y + K * Chroma.
float ycRadiusWeight = 1.75;
float r = rgb[0]; float g = rgb[1]; float b = rgb[2];
float chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b));
return (b + g + r + ycRadiusWeight * chroma) / 3.0;
}
float glow_fwd(float ycIn, float glowGainIn, float glowMid) {
float glowGainOut;
if (ycIn <= 2.0 / 3.0 * glowMid) {
glowGainOut = glowGainIn;
} else if ( ycIn >= 2.0 * glowMid) {
glowGainOut = 0;
} else {
glowGainOut = glowGainIn * (glowMid / ycIn - 0.5);
}
return glowGainOut;
}
float rgb_2_hue(vec3 rgb) { // Returns a geometric hue angle in degrees (0-360) based on RGB values.
float hue;
if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { // For neutral colors, hue is undefined and the function will return a quiet NaN value.
hue = 0;
} else {
hue = (180.0 / 3.1415) * atan(2.0 * rgb[0] - rgb[1] - rgb[2], sqrt(3.0) * (rgb[1] - rgb[2])); // flip due to opengl spec compared to hlsl
}
if (hue < 0.0)
hue = hue + 360.0;
return clamp(hue, 0.0, 360.0);
}
float center_hue(float hue, float centerH) {
float hueCentered = hue - centerH;
if (hueCentered < -180.0) {
hueCentered += 360.0;
} else if (hueCentered > 180.0) {
hueCentered -= 360.0;
}
return hueCentered;
}
// Transformations between CIE XYZ tristimulus values and CIE x,y
// chromaticity coordinates
vec3 XYZ_2_xyY( vec3 XYZ ) {
float divisor = max(XYZ[0] + XYZ[1] + XYZ[2], 1e-10);
vec3 xyY = XYZ.xyy;
xyY.rg = XYZ.rg / divisor;
return xyY;
}
vec3 xyY_2_XYZ(vec3 xyY) {
vec3 XYZ = vec3(0.0);
XYZ.r = xyY.r * xyY.b / max(xyY.g, 1e-10);
XYZ.g = xyY.b;
XYZ.b = (1.0 - xyY.r - xyY.g) * xyY.b / max(xyY.g, 1e-10);
return XYZ;
}
mat3 ChromaticAdaptation( vec2 src_xy, vec2 dst_xy ) {
// Von Kries chromatic adaptation
// Bradford
const mat3 ConeResponse = mat3(
vec3(0.8951, 0.2664, -0.1614),
vec3(-0.7502, 1.7135, 0.0367),
vec3(0.0389, -0.0685, 1.0296)
);
const mat3 InvConeResponse = mat3(
vec3(0.9869929, -0.1470543, 0.1599627),
vec3(0.4323053, 0.5183603, 0.0492912),
vec3(-0.0085287, 0.0400428, 0.9684867)
);
vec3 src_XYZ = xyY_2_XYZ( vec3( src_xy, 1 ) );
vec3 dst_XYZ = xyY_2_XYZ( vec3( dst_xy, 1 ) );
vec3 src_coneResp = src_XYZ * ConeResponse;
vec3 dst_coneResp = dst_XYZ * ConeResponse;
mat3 VonKriesMat = mat3(
vec3(dst_coneResp[0] / src_coneResp[0], 0.0, 0.0),
vec3(0.0, dst_coneResp[1] / src_coneResp[1], 0.0),
vec3(0.0, 0.0, dst_coneResp[2] / src_coneResp[2])
);
return (ConeResponse * VonKriesMat) * InvConeResponse;
}
/*******************************************************************************
- Color CorrectionUE4 Style
******************************************************************************/
// Accurate for 1000K < Temp < 15000K
// [Krystek 1985, "An algorithm to calculate correlated colour temperature"]
vec2 PlanckianLocusChromaticity(float Temp) {
float u = ( 0.860117757f + 1.54118254e-4f * Temp + 1.28641212e-7f * Temp*Temp ) / ( 1.0f + 8.42420235e-4f * Temp + 7.08145163e-7f * Temp*Temp );
float v = ( 0.317398726f + 4.22806245e-5f * Temp + 4.20481691e-8f * Temp*Temp ) / ( 1.0f - 2.89741816e-5f * Temp + 1.61456053e-7f * Temp*Temp );
float x = 3.0*u / ( 2.0*u - 8.0*v + 4.0 );
float y = 2.0*v / ( 2.0*u - 8.0*v + 4.0 );
return vec2(x, y);
}
vec2 D_IlluminantChromaticity(float Temp) {
// Accurate for 4000K < Temp < 25000K
// in: correlated color temperature
// out: CIE 1931 chromaticity
// Correct for revision of Plank's law
// This makes 6500 == D65
Temp *= 1.4388 / 1.438;
float x = Temp <= 7000 ?
0.244063 + ( 0.09911e3 + ( 2.9678e6 - 4.6070e9 / Temp ) / Temp ) / Temp :
0.237040 + ( 0.24748e3 + ( 1.9018e6 - 2.0064e9 / Temp ) / Temp ) / Temp;
float y = -3 * x*x + 2.87 * x - 0.275;
return vec2(x,y);
}
vec2 PlanckianIsothermal( float Temp, float Tint ) {
float u = ( 0.860117757f + 1.54118254e-4f * Temp + 1.28641212e-7f * Temp*Temp ) / ( 1.0f + 8.42420235e-4f * Temp + 7.08145163e-7f * Temp*Temp );
float v = ( 0.317398726f + 4.22806245e-5f * Temp + 4.20481691e-8f * Temp*Temp ) / ( 1.0f - 2.89741816e-5f * Temp + 1.61456053e-7f * Temp*Temp );
float ud = ( -1.13758118e9f - 1.91615621e6f * Temp - 1.53177f * Temp*Temp ) / pow( 1.41213984e6f + 1189.62f * Temp + Temp*Temp, 2.0 );
float vd = ( 1.97471536e9f - 705674.0f * Temp - 308.607f * Temp*Temp ) / pow( 6.19363586e6f - 179.456f * Temp + Temp*Temp , 2.0); //don't pow2 this
vec2 uvd = normalize( vec2( u, v ) );
// Correlated color temperature is meaningful within +/- 0.05
u += -uvd.y * Tint * 0.05;
v += uvd.x * Tint * 0.05;
float x = 3*u / ( 2*u - 8*v + 4 );
float y = 2*v / ( 2*u - 8*v + 4 );
return vec2(x,y);
}
vec3 WhiteBalance(vec3 LinearColor) {
const float WhiteTemp = float(WHITE_BALANCE);
const float WhiteTint = 0.0;
vec2 SrcWhiteDaylight = D_IlluminantChromaticity( WhiteTemp );
vec2 SrcWhitePlankian = PlanckianLocusChromaticity( WhiteTemp );
vec2 SrcWhite = WhiteTemp < 4000 ? SrcWhitePlankian : SrcWhiteDaylight;
const vec2 D65White = vec2(0.31270, 0.32900);
// Offset along isotherm
vec2 Isothermal = PlanckianIsothermal( WhiteTemp, WhiteTint ) - SrcWhitePlankian;
SrcWhite += Isothermal;
mat3x3 WhiteBalanceMat = ChromaticAdaptation( SrcWhite, D65White );
WhiteBalanceMat = (sRGB_2_XYZ_MAT * WhiteBalanceMat) * XYZ_2_sRGB_MAT;
return LinearColor * WhiteBalanceMat * 1.0;
}
/*******************************************************************************
- ACES Fimic Curve Approx.
******************************************************************************/
// ACES settings
const float FilmSlope = Film_Slope; //0.90
const float FilmToe = Film_Toe; //0.55
const float FilmShoulder = Film_Shoulder; //0.25
const float FilmBlackClip = Black_Clip;
const float FilmWhiteClip = White_Clip;
const float BlueCorrection = Blue_Correction;
const float ExpandGamut = Gamut_Expansion;
vec3 FilmToneMap(vec3 LinearColor) {
const mat3 AP0_2_sRGB = (AP0_2_XYZ_MAT * D60_2_D65_CAT) * XYZ_2_sRGB_MAT;
const mat3 AP1_2_sRGB = (AP1_2_XYZ_MAT * D60_2_D65_CAT) * XYZ_2_sRGB_MAT;
const mat3 AP0_2_AP1 = AP0_2_XYZ_MAT * XYZ_2_AP1_MAT;
const mat3 AP1_2_AP0 = AP1_2_XYZ_MAT * XYZ_2_AP0_MAT;
vec3 ColorAP1 = LinearColor * AP0_2_AP1;
float LumaAP1 = dot( ColorAP1, AP1_RGB2Y );
vec3 ChromaAP1 = ColorAP1 / LumaAP1;
float ChromaDistSqr = dot( ChromaAP1 - 1, ChromaAP1 - 1 );
float ExpandAmount = ( 1 - exp2( -4 * ChromaDistSqr ) ) * ( 1 - exp2( -4 * ExpandGamut * LumaAP1*LumaAP1 ) );
const mat3 Wide_2_XYZ_MAT = mat3(
vec3(0.5441691, 0.2395926, 0.1666943),
vec3(0.2394656, 0.7021530, 0.0583814),
vec3(-0.0023439, 0.0361834, 1.0552183)
);
const mat3 Wide_2_AP1 = Wide_2_XYZ_MAT * XYZ_2_AP1_MAT;
const mat3 ExpandMat = AP1_2_sRGB * Wide_2_AP1;
vec3 ColorExpand = ColorAP1 * ExpandMat;
ColorAP1 = mix(ColorAP1, ColorExpand, ExpandAmount);
const mat3 BlueCorrect = mat3(
vec3(0.9404372683, -0.0183068787, 0.0778696104),
vec3(0.0083786969, 0.8286599939, 0.1629613092),
vec3(0.0005471261, -0.0008833746, 1.0003362486)
);
const mat3 BlueCorrectInv = mat3(
vec3(1.06318, 0.0233956, -0.0865726),
vec3(-0.0106337, 1.20632, -0.19569),
vec3(-0.000590887, 0.00105248, 0.999538)
);
const mat3 BlueCorrectAP1 = (AP1_2_AP0 * BlueCorrect) * AP0_2_AP1;
const mat3 BlueCorrectInvAP1 = (AP1_2_AP0 * BlueCorrectInv) * AP0_2_AP1;
// Blue correction
ColorAP1 = mix(ColorAP1, ColorAP1 * BlueCorrectAP1, BlueCorrection);
vec3 ColorAP0 = LinearColor * AP1_2_AP0;
// "Glow" module constants
const float RRT_GLOW_GAIN = 0.05;
const float RRT_GLOW_MID = 0.08;
float saturation = rgb_2_saturation(ColorAP0);
float ycIn = rgb_2_yc(ColorAP0);
float s = sigmoid_shaper((saturation - 0.4) * 5.0);
float addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID) * 3;
ColorAP0 *= addedGlow;
// --- Red modifier --- //
const float RRT_RED_SCALE = 0.99;
const float RRT_RED_PIVOT = 0.22;
const float RRT_RED_HUE = 0.15;
const float RRT_RED_WIDTH = 135.0;
float hue = rgb_2_hue(ColorAP0);
float centeredHue = center_hue(hue, RRT_RED_HUE);
float hueWeight = pow(smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH)), 2.0);
ColorAP0.r += hueWeight * saturation * (RRT_RED_PIVOT - ColorAP0.r) * (1.0 - RRT_RED_SCALE);
// Use ACEScg primaries as working space
vec3 WorkingColor = ColorAP0 * AP0_2_AP1_MAT * 1.2;
WorkingColor = max(vec3(0.0), WorkingColor) * 1.1;
WorkingColor = mix(vec3(dot(WorkingColor, AP1_RGB2Y)), WorkingColor, 0.96); // Pre desaturate
const float ToeScale = 1.0 + FilmBlackClip - FilmToe;
const float ShoulderScale = 1.0 + FilmWhiteClip - FilmShoulder;
const float InMatch = in_Match;
const float OutMatch = Out_Match;
float ToeMatch = 0.0;
if(FilmToe > 0.8) {
// 0.18 will be on straight segment
ToeMatch = (1.0 - FilmToe - OutMatch) / FilmSlope + log10(InMatch);
} else {
// 0.18 will be on toe segment
// Solve for ToeMatch such that input of InMatch gives output of OutMatch.
const float bt = (OutMatch + FilmBlackClip) / ToeScale - 1.0;
ToeMatch = log10(InMatch) - 0.5 * log((1.0 + bt) / (1.0 - bt)) * (ToeScale / FilmSlope);
}
float StraightMatch = (1.0 - FilmToe) / FilmSlope - ToeMatch;
float ShoulderMatch = FilmShoulder / FilmSlope - StraightMatch;
vec3 LogColor = log10(WorkingColor);
vec3 StraightColor = FilmSlope * (LogColor + StraightMatch);
vec3 ToeColor = (-FilmBlackClip) + (2.0 * ToeScale) / (1.0 + exp((-2.0 * FilmSlope / ToeScale) * (LogColor - ToeMatch)));
vec3 ShoulderColor = (1.0 + FilmWhiteClip) - (2.0 * ShoulderScale) / (1.0 + exp(( 2.0 * FilmSlope / ShoulderScale) * (LogColor - ShoulderMatch)));
for(int i = 0; i < 1; ++i) {
ToeColor[i] = LogColor[i] < ToeMatch ? ToeColor[i] : StraightColor[i];
ShoulderColor[i] = LogColor[i] > ShoulderMatch ? ShoulderColor[i] : StraightColor[i];
}
vec3 t = clamp((LogColor - ToeMatch) / (ShoulderMatch - ToeMatch), 0.0, 1.0);
t = ShoulderMatch < ToeMatch ? 1.0 - t : t;
t = (3.0 - 2.0 * t) * t * t;
vec3 ToneColor = mix(ToeColor, ShoulderColor, t);
ToneColor = mix(vec3(dot(ToneColor, AP1_RGB2Y)), ToneColor, 0.93); // Post desaturate
ToneColor = mix(ToneColor, ToneColor * BlueCorrectInvAP1, BlueCorrection);
// Returning positive AP1 values
return max(vec3(0.0), ToneColor * AP1_2_sRGB);
}
vec3 Saturation(vec3 color, ColorCorrection m) {
float grey = dot(color, m.lum);
return grey + m.saturation * (color - grey);
}
vec3 Vibrance(vec3 color, ColorCorrection m) {
float maxColor = max(color.r, max(color.g, color.b));
float minColor = min(color.r, min(color.g, color.b));
float colorSaturation = maxColor - minColor;
float grey = dot(color, m.lum);
color = mix(vec3(grey), color, 1.0 + m.vibrance * (1.0 - sign(m.vibrance) * colorSaturation));
return color;
}
vec3 LiftGammaGain(vec3 v, ColorCorrection m) {
vec3 lerpV = clamp(pow(v, m.InvGamma), 0.0, 1.0);
return m.gain * lerpV + m.lift * (1.0 - lerpV);
}
float LogContrast(float x, const float eps, float logMidpoint, float contrast) {
float logX = log2(x + eps);
float adjX = (logX - logMidpoint) / contrast + logMidpoint;
return max(exp2(adjX) - eps, 0.0);
}
vec3 Contrast(vec3 color, ColorCorrection m) {
const float contrastEpsilon = 1e-5;
vec3 ret;
ret.x = LogContrast(color.x, contrastEpsilon, log2(0.18), m.contrast);
ret.y = LogContrast(color.y, contrastEpsilon, log2(0.18), m.contrast);
ret.z = LogContrast(color.z, contrastEpsilon, log2(0.18), m.contrast);
return ret;
}
vec3 srgbToLinear(vec3 srgb) {
return mix(
srgb * 0.07739938080495356, // 1.0 / 12.92 = ~0.07739938080495356
pow(0.947867 * srgb + 0.0521327, vec3(2.4)),
step(0.04045, srgb)
);
}
vec3 linearToSrgb(vec3 linear) {
return mix(
linear * 12.92,
pow(linear, vec3(0.416666666667)) * 1.055 - 0.055, // 1.0 / 2.4 = ~0.416666666667
step(0.0031308, linear)
);
}