// ITU-R BT.2446 Conversion Method C // https://www.itu.int/pub/R-REP-BT.2446 //!PARAM reference_white //!TYPE float //!MINIMUM 0.0 //!MAXIMUM 1000.0 203.0 //!PARAM alpha //!TYPE float //!MINIMUM 0.00 //!MAXIMUM 0.33 0.04 //!PARAM sigma //!TYPE float //!MINIMUM 0.0 //!MAXIMUM 1.0 0.33 //!HOOK OUTPUT //!BIND HOOKED //!WHEN alpha //!DESC tone mapping (bt.2446c, crosstalk) // The crosstalk matrix is applied such that saturations of linear signals are reduced to achromatic to // avoid hue changes caused by clipping of compressed highlight parts. vec3 crosstalk(vec3 x, float a) { float b = 1.0 - 2.0 * a; mat3 transform = mat3( b, a, a, a, b, a, a, a, b ); return x * transform; } vec4 hook() { vec4 color = HOOKED_tex(HOOKED_pos); color.rgb = crosstalk(color.rgb, alpha); return color; } //!HOOK OUTPUT //!BIND HOOKED //!WHEN sigma //!DESC tone mapping (bt.2446c, chroma correction) // Optional processing of chroma correction above HDR Reference White // In SDR production, highlight parts are sometimes intentionally expressed as white. The processing // described in this section is optionally used to shift chroma above HDR Reference White to achromatic // when the converted SDR content requires a degree of consistency for SDR production content. This // processing is applied as needed before the tone-mapping processing. 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 ); } float cbrt(float x) { return sign(x) * pow(abs(x), 1.0 / 3.0); } const float delta = 6.0 / 29.0; const float deltac = delta * 2.0 / 3.0; float f(float x) { return x > pow(delta, 3.0) ? cbrt(x) : deltac + x / (3.0 * pow(delta, 2.0)); } vec3 f(vec3 x) { return vec3(f(x.x), f(x.y), f(x.z)); } float f_inv(float x) { return x > delta ? pow(x, 3.0) : (x - deltac) * (3.0 * pow(delta, 2.0)); } vec3 f_inv(vec3 x) { return vec3(f_inv(x.x), f_inv(x.y), f_inv(x.z)); } const vec3 XYZn = vec3(0.95047, 1.00000, 1.08883); vec3 XYZ_to_Lab(vec3 XYZ) { XYZ = f(XYZ / XYZn); float X = XYZ.x; float Y = XYZ.y; float Z = XYZ.z; float L = 116.0 * Y - 16.0; float a = 500.0 * (X - Y); float b = 200.0 * (Y - Z); return vec3(L, a, b); } vec3 Lab_to_XYZ(vec3 Lab) { float L = Lab.x; float a = Lab.y; float b = Lab.z; float Y = (L + 16.0) / 116.0; float X = Y + a / 500.0; float Z = Y - b / 200.0; vec3 XYZ = f_inv(vec3(X, Y, Z)) * XYZn; return XYZ; } vec3 RGB_to_Lab(vec3 color) { color = RGB_to_XYZ(color); color = XYZ_to_Lab(color); return color; } vec3 Lab_to_RGB(vec3 color) { color = Lab_to_XYZ(color); color = XYZ_to_RGB(color); return color; } const float epsilon = 1e-6; vec3 Lab_to_LCh(vec3 Lab) { float L = Lab.x; float a = Lab.y; float b = Lab.z; float C = length(vec2(a, b)); float h = (abs(a) < epsilon && abs(b) < epsilon) ? 0.0 : atan(b, a); return vec3(L, C, h); } vec3 LCh_to_Lab(vec3 LCh) { float L = LCh.x; float C = LCh.y; float h = LCh.z; C = max(C, 0.0); float a = C * cos(h); float b = C * sin(h); return vec3(L, a, b); } float chroma_correction(float L, float Lref, float Lmax, float sigma) { return L <= Lref ? 1.0 : max(1.0 - sigma * (L - Lref) / (Lmax - Lref), 0.0); } vec4 hook() { vec4 color = HOOKED_tex(HOOKED_pos); const float Lref = RGB_to_Lab(vec3(1.0)).x; const float Lmax = RGB_to_Lab(vec3(1000.0 / reference_white)).x; color.rgb = RGB_to_Lab(color.rgb); color.rgb = Lab_to_LCh(color.rgb); color.y *= chroma_correction(color.x, Lref, Lmax, sigma); color.rgb = LCh_to_Lab(color.rgb); color.rgb = Lab_to_RGB(color.rgb); return color; } //!HOOK OUTPUT //!BIND HOOKED //!DESC tone mapping (bt.2446c) 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 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 multiplier = Y / max(y, 1e-6); float z = 1.0 - x - y; float X = x * multiplier; float Z = z * multiplier; return vec3(X, Y, Z); } const float ip = 0.58535; // linear length const float k1 = 0.83802; // linear strength const float k3 = 0.74204; // shoulder strength float f(float Y, float k1, float k3, float ip) { ip /= k1; float k2 = (k1 * ip) * (1.0 - k3); float k4 = (k1 * ip) - (k2 * log(1.0 - k3)); return Y < ip ? Y * k1 : log((Y / ip) - k3) * k2 + k4; } float curve(float x) { return f(x, k1, k3, ip); } vec4 hook() { vec4 color = HOOKED_tex(HOOKED_pos); color.rgb = RGB_to_XYZ(color.rgb); color.rgb = XYZ_to_xyY(color.rgb); color.z = curve(color.z); color.rgb = xyY_to_XYZ(color.rgb); color.rgb = XYZ_to_RGB(color.rgb); return color; } //!HOOK OUTPUT //!BIND HOOKED //!WHEN alpha //!DESC tone mapping (bt.2446c, inverse crosstalk) // The inverse crosstalk matrix is applied to ensure that the original hues of input HDR images are // recovered. vec3 crosstalk_inv(vec3 x, float a) { float b = 1.0 - a; float c = 1.0 / (1.0 - 3.0 * a); mat3 transform = mat3( b, -a, -a, -a, b, -a, -a, -a, b ); return x * transform * c; } vec4 hook() { vec4 color = HOOKED_tex(HOOKED_pos); color.rgb = crosstalk_inv(color.rgb, alpha); return color; } //!HOOK OUTPUT //!BIND HOOKED //!DESC tone mapping (bt.2446c, signal scaling) // Handling 109% range (super-whites) and black point compensation float f(float x, float a, float b, float c, float d) { return (x - a) * (d - c) / (b - a) + c; } vec3 f(vec3 x, float a, float b, float c, float d) { return vec3( f(x.x, a, b, c, d), f(x.y, a, b, c, d), f(x.z, a, b, c, d) ); } vec4 hook() { vec4 color = HOOKED_tex(HOOKED_pos); color.rgb = f(color.rgb, 0.0, 1019.0 / 940.0, 0.001, 1.0); return color; }