mirror of
https://github.com/X0nk/Bliss-Shader.git
synced 2025-01-05 17:13:28 +08:00
f82426c609
it has begun
383 lines
12 KiB
GLSL
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)
|
|
);
|
|
}
|