Files
UnrealEngineUWP/Engine/Shaders/Private/PostProcessHistogram.usf
tiago costa b009886be2 Local Exposure using Bilaterial Grid based Contrast Reduction.
- 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]
2021-09-01 11:22:04 -04:00

169 lines
5.6 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessHistogram.usf: PostProcessing histogram
=============================================================================*/
#include "Common.ush"
#include "ScreenPass.ush"
#include "PostProcessHistogramCommon.ush"
SCREEN_PASS_TEXTURE_VIEWPORT(Input)
Texture2D InputTexture;
#if BILATERAL_GRID
// Output bilateral grid texture (UAV)
RWTexture3D<float2> BilateralGridRWTexture;
#else
// Output histogram texture (UAV)
RWTexture2D<float4> HistogramRWTexture;
#endif
// Number of thread groups in the dispatch
uint2 ThreadGroupCount;
// THREADGROUP_SIZEX*THREADGROUP_SIZEY histograms of the size HISTOGRAM_SIZE
groupshared float SharedHistogram[HISTOGRAM_SIZE][THREADGROUP_SIZEX][THREADGROUP_SIZEY];
#if BILATERAL_GRID
groupshared float SharedLuminanceSum[HISTOGRAM_SIZE][THREADGROUP_SIZEX][THREADGROUP_SIZEY];
#endif
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void MainCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID,
uint GroupIndex: SV_GroupIndex)
{
// todo: can be cleared more efficiently
// clear all THREADGROUP_SIZEX*THREADGROUP_SIZEY histograms
UNROLL for (uint i = 0; i < HISTOGRAM_SIZE; ++i)
{
SharedHistogram[i][GroupThreadId.x][GroupThreadId.y] = 0.0f;
#if BILATERAL_GRID
SharedLuminanceSum[i][GroupThreadId.x][GroupThreadId.y] = 0.0f;
#endif
}
GroupMemoryBarrierWithGroupSync();
// Each thread group processes LoopX * LoopY texels of the input.
uint2 TileSize = uint2(LOOP_SIZEX, LOOP_SIZEY);
// Top left input texel for this group.
uint2 LeftTop = Input_ViewportMin + DispatchThreadId.xy * TileSize;
float2 InvViewSize = Input_ViewportSizeInverse.xy;
// Accumulate all pixels into THREADGROUP_SIZEX*THREADGROUP_SIZEY histograms
LOOP for (uint y = 0; y < LOOP_SIZEY; ++y)
{
LOOP for (uint x = 0; x < LOOP_SIZEX; ++x)
{
uint2 Tile = uint2(x, y);
uint2 TexelPos = LeftTop + Tile;
if(TexelPos.x < Input_ViewportMax.x && TexelPos.y < Input_ViewportMax.y)
{
float4 SceneColor = InputTexture.Load(int3(TexelPos, 0));
SceneColor.xyz *= View.OneOverPreExposure;
float LuminanceVal = CalculateEyeAdaptationLuminance(SceneColor.xyz);
float LogLuminance = log2(LuminanceVal);
float LogLuminanceHist = ComputeHistogramPositionFromLogLuminance(LogLuminance);
float2 ScreenUV = (TexelPos.xy - Input_ViewportMin) * InvViewSize;
float ScreenWeight = AdaptationWeightTexture(ScreenUV);
// Map the normalized histogram position into texels.
float fBucket = saturate(LogLuminanceHist) * (HISTOGRAM_SIZE-1);// * 0.9999f;
// Find two discrete buckets that straddle the continuous histogram position.
uint Bucket0 = (uint)(fBucket);
uint Bucket1 = Bucket0 + 1;
Bucket0 = min(Bucket0, uint(HISTOGRAM_SIZE - 1));
Bucket1 = min(Bucket1, uint(HISTOGRAM_SIZE - 1));
// Weighted blend between the two buckets.
float Weight1 = frac(fBucket);
float Weight0 = 1.0f - Weight1;
#if !BILATERAL_GRID
// When EyeAdaptation_BlackHistogramBucketInfluence=.0, we will ignore the last bucket. The main use
// case is so the black background pixels in the editor have no effect. But if we have cases where
// pixel values can actually be black, we want to set EyeAdaptation_LastHistogramBucketInfluence=1.0.
// This value is controlled by the cvar "r.EyeAdaptation.BlackHistogramBucketInfluence"
if (Bucket0 == 0)
{
Weight0 *= EyeAdaptation_BlackHistogramBucketInfluence;
}
Weight0 *= ScreenWeight;
Weight1 *= ScreenWeight;
#endif
// Accumulate the weight to the nearby history buckets.
SharedHistogram[Bucket0][GroupThreadId.x][GroupThreadId.y] += Weight0;
SharedHistogram[Bucket1][GroupThreadId.x][GroupThreadId.y] += Weight1;
#if BILATERAL_GRID
SharedLuminanceSum[Bucket0][GroupThreadId.x][GroupThreadId.y] += LogLuminance * Weight0;
SharedLuminanceSum[Bucket1][GroupThreadId.x][GroupThreadId.y] += LogLuminance * Weight1;
#endif
}
}
}
GroupMemoryBarrierWithGroupSync();
// Accumulate all histograms into one.
if (GroupIndex < HISTOGRAM_SIZE / 4)
{
float4 Sum = 0;
#if BILATERAL_GRID
float4 SumLuminance = 0;
#endif
LOOP for (uint y = 0; y < THREADGROUP_SIZEY; ++y)
{
LOOP for (uint x = 0; x < THREADGROUP_SIZEX; ++x)
{
Sum += float4(
SharedHistogram[GroupIndex * 4 + 0][x][y],
SharedHistogram[GroupIndex * 4 + 1][x][y],
SharedHistogram[GroupIndex * 4 + 2][x][y],
SharedHistogram[GroupIndex * 4 + 3][x][y]);
#if BILATERAL_GRID
SumLuminance += float4(
SharedLuminanceSum[GroupIndex * 4 + 0][x][y],
SharedLuminanceSum[GroupIndex * 4 + 1][x][y],
SharedLuminanceSum[GroupIndex * 4 + 2][x][y],
SharedLuminanceSum[GroupIndex * 4 + 3][x][y]);
#endif
}
}
#if BILATERAL_GRID
BilateralGridRWTexture[uint3(GroupId.xy, GroupIndex * 4 + 0)] = float2(SumLuminance.x, Sum.x);
BilateralGridRWTexture[uint3(GroupId.xy, GroupIndex * 4 + 1)] = float2(SumLuminance.y, Sum.y);
BilateralGridRWTexture[uint3(GroupId.xy, GroupIndex * 4 + 2)] = float2(SumLuminance.z, Sum.z);
BilateralGridRWTexture[uint3(GroupId.xy, GroupIndex * 4 + 3)] = float2(SumLuminance.w, Sum.w);
#else
float2 MaxExtent = Input_ViewportSize;
float Area = MaxExtent.x * MaxExtent.y;
// Fixed to include borders.
float NormalizeFactor = 1.0f / Area;
// Output texture with one histogram per line, x and y unwrapped into all the lines
HistogramRWTexture[uint2(GroupIndex, GroupId.x + GroupId.y * ThreadGroupCount.x)] = Sum * NormalizeFactor;
#endif
}
}