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

131 lines
3.8 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ColorSpace.ush"
//----------------------------------------------------------------------------------------------------
// reference: https://en.wikipedia.org/wiki/Color_difference
// CIE76
float DeltaE_CIE76(float3 Lab0, float3 Lab1)
{
return length(Lab0 - Lab1);
}
// graphics arts
static float CIE_k_L = 1;
static float CIE_K_1 = 0.045;
static float CIE_K_2 = 0.015;
static float CIE_k_C = 1;
static float CIE_k_H = 1;
float DeltaE_CIE94(float3 Lab0, float3 Lab1)
{
float Delta_L = Lab0.x - Lab1.x;
float C_1 = length(Lab0.yz);
float C_2 = length(Lab1.yz);
float Delta_C_ab = C_1 - C_2;
float2 Delta_ab = Lab0.yz - Lab1.yz;
float Delta_H_ab_sqr = length2(Delta_ab) - 2 * Delta_C_ab * Delta_C_ab;
float S_L = 1;
float S_C = 1 + CIE_K_1 * C_1;
float S_H = 1 + CIE_K_2 * C_2;
return sqrt(
Delta_L * Delta_L / ((CIE_k_L * S_L) * (CIE_k_L * S_L)) +
Delta_C_ab * Delta_C_ab / ((CIE_k_C * S_C) * (CIE_k_C * S_C)) +
Delta_H_ab_sqr / ((CIE_k_H * S_H) * (CIE_k_H * S_H))
);
}
float CosDeg(float Degree)
{
return cos(Degree * PI / 180.0f);
}
float SinDeg(float Degree)
{
return sin(Degree * PI / 180.0f);
}
// Verification is done with data in table 1
// from "The CIEDE2000 Color-Difference
// Formula: Implementation Notes,
// Supplementary Test Data, and
// Mathematical Observations"
// http://www2.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
float DeltaE_CIE2000(float3 Lab0, float3 Lab1)
{
float3 Lch0 = LAB_2_LCH(Lab0);
float3 Lch1 = LAB_2_LCH(Lab1);
float Delta_L = Lab1.x - Lab0.x;
float L_bar = (Lab1.x + Lab0.x) * 0.5f;
float C_bar = (Lch0.y + Lch1.y) * 0.5f;
float component = sqrt(pow(C_bar, 7) / (pow(C_bar, 7) + pow(25, 7)));
float G = 0.5f * (1.0f - component);
float a_1 = Lab0.y * (1.0f + G);
float a_2 = Lab1.y * (1.0f + G);
float C = (Lch0.y + Lch1.y) * 0.5f;
float3 Lch0_ap = LAB_2_LCH(float3(Lab0.x, a_1, Lab0.z));
float3 Lch1_ap = LAB_2_LCH(float3(Lab1.x, a_2, Lab1.z));
float C_bar_ap = (Lch0_ap.y + Lch1_ap.y) * 0.5f;
float Delta_C_ap = Lch1_ap.y - Lch0_ap.y;
float h1_ap = Lch0_ap.z;
float h2_ap = Lch1_ap.z;
float Abs_h2_ap_minus_h1_ap = abs(h1_ap - h2_ap);
float Delta_h_ap = (Abs_h2_ap_minus_h1_ap <= 180) ? (h2_ap - h1_ap) : (
(Abs_h2_ap_minus_h1_ap > 180 && h2_ap <= h1_ap) ? (h2_ap - h1_ap + 360) :
(h2_ap - h1_ap - 360)
);
// Corner case: Set Delta_h_ap to zero if either C is zero.
if (Lch0_ap.y == 0 || Lch1_ap.y == 0)
{
Delta_h_ap = 0.0f;
}
// Delta H' and H'
float Delta_H_ap = 2 * sqrt(Lch0_ap.y * Lch1_ap.y) * SinDeg(Delta_h_ap * 0.5f);
float h1_ap_plus_h2_ap = h1_ap + h2_ap;
float H_bar_ap = (Abs_h2_ap_minus_h1_ap <= 180) ? (h1_ap_plus_h2_ap * 0.5f) : (
(Abs_h2_ap_minus_h1_ap > 180 && h1_ap_plus_h2_ap < 360) ? ((h1_ap_plus_h2_ap + 360) * 0.5f) :
((h1_ap_plus_h2_ap - 360) * 0.5f)
);
// Corner case: When either C'_1 or C'_2 is zero, H_bar' = h'_1 + h'_2;
if (Lch0_ap.y == 0 || Lch1_ap.y == 0)
{
H_bar_ap = h1_ap_plus_h2_ap;
}
// Set T
float T = 1 - 0.17f * CosDeg(H_bar_ap - 30.0f) + 0.24f * CosDeg(2.0f * H_bar_ap)
+ 0.32f * CosDeg(3.0f * H_bar_ap + 6.0f) - 0.20f * CosDeg(4.0f * H_bar_ap - 63.0f);
float L_bar_minus_50_power2 = (L_bar - 50.0f) * (L_bar - 50.0f);
float S_L = 1 + 0.015f * L_bar_minus_50_power2 / sqrt(20.0f + L_bar_minus_50_power2);
float S_C = 1 + 0.045f * C_bar_ap;
float S_H = 1 + 0.015f * C_bar_ap * T;
float component_bar = sqrt(pow(C_bar_ap, 7) / (pow(C_bar_ap, 7) + pow(25, 7)));
float R_T = -2.0f * component_bar * SinDeg(60.0f * exp(-pow((H_bar_ap - 275.0f) / 25.0f, 2.0f)));
float C_component = Delta_C_ap / (CIE_k_C * S_C);
float H_component = Delta_H_ap / (CIE_k_H * S_H);
float Delta_E_Star_00 = sqrt(
pow(Delta_L / (CIE_k_L * S_L), 2.0f) +
pow(C_component, 2.0f) +
pow(H_component, 2.0f) +
R_T * C_component * H_component
);
return Delta_E_Star_00;
}