You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Image exposure is locally adjusted by decomposing luminance of the frame into a base layer and a detail layer. - Base layer contrast can be reduced while preserving detail. - Support blending with gaussian blurred luminance as proposed in Advances in Real-Time Rendering in Games 2021. - Controls in PostProcessVolume. - r.LocalExposure cvar to control whether local exposure is supported. - Visualization mode in "Show->Visualize->Local Expsoure". Multiple modes controllable using r.LocalExposure.VisualizeDebugMode. - Bilateral grid generation not yet optimized. Optimization should include increasing depth of grid to match histogram size so that global histogram can be generated from grid. - Added option to use mirror address mode in PostProcessWeightedSampleSum, since black border adds noticeable vignetting to the blurred luminance. #preflight 612f65169db3090001ba3eec #rb Brian.Karis, Guillaume.Abadie, Krzysztof.Narkowicz #ROBOMERGE-SOURCE: CL 17385317 via CL 17387198 #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v865-17346139) [CL 17387234 by tiago costa in ue5-release-engine-test branch]
238 lines
8.5 KiB
Plaintext
238 lines
8.5 KiB
Plaintext
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
PostProcessHistogramCommon.usf: PostProcessing histogram shared functions and structures.
|
|
=============================================================================*/
|
|
|
|
float EyeAdaptation_ExposureLowPercent;
|
|
float EyeAdaptation_ExposureHighPercent;
|
|
float EyeAdaptation_MinAverageLuminance;
|
|
float EyeAdaptation_MaxAverageLuminance;
|
|
float EyeAdaptation_ExposureCompensationSettings;
|
|
float EyeAdaptation_ExposureCompensationCurve;
|
|
float EyeAdaptation_DeltaWorldTime;
|
|
float EyeAdaptation_ExposureSpeedUp;
|
|
float EyeAdaptation_ExposureSpeedDown;
|
|
float EyeAdaptation_HistogramScale;
|
|
float EyeAdaptation_HistogramBias;
|
|
float EyeAdaptation_LuminanceMin;
|
|
float EyeAdaptation_LocalExposureContrastReduction;
|
|
float EyeAdaptation_LocalExposureDetailStrength;
|
|
float EyeAdaptation_LocalExposureBlurredLuminanceBlend;
|
|
float EyeAdaptation_LocalExposureMiddleGreyExposureCompensation;
|
|
float EyeAdaptation_BlackHistogramBucketInfluence;
|
|
float EyeAdaptation_GreyMult;
|
|
float EyeAdaptation_ExponentialUpM;
|
|
float EyeAdaptation_ExponentialDownM;
|
|
float EyeAdaptation_StartDistance;
|
|
float EyeAdaptation_LuminanceMax;
|
|
float EyeAdaptation_ForceTarget;
|
|
int EyeAdaptation_VisualizeDebugType;
|
|
Texture2D EyeAdaptation_MeterMaskTexture;
|
|
SamplerState EyeAdaptation_MeterMaskSampler;
|
|
|
|
float CalculateEyeAdaptationLuminance(float3 Color)
|
|
{
|
|
return max(dot(Color, float3(1.0f, 1.0f, 1.0f) / 3.0f), EyeAdaptation_LuminanceMin);
|
|
}
|
|
|
|
// inverse of ComputeLogLuminanceFromHistogramPosition
|
|
// @param LogLuminance
|
|
// @return HistogramPosition 0..1
|
|
float ComputeHistogramPositionFromLogLuminance(float LogLuminance)
|
|
{
|
|
return LogLuminance * EyeAdaptation_HistogramScale + EyeAdaptation_HistogramBias;
|
|
}
|
|
|
|
// inverse of ComputeLuminanceFromHistogramPosition
|
|
// @param Luminance
|
|
// @return HistogramPosition 0..1
|
|
float ComputeHistogramPositionFromLuminance(float Luminance)
|
|
{
|
|
return ComputeHistogramPositionFromLogLuminance(log2(Luminance));
|
|
}
|
|
|
|
// inverse of ComputeHistogramPositionFromLogLuminance()
|
|
// @param HistogramPosition 0..1
|
|
// @return LogLuminance
|
|
float ComputeLogLuminanceFromHistogramPosition(float HistogramPosition)
|
|
{
|
|
return ((HistogramPosition - EyeAdaptation_HistogramBias) / EyeAdaptation_HistogramScale);
|
|
}
|
|
|
|
// inverse of ComputeHistogramPositionFromLuminance()
|
|
// @param HistogramPosition 0..1
|
|
// @return Luminance
|
|
float ComputeLuminanceFromHistogramPosition(float HistogramPosition)
|
|
{
|
|
return exp2(ComputeLogLuminanceFromHistogramPosition(HistogramPosition));
|
|
}
|
|
|
|
#ifndef HISTOGRAM_SIZE
|
|
#define HISTOGRAM_SIZE 64
|
|
#endif
|
|
|
|
#define HISTOGRAM_TEXEL_SIZE (HISTOGRAM_SIZE / 4)
|
|
|
|
float4 ComputeARGBStripeMaskInt(uint x)
|
|
{
|
|
return float4(
|
|
(x % 4) == 0,
|
|
(x % 4) == 1,
|
|
(x % 4) == 2,
|
|
(x % 4) == 3);
|
|
}
|
|
|
|
float GetHistogramBucket(Texture2D HistogramTexture, uint BucketIndex)
|
|
{
|
|
uint Texel = BucketIndex / 4;
|
|
|
|
float4 HistogramColor = HistogramTexture.Load(int3(Texel, 0, 0));
|
|
|
|
uint channel = BucketIndex % 4;
|
|
float UnweightedValue = HistogramColor.r;
|
|
UnweightedValue = (channel == 1) ? HistogramColor.g : UnweightedValue;
|
|
UnweightedValue = (channel == 2) ? HistogramColor.b : UnweightedValue;
|
|
UnweightedValue = (channel == 3) ? HistogramColor.a : UnweightedValue;
|
|
|
|
return UnweightedValue;
|
|
}
|
|
|
|
float ComputeHistogramSum(Texture2D HistogramTexture)
|
|
{
|
|
float Sum = 0;
|
|
|
|
for(uint i = 0; i < HISTOGRAM_SIZE; ++i)
|
|
{
|
|
Sum += GetHistogramBucket(HistogramTexture, i);
|
|
}
|
|
|
|
return Sum;
|
|
}
|
|
|
|
// @param MinFractionSum e.g. ComputeHistogramSum() * 0.5f for 50% percentil
|
|
// @param MaxFractionSum e.g. ComputeHistogramSum() * 0.9f for 90% percentil
|
|
float ComputeAverageLuminanceWithoutOutlier(Texture2D HistogramTexture, float MinFractionSum, float MaxFractionSum)
|
|
{
|
|
float2 SumWithoutOutliers = 0;
|
|
|
|
UNROLL for(uint i = 0; i < HISTOGRAM_SIZE; ++i)
|
|
{
|
|
float LocalValue = GetHistogramBucket(HistogramTexture, i);
|
|
|
|
// remove outlier at lower end
|
|
float Sub = min(LocalValue, MinFractionSum);
|
|
LocalValue = LocalValue - Sub;
|
|
MinFractionSum -= Sub;
|
|
MaxFractionSum -= Sub;
|
|
|
|
// remove outlier at upper end
|
|
LocalValue = min(LocalValue, MaxFractionSum);
|
|
MaxFractionSum -= LocalValue;
|
|
|
|
float LogLuminanceAtBucket = ComputeLogLuminanceFromHistogramPosition(float(i) / (float)(HISTOGRAM_SIZE-1));
|
|
|
|
SumWithoutOutliers += float2(LogLuminanceAtBucket, 1) * LocalValue;
|
|
}
|
|
|
|
float AvgLogLuminance = SumWithoutOutliers.x / max(0.0001f, SumWithoutOutliers.y);
|
|
|
|
return exp2(AvgLogLuminance);
|
|
}
|
|
|
|
float ComputeEyeAdaptationExposure(Texture2D HistogramTexture)
|
|
{
|
|
const float HistogramSum = ComputeHistogramSum(HistogramTexture);
|
|
const float AverageSceneLuminance = ComputeAverageLuminanceWithoutOutlier(HistogramTexture, HistogramSum * EyeAdaptation_ExposureLowPercent, HistogramSum * EyeAdaptation_ExposureHighPercent);
|
|
const float LumAve = AverageSceneLuminance;
|
|
|
|
const float ClampedLumAve = LumAve;
|
|
|
|
// No longer clamping here. We are letting the target exposure be outside the min/max range, and then letting the exposure
|
|
// gradually transion.
|
|
return ClampedLumAve;
|
|
}
|
|
|
|
float AdaptationWeightTexture(float2 UV)
|
|
{
|
|
return Texture2DSampleLevel(EyeAdaptation_MeterMaskTexture, EyeAdaptation_MeterMaskSampler, UV, 0).x;
|
|
}
|
|
|
|
float ExponentialAdaption(float Current, float Target, float FrameTime, float AdaptionSpeed, float M)
|
|
{
|
|
const float Factor = 1.0f - exp2(-FrameTime * AdaptionSpeed);
|
|
const float Value = Current + (Target - Current) * Factor * M;
|
|
return Value;
|
|
}
|
|
|
|
float LinearAdaption(float Current, float Target, float FrameTime, float AdaptionSpeed)
|
|
{
|
|
const float Offset = FrameTime * AdaptionSpeed;
|
|
|
|
const float Value = (Current < Target) ? min(Target, Current + Offset) : max(Target, Current - Offset);
|
|
|
|
return Value;
|
|
}
|
|
|
|
float ComputeEyeAdaptation(float OldExposure, float TargetExposure, float FrameTime)
|
|
{
|
|
const float LogTargetExposure = log2(TargetExposure);
|
|
const float LogOldExposure = log2(OldExposure);
|
|
|
|
const float LogDiff = LogTargetExposure - LogOldExposure;
|
|
|
|
const float AdaptionSpeed = (LogDiff > 0) ? EyeAdaptation_ExposureSpeedUp : EyeAdaptation_ExposureSpeedDown;
|
|
const float M = (LogDiff > 0) ? EyeAdaptation_ExponentialUpM : EyeAdaptation_ExponentialDownM;
|
|
|
|
const float AbsLogDiff = abs(LogDiff);
|
|
|
|
// blended exposure
|
|
const float LogAdaptedExposure_Exponential = ExponentialAdaption(LogOldExposure, LogTargetExposure, FrameTime, AdaptionSpeed, M);
|
|
const float LogAdaptedExposure_Linear = LinearAdaption(LogOldExposure, LogTargetExposure, FrameTime, AdaptionSpeed);
|
|
|
|
const float LogAdaptedExposure = AbsLogDiff > EyeAdaptation_StartDistance ? LogAdaptedExposure_Linear : LogAdaptedExposure_Exponential;
|
|
|
|
// Note: no clamping here. The target exposure should always be clamped so if we are below the min or above the max,
|
|
// instead of clamping, we will gradually transition to the target exposure. If were to clamp, the then we would have a harsh transition
|
|
// when going from postFX volumes with different min/max luminance values.
|
|
const float AdaptedExposure = exp2(LogAdaptedExposure);
|
|
|
|
// for manual mode or camera cuts, just lerp to the target
|
|
const float AdjustedExposure = lerp(AdaptedExposure,TargetExposure,EyeAdaptation_ForceTarget);
|
|
|
|
return AdjustedExposure;
|
|
}
|
|
|
|
#define BILATERAL_GRID_DEPTH 32
|
|
|
|
float CalculateBaseLogLuminance(float BilateralLum, float BlurredLum, float BlurredLumBlend, float ExposureScale)
|
|
{
|
|
return lerp(BilateralLum, BlurredLum, BlurredLumBlend) + log2(ExposureScale);
|
|
}
|
|
|
|
float CalculateBaseLogLuminance(float LogLum, float BlurredLumBlend, float ExposureScale, float2 UV, Texture3D LumBilateralGrid, Texture2D BlurredLogLum, SamplerState LumBilateralGridSampler, SamplerState BlurredLogLumSampler)
|
|
{
|
|
float LumW = (ComputeHistogramPositionFromLogLuminance(LogLum) * (BILATERAL_GRID_DEPTH - 1) + 0.5f) / BILATERAL_GRID_DEPTH;
|
|
float2 BilateralGridLum = Texture3DSample(LumBilateralGrid, LumBilateralGridSampler, float3(UV, LumW)).xy;
|
|
float BilateralLum = BilateralGridLum.x / BilateralGridLum.y;
|
|
|
|
float BlurredLum = Texture2DSample(BlurredLogLum, BlurredLogLumSampler, UV).r;
|
|
|
|
if (BilateralGridLum.y < 0.001)
|
|
{
|
|
// fallback to blurred luminance if bilateral grid doesn't have data
|
|
// this can happen since grid is populated using half resolution image
|
|
BilateralLum = BlurredLum;
|
|
}
|
|
|
|
return CalculateBaseLogLuminance(BilateralLum, BlurredLum, BlurredLumBlend, ExposureScale);
|
|
}
|
|
|
|
float CalculateLocalExposure(float LogLum, float BaseLogLum, float LogMiddleGrey, float ContrastReduction, float DetailStrength)
|
|
{
|
|
float DetailLogLum = LogLum - BaseLogLum;
|
|
|
|
float LogLocalLum = LogMiddleGrey + (BaseLogLum - LogMiddleGrey) * ContrastReduction + DetailLogLum * DetailStrength;
|
|
return exp2(LogLocalLum - LogLum);
|
|
}
|