346 lines
9.6 KiB
GLSL
346 lines
9.6 KiB
GLSL
// The Academy Color Encoding System (ACES)
|
|
// https://github.com/ampas/aces-core/tree/v1.3.1
|
|
|
|
//!HOOK OUTPUT
|
|
//!BIND HOOKED
|
|
//!DESC tone mapping (aces)
|
|
|
|
const float TINY = 1e-5;
|
|
const float HALF_POS_INF = 31744.0;
|
|
|
|
// Gamut Compress Parameters
|
|
const float LIM_CYAN = 1.147;
|
|
const float LIM_MAGENTA = 1.264;
|
|
const float LIM_YELLOW = 1.312;
|
|
|
|
const float THR_CYAN = 0.815;
|
|
const float THR_MAGENTA = 0.803;
|
|
const float THR_YELLOW = 0.880;
|
|
|
|
const float PWR = 1.2;
|
|
|
|
// "Glow" module constants
|
|
const float RRT_GLOW_GAIN = 0.05;
|
|
const float RRT_GLOW_MID = 0.08;
|
|
|
|
// Red modifier constants
|
|
const float RRT_RED_SCALE = 0.82;
|
|
const float RRT_RED_PIVOT = 0.03;
|
|
const float RRT_RED_HUE = 0.0;
|
|
const float RRT_RED_WIDTH = 135.0;
|
|
|
|
// Desaturation constants
|
|
const float RRT_SAT_FACTOR = 0.96;
|
|
const float ODT_SAT_FACTOR = 0.93;
|
|
|
|
// Gamma compensation factor
|
|
const float DIM_SURROUND_GAMMA = 0.9811;
|
|
|
|
|
|
const mat3 RGB_to_XYZ = mat3(
|
|
0.6369580483012914, 0.14461690358620832, 0.1688809751641721,
|
|
0.2627002120112671, 0.6779980715188708, 0.05930171646986196,
|
|
0.000000000000000, 0.028072693049087428, 1.060985057710791
|
|
);
|
|
|
|
const mat3 XYZ_to_RGB = mat3(
|
|
1.716651187971268, -0.355670783776392, -0.253366281373660,
|
|
-0.666684351832489, 1.616481236634939, 0.0157685458139111,
|
|
0.017639857445311, -0.042770613257809, 0.942103121235474
|
|
);
|
|
|
|
const mat3 AP0_to_XYZ = mat3(
|
|
0.9525523959, 0.0000000000, 0.0000936786,
|
|
0.3439664498, 0.7281660966, -0.0721325464,
|
|
0.0000000000, 0.0000000000, 1.0088251844
|
|
);
|
|
|
|
const mat3 XYZ_to_AP0 = mat3(
|
|
1.0498110175, 0.0000000000, -0.0000974845,
|
|
-0.4959030231, 1.3733130458, 0.0982400361,
|
|
0.0000000000, 0.0000000000, 0.9912520182
|
|
);
|
|
|
|
const mat3 AP1_to_XYZ = mat3(
|
|
0.6624541811, 0.1340042065, 0.1561876870,
|
|
0.2722287168, 0.6740817658, 0.0536895174,
|
|
-0.0055746495, 0.0040607335, 1.0103391003
|
|
);
|
|
|
|
const mat3 XYZ_to_AP1 = mat3(
|
|
1.6410233797, -0.3248032942, -0.2364246952,
|
|
-0.6636628587, 1.6153315917, 0.0167563477,
|
|
0.0117218943, -0.0082844420, 0.9883948585
|
|
);
|
|
|
|
const vec3 LUMINANCE_AP1 = vec3(0.2722287168, 0.6740817658, 0.0536895174);
|
|
|
|
const mat3 D60_to_D65_CAT = mat3(
|
|
0.987224, -0.00611327, 0.0159533,
|
|
-0.00759836, 1.00186, 0.00533002,
|
|
0.00307257, -0.00509595, 1.08168
|
|
);
|
|
|
|
const mat3 D65_to_D60_CAT = mat3(
|
|
1.0130349238541335252, 0.0061053088545854651618, -0.014970963195236360098,
|
|
0.0076982295895192892886, 0.9981648317745535941, -0.0050320341346474782061,
|
|
-0.0028413125165573776196, 0.0046851555780399034147, 0.92450665292696206889
|
|
);
|
|
|
|
// Power compression function
|
|
// https://www.desmos.com/calculator/iwcyjg6av0
|
|
float compress(float dist, float lim, float thr, float pwr) {
|
|
float scl = (lim - thr) / pow(pow((1.0 - thr) / (lim - thr), -pwr) - 1.0, 1.0 / pwr);
|
|
float c = thr + (dist - thr) / (pow(1.0 + pow((dist - thr) / scl, pwr), 1.0 / pwr));
|
|
return (dist < thr ? dist : c);
|
|
}
|
|
|
|
vec3 reference_gamut_compress(vec3 rgb) {
|
|
// Achromatic axis
|
|
float ac = max(max(rgb.r, rgb.g), rgb.b);
|
|
|
|
// Inverse RGB Ratios: distance from achromatic axis
|
|
vec3 d = ac == 0.0 ? vec3(0.0) : (ac - rgb) / abs(ac);
|
|
|
|
// Compressed distance
|
|
vec3 cd = vec3(
|
|
compress(d.x, LIM_CYAN, THR_CYAN, PWR),
|
|
compress(d.y, LIM_MAGENTA, THR_MAGENTA, PWR),
|
|
compress(d.z, LIM_YELLOW, THR_YELLOW, PWR)
|
|
);
|
|
|
|
// Inverse RGB Ratios to RGB
|
|
vec3 crgb = ac - cd * abs(ac);
|
|
|
|
return crgb;
|
|
}
|
|
|
|
float rgb_to_saturation(vec3 rgb) {
|
|
float mi = min(min(rgb.r, rgb.g), rgb.b);
|
|
float ma = max(max(rgb.r, rgb.g), rgb.b);
|
|
return (max(ma, TINY) - max(mi, TINY)) / max(ma, 1e-2);
|
|
}
|
|
|
|
// Converts RGB to a luminance proxy, here called YC
|
|
// YC is ~ Y + K * Chroma
|
|
// Constant YC is a cone-shaped surface in RGB space, with the tip on the
|
|
// neutral axis, towards white.
|
|
// YC is normalized: RGB 1 1 1 maps to YC = 1
|
|
//
|
|
// ycRadiusWeight defaults to 1.75, although can be overridden in function
|
|
// call to rgb_to_yc
|
|
// ycRadiusWeight = 1 -> YC for pure cyan, magenta, yellow == YC for neutral
|
|
// of same value
|
|
// ycRadiusWeight = 2 -> YC for pure red, green, blue == YC for neutral of
|
|
// same value.
|
|
float rgb_to_yc(vec3 rgb) {
|
|
float ycRadiusWeight = 1.75;
|
|
|
|
float r = rgb.r;
|
|
float g = rgb.g;
|
|
float b = rgb.b;
|
|
|
|
float chroma = sqrt(b * (b - g) + g * (g - r) + r * (r - b));
|
|
|
|
return (b + g + r + ycRadiusWeight * chroma) / 3.0;
|
|
}
|
|
|
|
// Sigmoid function in the range 0 to 1 spanning -2 to +2.
|
|
float sigmoid_shaper(float x) {
|
|
float t = max(1.0 - abs(x / 2.0), 0.0);
|
|
float y = 1.0 + sign(x) * (1.0 - t * t);
|
|
return y / 2.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.0;
|
|
} else {
|
|
glowGainOut = glowGainIn * (glowMid / ycIn - 1.0 / 2.0);
|
|
}
|
|
|
|
return glowGainOut;
|
|
}
|
|
|
|
// Returns a geometric hue angle in degrees (0-360) based on RGB values.
|
|
// For neutral colors, hue is undefined and the function will return a quiet NaN value.
|
|
float rgb_to_hue(vec3 rgb) {
|
|
// RGB triplets where RGB are equal have an undefined hue
|
|
float hue = 0.0;
|
|
|
|
if (!(rgb.x == rgb.y && rgb.y == rgb.z)) {
|
|
float x = sqrt(3.0) * (rgb.y - rgb.z);
|
|
float y = 2.0 * rgb.x - rgb.y - rgb.z;
|
|
hue = degrees(atan(y, x));
|
|
}
|
|
|
|
return (hue < 0.0) ? hue + 360.0 : hue;
|
|
}
|
|
|
|
float center_hue(float hue, float centerH) {
|
|
float hueCentered = hue - centerH;
|
|
if (hueCentered < -180.0) {
|
|
hueCentered = hueCentered + 360.0;
|
|
} else if (hueCentered > 180.0) {
|
|
hueCentered = hueCentered - 360.0;
|
|
}
|
|
return hueCentered;
|
|
}
|
|
|
|
// Fitting of RRT + ODT (RGB monitor 100 nits dim) from:
|
|
// https://github.com/colour-science/colour-unity/blob/master/Assets/Colour/Notebooks/CIECAM02_Unity.ipynb
|
|
// RMSE: 0.0012846272106
|
|
vec3 tonescale(vec3 ap1) {
|
|
float a = 2.785085;
|
|
float b = 0.107772;
|
|
float c = 2.936045;
|
|
float d = 0.887122;
|
|
float e = 0.806889;
|
|
return (ap1 * (a * ap1 + b)) / (ap1 * (c * ap1 + d) + e);
|
|
}
|
|
|
|
vec3 XYZ_to_xyY(vec3 XYZ) {
|
|
float X = XYZ.x;
|
|
float Y = XYZ.y;
|
|
float Z = XYZ.z;
|
|
|
|
float divisor = X + Y + Z;
|
|
if (divisor == 0.0) divisor = 1e-6;
|
|
|
|
float x = X / divisor;
|
|
float y = Y / divisor;
|
|
|
|
return vec3(x, y, Y);
|
|
}
|
|
|
|
vec3 xyY_to_XYZ(vec3 xyY) {
|
|
float x = xyY.x;
|
|
float y = xyY.y;
|
|
float Y = xyY.z;
|
|
|
|
float multiplo = Y / max(y, 1e-6);
|
|
|
|
float z = 1.0 - x - y;
|
|
float X = x * multiplo;
|
|
float Z = z * multiplo;
|
|
|
|
return vec3(X, Y, Z);
|
|
}
|
|
|
|
vec3 darkSurround_to_dimSurround(vec3 linearCV) {
|
|
vec3 XYZ = linearCV * AP1_to_XYZ;
|
|
vec3 xyY = XYZ_to_xyY(XYZ);
|
|
|
|
xyY.z = clamp(xyY.z, 0.0, HALF_POS_INF);
|
|
xyY.z = pow(xyY.z, DIM_SURROUND_GAMMA);
|
|
|
|
XYZ = xyY_to_XYZ(xyY);
|
|
return XYZ * XYZ_to_AP1;
|
|
}
|
|
|
|
vec3 ACES(vec3 color) {
|
|
vec3 ap0;
|
|
vec3 ap1;
|
|
vec3 cv;
|
|
|
|
// Look Modification Transforms (LMTs)
|
|
ap1 = color * RGB_to_XYZ * D65_to_D60_CAT * XYZ_to_AP1;
|
|
|
|
ap1 = reference_gamut_compress(ap1);
|
|
|
|
ap0 = ap1 * AP1_to_XYZ * XYZ_to_AP0;
|
|
|
|
|
|
// Reference Rendering Transform (RRT)
|
|
|
|
// Glow module
|
|
float saturation = rgb_to_saturation(ap0);
|
|
float ycIn = rgb_to_yc(ap0);
|
|
float s = sigmoid_shaper((saturation - 0.4) / 0.2);
|
|
float addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
|
|
ap0 *= addedGlow;
|
|
|
|
// Red modifier
|
|
float hue = rgb_to_hue(ap0);
|
|
float centeredHue = center_hue(hue, RRT_RED_HUE);
|
|
// hueWeight = cubic_basis_shaper(centeredHue, RRT_RED_WIDTH);
|
|
float hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH));
|
|
hueWeight *= hueWeight;
|
|
|
|
ap0.r += hueWeight * saturation * (RRT_RED_PIVOT - ap0.r) * (1.0 - RRT_RED_SCALE);
|
|
|
|
// ACES to RGB rendering space
|
|
ap1 = ap0 * AP0_to_XYZ * XYZ_to_AP1;
|
|
|
|
// avoids saturated negative colors from becoming positive in the matrix
|
|
ap1 = clamp(ap1, 0.0, HALF_POS_INF);
|
|
|
|
// Global desaturation
|
|
ap1 = mix(vec3(dot(ap1, LUMINANCE_AP1)), ap1, RRT_SAT_FACTOR);
|
|
|
|
|
|
// Output Device Transform (ODT)
|
|
|
|
// Apply the tonescale independently in rendering-space RGB
|
|
ap1 = tonescale(ap1);
|
|
|
|
// Apply gamma adjustment to compensate for dim surround
|
|
cv = darkSurround_to_dimSurround(ap1);
|
|
|
|
// Apply desaturation to compensate for luminance difference
|
|
cv = mix(vec3(dot(cv, LUMINANCE_AP1)), cv, ODT_SAT_FACTOR);
|
|
|
|
// Convert to display primary encoding
|
|
cv = cv * AP1_to_XYZ * D60_to_D65_CAT * XYZ_to_RGB;
|
|
|
|
return cv;
|
|
}
|
|
|
|
vec4 hook() {
|
|
vec4 color = HOOKED_tex(HOOKED_pos);
|
|
|
|
color.rgb = ACES(color.rgb);
|
|
|
|
return color;
|
|
}
|
|
|
|
//!HOOK OUTPUT
|
|
//!BIND HOOKED
|
|
//!DESC black point compensation
|
|
|
|
// https://www.color.org/WP40-Black_Point_Compensation_2010-07-27.pdf
|
|
|
|
vec3 RGB_to_XYZ(vec3 RGB) {
|
|
return RGB * mat3(
|
|
0.6369580483012914, 0.14461690358620832, 0.1688809751641721,
|
|
0.2627002120112671, 0.6779980715188708, 0.05930171646986196,
|
|
0.000000000000000, 0.028072693049087428, 1.060985057710791
|
|
);
|
|
}
|
|
|
|
vec3 XYZ_to_RGB(vec3 XYZ) {
|
|
return XYZ * mat3(
|
|
1.716651187971268, -0.355670783776392, -0.253366281373660,
|
|
-0.666684351832489, 1.616481236634939, 0.0157685458139111,
|
|
0.017639857445311, -0.042770613257809, 0.942103121235474
|
|
);
|
|
}
|
|
|
|
vec3 black_point_compensation(vec3 XYZ, float s, float d) {
|
|
float r = (1.0 - d) / (1.0 - s);
|
|
return r * XYZ + (1.0 - r) * RGB_to_XYZ(vec3(1.0));
|
|
}
|
|
|
|
vec4 hook() {
|
|
vec4 color = HOOKED_tex(HOOKED_pos);
|
|
|
|
color.rgb = RGB_to_XYZ(color.rgb);
|
|
color.rgb = black_point_compensation(color.rgb, 0.0, 0.001);
|
|
color.rgb = XYZ_to_RGB(color.rgb);
|
|
|
|
return color;
|
|
}
|