Files
UnrealEngine/Engine/Shaders/Private/ColorSpace.ush
2025-05-18 13:04:45 +08:00

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
}