// Copyright Epic Games, Inc. All Rights Reserved. #pragma once //------------------------------------------------------------------ // LMS // https://en.wikipedia.org/wiki/LMS_color_space //------------------------------------------------------------------ static const float3x3 sRGB_2_LMS_MAT = { 17.8824, 43.5161, 4.1193, 3.4557, 27.1554, 3.8671, 0.02996, 0.18431, 1.4670, }; static const float3x3 LMS_2_sRGB_MAT = { 0.0809, -0.1305, 0.1167, -0.0102, 0.0540, -0.1136, -0.0003, -0.0041, 0.6935, }; float3 sRGB_2_LMS( float3 RGB ) { return mul(sRGB_2_LMS_MAT, RGB); } float3 LMS_2_sRGB( float3 LMS ) { return mul(LMS_2_sRGB_MAT, LMS); } //------------------------------------------------------------------ // CIE XYZ // https://en.wikipedia.org/wiki/CIE_1931_color_space //------------------------------------------------------------------ static const float3x3 XYZ_2_LinearRGB_MAT = { 3.2409699419, -1.5373831776, -0.4986107603, -0.9692436363, 1.8759675015, 0.0415550574, 0.0556300797, -0.2039769589, 1.0569715142, }; static const float3x3 LinearRGB_2_XYZ_MAT = { 0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339, 0.1191920, 0.9503041, }; float3 LinearRGB_2_XYZ( float3 LinearRGB ) { return mul(LinearRGB_2_XYZ_MAT, LinearRGB); } float3 XYZ_2_LinearRGB( float3 XYZ ) { return mul(XYZ_2_LinearRGB_MAT, XYZ); } //------------------------------------------------------------------ // CIE LAB // https://en.wikipedia.org/wiki/Lab_color_space //------------------------------------------------------------------ static const float3 XYZ_WHITE_REF_D65 = float3(95.047, 100.0, 108.883); static const float3 XYZ_WHITE_REF_D50 = float3(96.6797, 100.0, 82.5188); static const float XYZ_2_LAB_DELTA_SQUARED = 0.04280618311; // (6/29)^3 static const float XYZ_2_LAB_DELTA_CUBED = 0.00885645167; // (6/29)^3 float xyz_otherwise(float t) { return (t / (3.0 * XYZ_2_LAB_DELTA_SQUARED)) + 4.0 / 29.0; } float3 LinearRGB_2_LAB( float3 LinearRGB, float3 ReferenceWhite ) { float3 XYZ = LinearRGB_2_XYZ(LinearRGB); float t_X = XYZ.x / ReferenceWhite.x; float t_Y = XYZ.y / ReferenceWhite.y; float t_Z = XYZ.z / ReferenceWhite.z; float f_X = (t_X > XYZ_2_LAB_DELTA_CUBED) ? pow(t_X, 1.0 / 3.0) : xyz_otherwise(t_X); float f_Y = (t_Y > XYZ_2_LAB_DELTA_CUBED) ? pow(t_Y, 1.0 / 3.0) : xyz_otherwise(t_Y); float f_Z = (t_Z > XYZ_2_LAB_DELTA_CUBED) ? pow(t_Z, 1.0 / 3.0) : xyz_otherwise(t_Z); float L = ( 116.0 * f_Y ) - 16.0; float a = 500.0 * ( f_X - f_Y ); float b = 200.0 * ( f_Y - f_Z ); return float3(L, a, b); } float lab_otherwise(float t) { return (3.0 * XYZ_2_LAB_DELTA_SQUARED) * (t - (4.0 / 29.0)); } float3 LAB_2_LinearRGB( float3 LAB, float3 ReferenceWhite) { float L = LAB.x; float a = LAB.y; float b = LAB.z; float t_y = (L + 16.0) / 116.0; float t_x = t_y + (a / 500.0); float t_z = t_y - (b / 200.0); float f_x = pow(t_x, 3.0); float f_y = pow(t_y, 3.0); float f_z = pow(t_z, 3.0); if (f_x <= XYZ_2_LAB_DELTA_CUBED) { f_x = lab_otherwise(t_x); } if (f_y <= XYZ_2_LAB_DELTA_CUBED) { f_y = lab_otherwise(t_y); } if (f_z <= XYZ_2_LAB_DELTA_CUBED) { f_z = lab_otherwise(t_z); } float X = ReferenceWhite.x * f_x; float Y = ReferenceWhite.y * f_y; float Z = ReferenceWhite.z * f_z; return XYZ_2_LinearRGB(float3(X, Y, Z)); } //------------------------------------------------------------------ // YCoCg and LCoCg color space for luma / chroma orthogonal processing //------------------------------------------------------------------ // Multiplier that get applied for ALU optimisation purposes on the luma, when converting LinearRGB to YCoCg. #define LINEARRGB_2_YCOCG_LUMA_MULTIPLIER 4.0 // YCoCg ranges: // - Y in [0, LINEARRGB_2_YCOCG_LUMA_MULTIPLIER] // - Co in [-2, 2] // - Cg in [-2, 2] float3 LinearRGB_2_YCoCg(float3 RGB) { float Y = dot(RGB, float3(1, 2, 1)); float Co = dot(RGB, float3(2, 0, -2)); float Cg = dot(RGB, float3(-1, 2, -1)); float3 YCoCg = float3(Y, Co, Cg); return YCoCg; } float3 YCoCg_2_LinearRGB(float3 YCoCg) { float Y = YCoCg.x * 0.25; float Co = YCoCg.y * 0.25; float Cg = YCoCg.z * 0.25; float R = Y + Co - Cg; float G = Y + Cg; float B = Y - Co - Cg; float3 RGB = float3(R, G, B); return RGB; } float3 YCoCg_2_LCoCg(float3 YCoCg) { return float3( YCoCg.x, YCoCg.yz * (YCoCg.x > 0 ? rcp(YCoCg.x) : 0)); } float3 LCoCg_2_YCoCg(float3 LCoCg) { return float3(LCoCg.x, LCoCg.x * LCoCg.yz); } float3 LinearRGB_2_LCoCg(float3 RGB) { return YCoCg_2_LCoCg(LinearRGB_2_YCoCg(RGB)); } float3 LCoCg_2_LinearRGB(float3 LCoCg) { return YCoCg_2_LinearRGB(LCoCg_2_YCoCg(LCoCg)); } // NormalisedYCoCg has Y, Co and Cg values mapped to [0, 1] float3 LinearRGB_2_NormalisedYCoCg(float3 RGB) { return LinearRGB_2_YCoCg(RGB) * float3(1.0f / LINEARRGB_2_YCOCG_LUMA_MULTIPLIER, 0.25f, 0.25f) + float3(0.0f, 0.5f, 0.5f); } float3 NormalisedYCoCg_2_LinearRGB(float3 YCoCg) { return YCoCg_2_LinearRGB(YCoCg * float3(LINEARRGB_2_YCOCG_LUMA_MULTIPLIER, 4.0f, 4.0f) + float3(0.0f, -2.0f, -2.0f)); } //------------------------------------------------------------------ // HSV //------------------------------------------------------------------ float3 HUE_2_LinearRGB(in float H) { float R = abs(H * 6 - 3) - 1; float G = 2 - abs(H * 6 - 2); float B = 2 - abs(H * 6 - 4); return saturate(float3(R, G, B)); } float3 HSV_2_LinearRGB(in float3 HSV) { float3 RGB = HUE_2_LinearRGB(HSV.x); return ((RGB - 1) * HSV.y + 1) * HSV.z; } float3 RGB_2_HCV(in float3 RGB) { // Based on work by Sam Hocevar and Emil Persson float4 P = (RGB.g < RGB.b) ? float4(RGB.bg, -1.0f, 2.0f / 3.0f): float4(RGB.gb, 0.0f, -1.0f / 3.0f); float4 Q = (RGB.r < P.x) ? float4(P.xyw, RGB.r) : float4(RGB.r, P.yzx); float Chroma = Q.x - min(Q.w, Q.y); float Hue = abs((Q.w - Q.y) / (6.0f * Chroma + 1e-10f) + Q.z); return float3(Hue, Chroma, Q.x); } float3 LinearRGB_2_HSV(in float3 RGB) { float3 HCV = RGB_2_HCV(RGB); float s = HCV.y / (HCV.z + 1e-10f); return float3(HCV.x, s, HCV.z); }