// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. /*============================================================================= PostProcessHistogramCommon.usf: PostProcessing histogram shared functions and structures. =============================================================================*/ // [0] .x:ExposureLowPercent/100, .y:EyeAdaptationHighPercent/100, .z:EyeAdaptationMin, .w:EyeAdaptationMax // [1] .x:exp2(ExposureOffset), .y:DeltaWorldTime, .zw: EyeAdaptionSpeedUp/Down // [2] .x:Histogram multiply, .y:Histogram add, .zw:unused float4 EyeAdaptationParams[3]; // inverse of ComputeLuminanceFromHistogramPosition // is executed more often than ComputeLuminanceFromHistogramPosition() // @param Luminance // @return HistogramPosition 0..1 float ComputeHistogramPositionFromLuminance(float Luminance) { return log2(Luminance) * EyeAdaptationParams[2].x + EyeAdaptationParams[2].y; } // inverse of ComputeHistogramPositionFromLuminance() // is not as often executed as ComputeHistogramPositionFromLuminance() // @param HistogramPosition 0..1 // @return Lumiance float ComputeLuminanceFromHistogramPosition(float HistogramPosition) { return exp2( (HistogramPosition - EyeAdaptationParams[2].y) / EyeAdaptationParams[2].x ); } #define HISTOGRAM_SIZE 64 #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)); // WAR for a GLSL shader compiler bug in the driver #if 0 float UnweightedValue = dot(HistogramColor, ComputeARGBStripeMaskInt(BucketIndex)); #else 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; #endif // because of the logarithmic scale we need to weight the buckets ? // return UnweightedValue * pow(2, BucketIndex); // return UnweightedValue * pow(2, BucketIndex / HISTOGRAM_SIZE * (2 * HDRLogRadius)); 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 ComputeAverageLuminaneWithoutOutlier(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 LuminanceAtBucket = ComputeLuminanceFromHistogramPosition(i / (float)HISTOGRAM_SIZE); SumWithoutOutliers += float2(LuminanceAtBucket, 1) * LocalValue; } return SumWithoutOutliers.x / max(0.0001f, SumWithoutOutliers.y); } // used by bloom and tonemapper VS // requires EyeAdaptationParams to be set float ComputeEyeAdaptationExposure(Texture2D HistogramTexture) { float HistogramSum = ComputeHistogramSum(HistogramTexture); float UnclampedAdaptedLuminance = ComputeAverageLuminaneWithoutOutlier(HistogramTexture, HistogramSum * EyeAdaptationParams[0].x, HistogramSum * EyeAdaptationParams[0].y); float ClampedAdaptedLuminance = clamp(UnclampedAdaptedLuminance, EyeAdaptationParams[0].z, EyeAdaptationParams[0].w); return ClampedAdaptedLuminance; }