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

571 lines
18 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Common.ush"
#include "ScreenPass.ush"
#include "PostProcessCommon.ush"
#include "PostProcessHistogramCommon.ush"
#include "DeferredShadingCommon.ush"
#include "TonemapCommon.ush"
#include "MiniFontCommon.ush"
#include "ColorSpace.ush"
// the prior frame's eye adaptation settings
StructuredBuffer<float4> EyeAdaptationBuffer;
Texture2D SceneColorTexture;
SamplerState SceneColorSampler;
Texture2D HDRSceneColorTexture;
SamplerState HDRSceneColorSampler;
FScreenTransform ColorUVToLuminanceUV;
Texture2D LuminanceTexture;
SamplerState LuminanceSampler;
Texture2D HistogramTexture;
SCREEN_PASS_TEXTURE_VIEWPORT(Input)
SCREEN_PASS_TEXTURE_VIEWPORT(Output)
float IlluminanceMeterEnabled;
float UsingIlluminance;
float LuminanceScale;
// only needed for nice visualization
float ComputeHistogramMax(Texture2D HistogramTexture)
{
float Max = 0;
for(uint i = 0; i < HISTOGRAM_SIZE; ++i)
{
Max = max(Max, GetHistogramBucket(HistogramTexture, i));
}
return Max;
}
float ComputePlaceInHistogram(float3 HDRColor)
{
float LogLuminance = log2(dot(HDRColor,float3(1.0f,1.0f,1.0f))/3.0f);
float SumTotal = 0.0;
float SumBelow = 0.0f;
for(uint i = 0; i < HISTOGRAM_SIZE; ++i)
{
float Weight = GetHistogramBucket(HistogramTexture, i);
float LogLuminanceLow = ComputeLogLuminanceFromHistogramPosition(float(i) / (float)(HISTOGRAM_SIZE-1));
float LogLuminanceHigh = ComputeLogLuminanceFromHistogramPosition(float(i+1) / (float)(HISTOGRAM_SIZE-1));
// if log luminace is greater than high, it's 1.0.
// if log luminace is lower than low, it's 0.0
// else it's a lerp between
float T = saturate((LogLuminance - LogLuminanceLow)/(LogLuminanceHigh - LogLuminanceLow));
SumBelow += Weight * T;
SumTotal += Weight;
}
float Percentile = SumBelow/SumTotal;
return Percentile;
}
bool InUnitBox(float2 UV)
{
return UV.x >= 0 && UV.y >= 0 && UV.y < 1 && UV.y < 1;
}
// @param x 0=cold..1=hot
float3 Colorize(float x)
{
x = saturate(x);
float3 Heat = float3(1.0f, 0.0f, 0.0f);
float3 Middle = float3(0.0f, 1.0f, 0.0f);
float3 Cold = float3(0.0f, 0.0f, 1.0f);
float3 ColdHeat = lerp(Cold, Heat, x);
return lerp(Middle, ColdHeat, abs(0.5f - x) * 2);
}
// for printf debugging in the shader, does not have to be positive
// outputs a float number in the form: xx.yy
// @param LeftTop - in pixels
void PrintSmallFloatWithSign(int2 PixelPos, inout float3 OutColor, float3 FontColor, int2 LeftTop, float Number)
{
int2 Cursor = LeftTop;
bool bHasSign = Number < 0;
// Minus Sign
Number = abs(Number) + 0.005; // Round up first digit
bool bIsGreater10 = Number >= 10;
FLATTEN
if (bHasSign && bIsGreater10)
{
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, _MINUS_);
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 10));
Number = abs(Number);
}
else if (bIsGreater10)
{
Cursor.x += 8;
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 10));
}
else if (bHasSign)
{
Cursor.x += 8;
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, _MINUS_);
}
else
{
Cursor.x += 2*8;
}
// we always print this character, so no ifs needed
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 1));
// period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, _DOT_);
// after period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 0.1));
// after period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 0.01));
}
// for printf debugging in the shader, does not have to be positive
// outputs a float number in the form: xx.yyy
// @param LeftTop - in pixels
void PrintMediumFloatWithSign(int2 PixelPos, inout float3 OutColor, float3 FontColor, int2 LeftTop, float Number)
{
int2 Cursor = LeftTop;
bool bHasSign = Number < 0;
// Minus Sign
Number = abs(Number) + 0.005; // Round up first digit
bool bIsGreater10 = Number >= 10;
FLATTEN
if (bHasSign && bIsGreater10)
{
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, _MINUS_);
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 10));
Number = abs(Number);
}
else if (bIsGreater10)
{
Cursor.x += 8;
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 10));
}
else if (bHasSign)
{
Cursor.x += 8;
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, _MINUS_);
}
else
{
Cursor.x += 2*8;
}
// we always print this character, so no ifs needed
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 1));
// period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, _DOT_);
// after period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 0.1));
// after period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 0.01));
// after period
PrintCharacter(PixelPos, OutColor, FontColor, Cursor, ExtractDigitFromFloat(Number, 0.001));
}
//
void MainPS(noperspective float4 UVAndScreenPos : TEXCOORD0, float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScreenPos.xy;
int2 PixelPos = (int2)SvPosition.xy;
float2 ViewLocalUV = float2(UVAndScreenPos.z * 0.5f + 0.5f, 0.5f - 0.5f * UVAndScreenPos.w);
// retrieve the exposure scale and target exposure scale from the eye-adaptation buffer.
float EyeAdaptationResult = EyeAdaptationBuffer[0].r;
float EyeAdaptationTarget = EyeAdaptationBuffer[0].g;
float EyeAdaptationAverageSceneLuminance = EyeAdaptationBuffer[0].z;
float EyeAdaptationExposureBiasOnly = EyeAdaptationBuffer[0].w;
float EyeAdaptationResultWithoutExposureBias = EyeAdaptationResult / EyeAdaptationExposureBiasOnly;
int2 OutputViewportCenter = (int2)(Output_ViewportMin + Output_ViewportSize / 2);
float2 UVViewportCenter = (Input_UVViewportMin + Input_UVViewportSize / 2);
const float3 NumberColor = float3(0.8, 0.85, 0.85);
// Luminance meter at the center of the screen
{
const float PixelDx = abs(PixelPos.x - OutputViewportCenter.x);
const float PixelDy = abs(PixelPos.y - OutputViewportCenter.y);
if (PixelDx < 180 && PixelDy < 42)
{
float3 HDRSceneColorAvg = 0.0f;
float3 SceneColorAvg = 0.0f;
float SampleCount = 0.0f;
float MeterSize = 10.0f;
for (float OffsetX = -MeterSize; OffsetX <= MeterSize; OffsetX++)
{
for (float OffsetY = -MeterSize; OffsetY <= MeterSize; OffsetY++)
{
float2 UV = UVViewportCenter + float2(OffsetX, OffsetY) * Input_ExtentInverse;
HDRSceneColorAvg += Texture2DSample(HDRSceneColorTexture, HDRSceneColorSampler, UV).rgb;
SceneColorAvg += Texture2DSample(SceneColorTexture, SceneColorSampler, UV).rgb;
SampleCount++;
}
}
HDRSceneColorAvg = HDRSceneColorAvg / SampleCount;
SceneColorAvg = SceneColorAvg / SampleCount;
HDRSceneColorAvg *= View.OneOverPreExposure;
if (PixelDx < MeterSize && PixelDy < MeterSize)
{
if (PixelDx == MeterSize-1 || PixelDy == MeterSize-1)
{
OutColor = float4(1.0, 1.0, 1.0, 1.0); // White border
}
else
{
OutColor = float4(SceneColorAvg, 1.0); // Inner visor average color
}
}
else
{
OutColor = Texture2DSample(SceneColorTexture, SceneColorSampler, UV); // Influenced area default scene color
}
float LuminanceAvg = Luminance(HDRSceneColorAvg);
// Luminance
int2 TopLeft = OutputViewportCenter + int2(11, 11);
if (LuminanceAvg < 1000.0f)
{
PrintFloat(PixelPos, OutColor.xyz, NumberColor, TopLeft, LuminanceAvg);
TopLeft.x += 60;
}
else if (LuminanceAvg < 999999.0f)
{
PrintFloatNoFractionLarge(PixelPos, OutColor.xyz, NumberColor, TopLeft, LuminanceAvg);
TopLeft.x += 60;
}
else
{
PrintFloatNoFraction(PixelPos, OutColor.xyz, NumberColor, TopLeft, LuminanceAvg, 10);
TopLeft.x += 100;
}
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _N_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _I_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _T_);
// EV100
TopLeft = OutputViewportCenter + int2(11, 22);
const float EyeAdaptationEV100 = log2((LuminanceAvg/.18)/EyeAdaptation_LuminanceMax);
PrintMediumFloatWithSign(PixelPos, OutColor.xyz, NumberColor, TopLeft, EyeAdaptationEV100);
TopLeft.x += 60;
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _E_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _V_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _1_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _0_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _0_);
return;
}
}
// Illuminance meter for the hemisphere in front of the camera
if(IlluminanceMeterEnabled > 0.0f)
{
OutColor = float4(0.0f, 0.0f, 0.0f, 0.0f);
// IlluminanceMeter variables must match the one in DebugProbes.usf
const float IlluminanceMeterSize = 20;
const float IlluminanceMeterHalfSize = IlluminanceMeterSize / 2;
const float2 IlluminanceMeterCenter = OutputViewportCenter - uint2(0, 100);
const float2 IlluminanceMeterCenterUV = IlluminanceMeterCenter * Input_ExtentInverse;
const float PixelDx = PixelPos.x - IlluminanceMeterCenter.x;
const float PixelDy = PixelPos.y - IlluminanceMeterCenter.y;
const float AbsPixelDx = abs(PixelDx);
const float AbsPixelDy = abs(PixelDy);
if (PixelDx > -20 && PixelDx < 300 && PixelDy < 40 && PixelDy > -20)
{
float3 HDRSceneColorAvg = 0.0f;
float SampleCount = 0.0f;
LOOP
for (float OffsetX = (-IlluminanceMeterHalfSize + 2); OffsetX < (IlluminanceMeterHalfSize-2); OffsetX++)
{
for (float OffsetY = (-IlluminanceMeterHalfSize + 2); OffsetY < (IlluminanceMeterHalfSize-2); OffsetY++)
{
float2 UV = IlluminanceMeterCenterUV + float2(OffsetX, OffsetY) * Input_ExtentInverse;
HDRSceneColorAvg += Texture2DSampleLevel(HDRSceneColorTexture, HDRSceneColorSampler, UV, 0).rgb;
SampleCount++;
}
}
HDRSceneColorAvg = HDRSceneColorAvg / SampleCount;
HDRSceneColorAvg *= View.OneOverPreExposure;
OutColor = Texture2DSample(SceneColorTexture, SceneColorSampler, UV); // Influenced area default scene color
const int IlluminanceMeterHalfSizeInt = int(IlluminanceMeterHalfSize);
const int AbsPixelDxInt = int(AbsPixelDx);
const int AbsPixelDyInt = int(AbsPixelDy);
if (AbsPixelDxInt < IlluminanceMeterHalfSizeInt && AbsPixelDyInt < IlluminanceMeterHalfSizeInt)
{
if (AbsPixelDxInt == (IlluminanceMeterHalfSizeInt - 1) || AbsPixelDyInt == (IlluminanceMeterHalfSizeInt-1))
{
OutColor = float4(1.0, 1.0, 1.0, 1.0); // White border
}
}
float3 Illuminance = HDRSceneColorAvg * PI;
float MeanIlluminance = Luminance(Illuminance);
int2 TopLeft = IlluminanceMeterCenter + int2(11, 11);
// Mean illuminance
if (MeanIlluminance < 1000.0f)
{
PrintFloat(PixelPos, OutColor.xyz, NumberColor, TopLeft, MeanIlluminance);
TopLeft.x += 60;
}
else if (MeanIlluminance < 999999.0f)
{
PrintFloatNoFractionLarge(PixelPos, OutColor.xyz, NumberColor, TopLeft, MeanIlluminance);
TopLeft.x += 60;
}
else
{
PrintFloatNoFraction(PixelPos, OutColor.xyz, NumberColor, TopLeft, MeanIlluminance, 10);
TopLeft.x += 100;
}
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _L_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _U_);
PrintCharacter(PixelPos, OutColor.xyz, NumberColor, TopLeft, _X_);
// Illuminance as RGB
TopLeft = IlluminanceMeterCenter + int2(11, 22);
if (Illuminance.r < 1000.0f)
{
PrintFloat(PixelPos, OutColor.xyz, float3(1, 0.5, 0.5), TopLeft, Illuminance.r);
}
else if (Illuminance.r < 999999.0f)
{
PrintFloatNoFractionLarge(PixelPos, OutColor.xyz, float3(1, 0.5, 0.5), TopLeft, Illuminance.r);
}
else
{
PrintFloatNoFraction(PixelPos, OutColor.xyz, NumberColor, TopLeft, Illuminance.r, 10);
}
TopLeft.x += 100;
if (Illuminance.g < 1000.0f)
{
PrintFloat(PixelPos, OutColor.xyz, float3(0.5, 1, 0.5), TopLeft, Illuminance.g);
}
else if (Illuminance.g < 999999.0f)
{
PrintFloatNoFractionLarge(PixelPos, OutColor.xyz, float3(0.5, 1, 0.5), TopLeft, Illuminance.g);
}
else
{
PrintFloatNoFraction(PixelPos, OutColor.xyz, NumberColor, TopLeft, Illuminance.g, 10);
}
TopLeft.x += 100;
if (Illuminance.b < 1000.0f)
{
PrintFloat(PixelPos, OutColor.xyz, float3(0.5, 0.5, 1), TopLeft, Illuminance.b);
}
else if (Illuminance.b < 999999.0f)
{
PrintFloatNoFractionLarge(PixelPos, OutColor.xyz, float3(0.5, 0.5, 1), TopLeft, Illuminance.b);
}
else
{
PrintFloatNoFraction(PixelPos, OutColor.xyz, NumberColor, TopLeft, Illuminance.b, 10);
}
return;
}
}
if (EyeAdaptation_VisualizeDebugType == 0)
{
OutColor = Texture2DSample(SceneColorTexture, SceneColorSampler, UV);
}
else if (EyeAdaptation_VisualizeDebugType == 1)
{
float4 SceneColor = Texture2DSample(HDRSceneColorTexture, HDRSceneColorSampler, UV);
SceneColor.xyz *= View.OneOverPreExposure;
float Percentile = ComputePlaceInHistogram(SceneColor.xyz);
OutColor.rgb = Percentile;
if (Percentile < EyeAdaptation_ExposureLowPercent)
{
OutColor.rgb = float3(.5,0,0);
}
else if (Percentile >= EyeAdaptation_ExposureHighPercent)
{
OutColor.rgb = float3(0,0,.5);
}
}
else if (EyeAdaptation_VisualizeDebugType == 2)
{
float Luminance;
if (UsingIlluminance > 0.5f)
{
float2 LuminanceUV = ApplyScreenTransform(UV, ColorUVToLuminanceUV);
Luminance = Texture2DSample(LuminanceTexture, LuminanceSampler, LuminanceUV).r;
Luminance *= View.OneOverPreExposure;
}
else
{
float4 SceneColor = Texture2DSample(HDRSceneColorTexture, HDRSceneColorSampler, UV);
SceneColor.xyz *= View.OneOverPreExposure;
Luminance = CalculateEyeAdaptationLuminance(SceneColor.xyz);
}
OutColor.rgb = Luminance * LuminanceScale;
}
else // should never happen?
{
OutColor.rgb = float3(0,0,0);
}
// Compute and apply weight value at this location
float2 NdPos = Output_ViewportSizeInverse * (PixelPos.xy - Output_ViewportMin);
float weight = AdaptationWeightTexture(NdPos);
float2 IDAreaLocalUV = ViewLocalUV * 2 + float2(-1, 0);
// left top of the border
const int2 HistogramLeftTop = int2(Output_ViewportMin.x + 64, Output_ViewportMax.y - 128 - 32);
const int2 HistogramSize = int2(Output_ViewportMax.x - Output_ViewportMin.x - 64 * 2, 128);
const int HistogramOuterBorder = 4;
// (0, 0) .. (1, 1)
float2 InsetPx = PixelPos - HistogramLeftTop;
float2 InsetUV = InsetPx / HistogramSize;
const float3 BorderColor = Colorize(InsetUV.x);
float BorderDistance = ComputeDistanceToRect(PixelPos, HistogramLeftTop, HistogramSize);
// thin black border around the histogram
OutColor.xyz = lerp(float3(0, 0, 0), OutColor.xyz, saturate(BorderDistance - (HistogramOuterBorder + 2)));
// big solid border around the histogram
OutColor.xyz = lerp(BorderColor, OutColor.xyz, saturate(BorderDistance - (HistogramOuterBorder + 1)));
// thin black border around the histogram
OutColor.xyz = lerp(float3(0, 0, 0), OutColor.xyz, saturate(BorderDistance - 1));
if(BorderDistance > 0)
{
// outside of the histogram
return;
}
// inside the histogram
uint Bucket = (uint)(InsetUV.x * HISTOGRAM_SIZE);
float HistogramSum = ComputeHistogramSum(HistogramTexture);
float MinExposure = EyeAdaptation_MinAverageLuminance / 0.18;
float MaxExposure = EyeAdaptation_MaxAverageLuminance / 0.18;
if(InsetUV.x < ComputeHistogramPositionFromLuminance(MinExposure))
{
// < min: grey
OutColor.xyz = lerp(OutColor.xyz, float3(0.5f, 0.5f, 0.5f), 0.5f);
}
else if(InsetUV.x < ComputeHistogramPositionFromLuminance(MaxExposure))
{
// >= min && < max: green
OutColor.xyz = lerp(OutColor.xyz, float3(0.5f, 0.8f, 0.5f), 0.5f);
}
else
{
// >= max: grey
OutColor.xyz = lerp(OutColor.xyz, float3(0.5f, 0.5f, 0.5f), 0.5f);
}
float LocalHistogramValue = GetHistogramBucket(HistogramTexture, Bucket) / ComputeHistogramMax(HistogramTexture);
if(LocalHistogramValue >= 1 - InsetUV.y)
{
// histogram bars
OutColor.xyz = lerp(OutColor.xyz, Colorize(InsetUV.x), 0.5f);
}
{
// HDR luminance >0
float LuminanceVal = ComputeLuminanceFromHistogramPosition(InsetUV.x);
// HDR > 0
float3 AdpatedLuminance = EyeAdaptationResult * float3(LuminanceVal, LuminanceVal, LuminanceVal);
// 0..1
float3 TonemappedLuminance = FilmToneMap(AdpatedLuminance);
float3 DistMask = saturate(1.0 - 100.0 * abs(TonemappedLuminance - (1.0 - InsetUV.y)));
OutColor = lerp(OutColor, float4(1, 1, 1, 0), float4(DistMask, 0.0));
}
{
float ValuePx = ComputeHistogramPositionFromLuminance(EyeAdaptationAverageSceneLuminance/.18f) * HistogramSize.x ;
if(abs(InsetPx.x - ValuePx) < 3)
{
// blue line to show the clamped percentil
OutColor = lerp(OutColor, float4(0, 0, 1, 0), 0.5f);
}
}
// eye adaptation without bias
{
const float OneOverEyeAdaptationResult = 1.0f/EyeAdaptationResultWithoutExposureBias;
const float EyeAdaptationValue = ComputeHistogramPositionFromLuminance(OneOverEyeAdaptationResult);
const float ValuePx = EyeAdaptationValue * HistogramSize.x;
const float EyeAdaptationEV100 = log2(OneOverEyeAdaptationResult);
PrintSmallFloatWithSign(PixelPos, OutColor.xyz, float3(1.0, 0.0, 1.0), HistogramLeftTop + int2(ValuePx + - 6 * 8 - 3, 1), EyeAdaptationEV100);
if(abs(InsetPx.x - ValuePx) < 2 && PixelPos.y > HistogramLeftTop.y + 9)
{
// white line to show the smoothed exposure
OutColor = lerp(OutColor, float4(0.5, 0, .5, 0), 1.0f);
}
}
// eye adaptation
{
const float OneOverEyeAdaptationResult = 1.0f / EyeAdaptationResult;
const float EyeAdaptationValue = ComputeHistogramPositionFromLuminance(OneOverEyeAdaptationResult);
const float ValuePx = EyeAdaptationValue * HistogramSize.x;
const float EyeAdaptationEV100 = log2(OneOverEyeAdaptationResult/EyeAdaptation_LuminanceMax);
PrintSmallFloatWithSign(PixelPos, OutColor.xyz, float3(1, 1, 1), HistogramLeftTop + int2(ValuePx + - 6 * 8 - 3, 1), EyeAdaptationEV100);
if(abs(InsetPx.x - ValuePx) < 2 && PixelPos.y > HistogramLeftTop.y + 18)
{
// white line to show the smoothed exposure
OutColor = lerp(OutColor, float4(1, 1, 1, 0), 1.0f);
}
}
return;
}