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

251 lines
7.7 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
CompositeUIPixelShader.usf: Filter pixel shader source.
=============================================================================*/
#include "Common.ush"
#include "TonemapCommon.ush"
#include "ColorDeficiency.ush"
#define LUT_SCALING 1.05f
#define LUT_SIZE 32.f
///////////////////////////////////////////////////////////////////////////////////////
#if APPLY_COLOR_DEFICIENCY
float ColorVisionDeficiencyType;
float ColorVisionDeficiencySeverity;
float bCorrectDeficiency;
float bSimulateCorrectionWithDeficiency;
#endif
///////////////////////////////////////////////////////////////////////////////////////
#if PLATFORM_SUPPORTS_RENDERTARGET_WRITE_MASK
Texture2D<uint> UIWriteMaskTexture;
#endif
Texture2D UITexture;
SamplerState UISampler;
Texture2D SceneTexture;
SamplerState SceneSampler;
float UILevel;
float UILuminance;
// Invert the encoding applied by the ApplyGammaCorrection function.
float3 LinearizeColor(float3 EncodedColor)
{
#if MAC
// Note, MacOSX native output is raw gamma 2.2 not sRGB!
return pow(EncodedColor, 2.2);
#else
#if USE_709
// Didn't profile yet if the branching version would be faster (different linear segment).
return Rec709ToLinear(EncodedColor);
#else
return sRGBToLinear(EncodedColor);
#endif
#endif
}
static const float ScRGBScaleFactor = 80.0f; // used to convert between ScRGB and nits
void HDRBackBufferConvertPS(
FScreenVertexOutput Input,
out float3 OutColor : SV_Target0
)
{
OutColor = 0;
float3 SceneColor = Texture2DSample(SceneTexture, SceneSampler, Input.UV).xyz ;
SceneColor = ST2084ToLinear( SceneColor ) / ScRGBScaleFactor;
OutColor.rgb = SceneColor;
}
float3 ComposeUIAndScene(float3 SceneColor, float4 InUIColor, float InUILevel)
{
BRANCH
if (InUIColor.w > 0.f && InUIColor.w < 1.f)
{
// Clamp gamut to sRGB as extended gamut colors bleeding into the UI can look funny
SceneColor = max(SceneColor, 0.f);
// Tonemap HDR under transparent UI with a simple Reinhard to the max luminance of the UI
// This prevents HDR bleed through destroying UI legibility
// Rec2020 coefficients to compute luminance
float KR = 0.2627, KG = 0.678, KB = 0.0593;
float Luminance = dot(SceneColor, half3(KR, KG, KB)) / InUILevel;
float OutL = 1.f / (Luminance + 1.f);
// Ease out remapping to avoid hard transitions where UI is near zero opacity
SceneColor *= lerp(1.f, OutL * InUILevel, InUIColor.w);
}
// Composite, assuming pre-multiplied alpha
return SceneColor * (1.f - InUIColor.w) + InUIColor.xyz * InUILevel;
}
float3 ApplyColorDeficiency(float3 Rec2020Color)
{
#if APPLY_COLOR_DEFICIENCY
const float3x3 Rec2020_2_sRGB = mul(XYZ_2_sRGB_MAT, Rec2020_2_XYZ_MAT);
const float3x3 sRGB_2_Rec2020 = mul(XYZ_2_Rec2020_MAT, sRGB_2_XYZ_MAT);
float3 SRGBColor = mul(Rec2020_2_sRGB, Rec2020Color.xyz);
SRGBColor = ColorDeficiency(SRGBColor, ColorVisionDeficiencyType, ColorVisionDeficiencySeverity, bCorrectDeficiency, bSimulateCorrectionWithDeficiency);
Rec2020Color = mul(sRGB_2_Rec2020, SRGBColor);
#endif
return Rec2020Color;
}
// About sRGB_2_Rec2020 in ComputeHDRUIColor/ComputeHDRSceneColor: to get consistent blending between UI and scene color, regardless of the swapchain format choice, we perform blending in the rec2020 color space, and
// move back to the output device space. Rec2020 is chosen over ScRGB because this is the way blending is currently being done in engine mode, and contains only positive values
float4 ComputeHDRUIColor(float2 InputUV)
{
const float3x3 sRGB_2_Rec2020 = mul(XYZ_2_Rec2020_MAT, sRGB_2_XYZ_MAT);
float4 UIColor = Texture2DSample(UITexture, UISampler, InputUV);
UIColor.xyz = LinearizeColor(UIColor.xyz);
UIColor.xyz = mul(sRGB_2_Rec2020, UIColor.xyz) * UILuminance;
return UIColor;
}
float3 ComputeHDRSceneColor(float3 SceneColor)
{
const float3x3 sRGB_2_Rec2020 = mul(XYZ_2_Rec2020_MAT, sRGB_2_XYZ_MAT);
#if !SCRGB_ENCODING
SceneColor.xyz = ST2084ToLinear(SceneColor.xyz);
#else
SceneColor.xyz = mul(sRGB_2_Rec2020, SceneColor.xyz) * ScRGBScaleFactor;
#endif
return SceneColor;
}
void Main(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
#if PLATFORM_SUPPORTS_RENDERTARGET_WRITE_MASK
uint CompositeUIMask = DecodeRTWriteMask(uint2(Input.Position.xy), UIWriteMaskTexture, 1);
// in case of color deficiency, we will need to update the whole texture, not just the regions that were touched by the UI
#if (APPLY_COLOR_DEFICIENCY == 0)
BRANCH
if (CompositeUIMask == 0)
{
discard;
}
#endif
#endif
float4 UIColor = ComputeHDRUIColor(Input.UV);
#if PLATFORM_SUPPORTS_RENDERTARGET_WRITE_MASK
// If the UI texture has a mask of 0, this means the UI color needs to be forced to 0 as it may contain uninitialized data
if (CompositeUIMask == 0)
{
UIColor = 0;
}
#endif
float3 SceneColor = Texture2DSample(SceneTexture, SceneSampler, Input.UV).xyz;
SceneColor = ComputeHDRSceneColor(SceneColor);
OutColor.xyz = ComposeUIAndScene(SceneColor.xyz, UIColor, UILevel);
OutColor.rgb = ApplyColorDeficiency(OutColor.rgb);
// This is not compatible when alpha is preserved through post-processing
OutColor.w = UIColor.w;
#if !SCRGB_ENCODING
// Linear -> PQ
OutColor.xyz = LinearToST2084(OutColor.xyz);
#else
const float3x3 Rec2020_2_sRGB = mul(XYZ_2_sRGB_MAT, Rec2020_2_XYZ_MAT);
OutColor.xyz = mul(Rec2020_2_sRGB, OutColor.xyz / ScRGBScaleFactor);
#endif
}
#if USE_COMPUTE_FOR_COMPOSITION
float4 SceneTextureDimensions;
RWTexture2D<float4> RWSceneTexture;
[numthreads(NUM_THREADS_PER_GROUP, NUM_THREADS_PER_GROUP, 1)]
void CompositeUICS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
if (any(DispatchThreadId.xy >= (uint2)SceneTextureDimensions.xy))
{
return;
}
float2 InputPosition = DispatchThreadId.xy + float2(0.5, 0.5);
#if PLATFORM_SUPPORTS_RENDERTARGET_WRITE_MASK
uint CompositeUIMask = DecodeRTWriteMask(DispatchThreadId.xy, UIWriteMaskTexture, 1);
// in case of color deficiency, we will need to update the whole texture, not just the regions that were touched by the UI
#if (APPLY_COLOR_DEFICIENCY == 0)
BRANCH
if (CompositeUIMask == 0)
{
return;
}
#endif
#endif
float2 InputUV = InputPosition * SceneTextureDimensions.zw;
float4 UIColor = ComputeHDRUIColor(InputUV);
#if PLATFORM_SUPPORTS_RENDERTARGET_WRITE_MASK
// If the UI texture has a mask of 0, this means the UI color needs to be forced to 0 as it may contain uninitialized data
if (CompositeUIMask == 0)
{
UIColor = 0;
}
#endif
float3 SceneColor = RWSceneTexture[DispatchThreadId.xy].rgb;
SceneColor = ComputeHDRSceneColor(SceneColor);
float4 OutColor = 0;
OutColor.xyz = ComposeUIAndScene(SceneColor.xyz, UIColor, UILevel);
// This is not compatible when alpha is preserved through post-processing
OutColor.w = UIColor.w;
OutColor.rgb = ApplyColorDeficiency(OutColor.rgb);
#if !SCRGB_ENCODING
// Linear -> PQ
OutColor.xyz = LinearToST2084(OutColor.xyz);
#else
const float3x3 Rec2020_2_sRGB = mul(XYZ_2_sRGB_MAT, Rec2020_2_XYZ_MAT);
OutColor.xyz = mul(Rec2020_2_sRGB, OutColor.xyz / ScRGBScaleFactor);
#endif
RWSceneTexture[DispatchThreadId.xy] = OutColor;
}
#endif
#if COMPOSITE_UI_FOR_BLUR_PS
float2 UITextureSize;
void CompositeUIForBlur(FScreenVertexOutput Input, out float4 OutColor : SV_Target0)
{
float4 UIColor = ComputeHDRUIColor(Input.UV);
#if PLATFORM_SUPPORTS_RENDERTARGET_WRITE_MASK
uint2 RTWriteMaskInputPosition = uint2(Input.UV * UITextureSize);
uint CompositeUIMask = DecodeRTWriteMask(RTWriteMaskInputPosition, UIWriteMaskTexture, 1);
BRANCH
if (CompositeUIMask == 0)
{
UIColor = float4(0,0,0,0);
}
#endif
float3 SceneColor = Texture2DSample(SceneTexture, UISampler, Input.UV).xyz;
SceneColor = ComputeHDRSceneColor(SceneColor);
OutColor.xyz = ComposeUIAndScene(SceneColor.xyz, UIColor, UILevel) / ScRGBScaleFactor;
OutColor.a = 1;
}
#endif