309 lines
8.0 KiB
HLSL
309 lines
8.0 KiB
HLSL
// 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_Linear_sRGB_MAT =
|
|
{
|
|
3.2409699419, -1.5373831776, -0.4986107603,
|
|
-0.9692436363, 1.8759675015, 0.0415550574,
|
|
0.0556300797, -0.2039769589, 1.0569715142,
|
|
};
|
|
|
|
static const float3x3 Linear_sRGB_2_XYZ_MAT =
|
|
{
|
|
0.4123907993, 0.3575843394, 0.1804807884,
|
|
0.2126390059, 0.7151686788, 0.0721923154,
|
|
0.0193308187, 0.1191947798, 0.9505321522,
|
|
};
|
|
|
|
float3 LinearRGB_2_XYZ( float3 LinearRGB )
|
|
{
|
|
#if WORKING_COLOR_SPACE_IS_SRGB
|
|
return mul(Linear_sRGB_2_XYZ_MAT, LinearRGB);
|
|
#else
|
|
return mul(WORKING_COLOR_SPACE_RGB_TO_XYZ_MAT, LinearRGB);
|
|
#endif
|
|
}
|
|
|
|
float3 XYZ_2_LinearRGB( float3 XYZ )
|
|
{
|
|
#if WORKING_COLOR_SPACE_IS_SRGB
|
|
return mul(XYZ_2_Linear_sRGB_MAT, XYZ);
|
|
#else
|
|
return mul(XYZ_TO_RGB_WORKING_COLOR_SPACE_MAT, XYZ);
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// CIE LAB
|
|
// https://en.wikipedia.org/wiki/Lab_color_space
|
|
//------------------------------------------------------------------
|
|
// Assume XYZ is in nominal range to reduce the operation on a constant coefficient 100.
|
|
// E.g., white RGB (1, 1, 1) maps to XYZ (0.9504559271, 1.0, 1.0890577508)
|
|
// for a reference white of D65. LAB is still in the original range.
|
|
|
|
#if WORKING_COLOR_SPACE_IS_SRGB
|
|
static const float3 XYZ_WHITE_REF_NOMINAL = float3(0.9504559271, 1.0, 1.0890577508);
|
|
#else
|
|
static const float3 XYZ_WHITE_REF_NOMINAL = mul(WORKING_COLOR_SPACE_RGB_TO_XYZ_MAT, float3(1.0, 1.0, 1.0));
|
|
#endif
|
|
|
|
static const float XYZ_2_LAB_DELTA_SQUARED = 0.04280618311; // (6/29)^2
|
|
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 XYZ = LinearRGB_2_XYZ(LinearRGB);
|
|
|
|
float t_X = XYZ.x / XYZ_WHITE_REF_NOMINAL.x;
|
|
float t_Y = XYZ.y / XYZ_WHITE_REF_NOMINAL.y;
|
|
float t_Z = XYZ.z / XYZ_WHITE_REF_NOMINAL.z;
|
|
|
|
float f_X = (t_X > XYZ_2_LAB_DELTA_CUBED) ? pow(max(t_X, XYZ_2_LAB_DELTA_CUBED), 1.0 / 3.0) : xyz_otherwise(t_X);
|
|
float f_Y = (t_Y > XYZ_2_LAB_DELTA_CUBED) ? pow(max(t_Y, XYZ_2_LAB_DELTA_CUBED), 1.0 / 3.0) : xyz_otherwise(t_Y);
|
|
float f_Z = (t_Z > XYZ_2_LAB_DELTA_CUBED) ? pow(max(t_Z, XYZ_2_LAB_DELTA_CUBED), 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 )
|
|
{
|
|
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 = XYZ_WHITE_REF_NOMINAL.x * f_x;
|
|
float Y = XYZ_WHITE_REF_NOMINAL.y * f_y;
|
|
float Z = XYZ_WHITE_REF_NOMINAL.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);
|
|
}
|
|
|
|
// adapted from https://www.shadertoy.com/view/MsS3Wc by Inigo Quilez
|
|
float3 HSV_2_LinearRGB_Smooth(float3 HSV)
|
|
{
|
|
float3 RGB = clamp(abs((HSV.x * 6.0 + float3(0.0, 4.0, 2.0)) % 6.0 - 3.0) - 1.0, 0.0, 1.0);
|
|
RGB = RGB * RGB * (3.0 - 2.0 * RGB); // cubic smoothing
|
|
return HSV.z * lerp(float3(1.0, 1.0, 1.0), RGB, HSV.y);
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// LCH
|
|
//------------------------------------------------------------------
|
|
|
|
float3 LAB_2_LCH(float3 LAB)
|
|
{
|
|
float3 LCH;
|
|
LCH.x = LAB.x;
|
|
LCH.y = length(LAB.yz);
|
|
float HInDegree0To360 = 0.0f;
|
|
|
|
if (LAB.z != 0 || LAB.y != 0)
|
|
{
|
|
HInDegree0To360 = atan2(LAB.z, LAB.y) * 180 / 3.1415926535897932f;
|
|
HInDegree0To360 += lerp(0, 360.0f, HInDegree0To360 < 0);
|
|
}
|
|
LCH.z = HInDegree0To360;
|
|
return LCH;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------
|
|
// sRGB / Working Color Space
|
|
//------------------------------------------------------------------
|
|
float3 SRGBCoefficientsToWorkingColorSpace(in float3 CoefficientsSRGB)
|
|
{
|
|
#if WORKING_COLOR_SPACE_IS_SRGB
|
|
return CoefficientsSRGB;
|
|
#else
|
|
float3 TransmittanceSRGB = exp(-CoefficientsSRGB);
|
|
float3 TransmittanceWCS = mul(SRGB_TO_WORKING_COLOR_SPACE_MAT, TransmittanceSRGB);
|
|
return -log(TransmittanceWCS);
|
|
#endif
|
|
}
|
|
|
|
float3 SRGBColorToWorkingColorSpace(in float3 ColorSRGB)
|
|
{
|
|
#if WORKING_COLOR_SPACE_IS_SRGB
|
|
return ColorSRGB;
|
|
#else
|
|
return mul(SRGB_TO_WORKING_COLOR_SPACE_MAT, ColorSRGB);
|
|
#endif
|
|
}
|