// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. /*============================================================================= PostProcessTemporalCommon.usf: Common Temporal AA Functionality ------------------------------------------------------------------------------- This is the common bits of the temporal AA shader which can be configured via defines for various temporal AA passes. =============================================================================*/ // 1 = Use tighter AABB clamp for history. // 0 = Use simple min/max clamp. #ifndef AA_AABB #define AA_AABB 1 #endif // 0 = Input doesn't have or need the alpha channel. Don't AA or preserve the alpha. Potentially use alpha channel of the output to improve quality. // Leverages dead code removal to work in RGB instead of RGBA. // 1 = Anti-alias the alpha channel also. #ifndef AA_ALPHA #define AA_ALPHA 0 #endif // Cross distance in pixels used in depth search X pattern. // 0 = Turn this feature off. // 2 = Is required for standard temporal AA pass. #ifndef AA_CROSS #define AA_CROSS 2 #endif // 1 = Use dynamic motion. // 0 = Skip dynamic motion, currently required for half resolution passes. #ifndef AA_DYNAMIC #define AA_DYNAMIC 1 #endif // 1 = Use filtered sample. // 0 = Use center sample. #ifndef AA_FILTERED #define AA_FILTERED 1 #endif // Disables filtering when high contrast // Useful for Catmull Rom filter kernel #ifndef AA_FILTERED_CONTRAST_LIMIT #define AA_FILTERED_CONTRAST_LIMIT 1 #endif // 0 = Dynamic motion based lerp value (default). // non-zero = Use 1/LERP fixed lerp value (used for reflections). #ifndef AA_LERP #define AA_LERP 0 #endif // 1 = Use higher quality round clamp. // 0 = Use lower quality but faster box clamp. #ifndef AA_ROUND #define AA_ROUND 1 #endif // 1 = Use extra clamp to avoid NANs // 0 = Don't use. #ifndef AA_NAN #define AA_NAN 1 #endif // Fix for lack of borders during current frame filter. #ifndef AA_BORDER #define AA_BORDER 0 #endif // Force clamp on alpha. #ifndef AA_FORCE_ALPHA_CLAMP #define AA_FORCE_ALPHA_CLAMP 0 #endif // Use YCoCg path. #ifndef AA_YCOCG #define AA_YCOCG 0 #endif // Bicubic filter history #ifndef AA_BICUBIC #define AA_BICUBIC 0 #endif // Tone map to kill fireflies #ifndef AA_TONE #define AA_TONE 1 #endif // Neighborhood clamping. Disable for testing reprojection. #ifndef AA_CLAMP #define AA_CLAMP 1 #endif // Antighosting using dynamic mask #ifndef AA_DYNAMIC_ANTIGHOST #define AA_DYNAMIC_ANTIGHOST 0 #endif // Compute alpha accurately according samples' to tone weights. #ifndef AA_ACCURATE_ALPHA #define AA_ACCURATE_ALPHA 0 #endif float2 ScreenPos = ( UV * PostprocessInput0Size.xy - 0.5 - ScreenPosToPixel.zw ) / ScreenPosToPixel.xy; // FIND MOTION OF PIXEL AND NEAREST IN NEIGHBORHOOD // ------------------------------------------------ float3 PosN; // Position of this pixel, possibly later nearest pixel in neighborhood. PosN.xy = ScreenPos; PosN.z = SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0).r; // Screen position of minimum depth. float2 VelocityOffset = float2(0.0, 0.0); #if AA_CROSS // For motion vector, use camera/dynamic motion from min depth pixel in pattern around pixel. // This enables better quality outline on foreground against different motion background. // Larger 2 pixel distance "x" works best (because AA dilates surface). float4 Depths; Depths.x = SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0, int2(-AA_CROSS, -AA_CROSS)).r; Depths.y = SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0, int2( AA_CROSS, -AA_CROSS)).r; Depths.z = SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0, int2(-AA_CROSS, AA_CROSS)).r; Depths.w = SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0, int2( AA_CROSS, AA_CROSS)).r; float2 DepthOffset = float2(AA_CROSS, AA_CROSS); float DepthOffsetXx = float(AA_CROSS); #if HAS_INVERTED_Z_BUFFER // Nearest depth is the largest depth (depth surface 0=far, 1=near). if(Depths.x > Depths.y) { DepthOffsetXx = -AA_CROSS; } if(Depths.z > Depths.w) { DepthOffset.x = -AA_CROSS; } float DepthsXY = max(Depths.x, Depths.y); float DepthsZW = max(Depths.z, Depths.w); if(DepthsXY > DepthsZW) { DepthOffset.y = -AA_CROSS; DepthOffset.x = DepthOffsetXx; } float DepthsXYZW = max(DepthsXY, DepthsZW); if(DepthsXYZW > PosN.z) { // This is offset for reading from velocity texture. // This supports half or fractional resolution velocity textures. // With the assumption that UV position scales between velocity and color. VelocityOffset = DepthOffset * PostprocessInput0Size.zw; // This is [0 to 1] flipped in Y. //PosN.xy = ScreenPos + DepthOffset * ViewportSize.zw * 2.0; PosN.z = DepthsXYZW; } #else #error Fix me! #endif // HAS_INVERTED_Z_BUFFER #endif // AA_CROSS // Camera motion for pixel or nearest pixel (in ScreenPos space). float4 ThisClip = float4( PosN.xy, PosN.z, 1 ); float4 PrevClip = mul( ThisClip, View.ClipToPrevClip ); float2 PrevScreen = PrevClip.xy / PrevClip.w; float2 BackN = PosN.xy - PrevScreen; float2 BackTemp = BackN * ViewportSize.xy; #if AA_DYNAMIC float2 VelocityN; #if AA_CROSS VelocityN = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV + VelocityOffset, 0).xy; #else VelocityN = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV, 0).xy; #endif VelocityN *= VelocityScaling.x; bool DynamicN = VelocityN.x > 0.0; if(DynamicN) { BackN = DecodeVelocityFromTexture(VelocityN); } BackTemp = BackN * ViewportSize.xy; #endif #if !AA_BICUBIC // Save the amount of pixel offset of just camera motion, used later as the amount of blur introduced by history. float HistoryBlurAmp = 2.0; float HistoryBlur = saturate(abs(BackTemp.x) * HistoryBlurAmp + abs(BackTemp.y) * HistoryBlurAmp); float Velocity = sqrt(dot(BackTemp, BackTemp)); #endif // Easier to do off screen check before conversion. // BackN is in units of 2pixels/viewportWidthInPixels // This converts back projection vector to [-1 to 1] offset in viewport. BackN = ScreenPos - BackN; bool OffScreen = max(abs(BackN.x), abs(BackN.y)) >= 1.0; // Also clamp to be on screen (fixes problem with DOF). // The .z and .w is the 1/width and 1/height. // This clamps to be a pixel inside the viewport. BackN.x = clamp(BackN.x, -1.0 + ViewportSize.z, 1.0 - ViewportSize.z); BackN.y = clamp(BackN.y, -1.0 + ViewportSize.w, 1.0 - ViewportSize.w); // Convert from [-1 to 1] to view rectangle which is somewhere in [0 to 1]. // The extra +0.5 factor is because ScreenPosToPixel.zw is incorrectly computed // as the upper left of the pixel instead of the center of the pixel. BackN = (BackN * ScreenPosToPixel.xy + ScreenPosToPixel.zw + 0.5) * PostprocessInput0Size.zw; // FILTER PIXEL (RESAMPLE TO REMOVE JITTER OFFSET) AND GET NEIGHBORHOOD // -------------------------------------------------------------------- // 012 // 345 // 678 #if AA_YCOCG // Special case, only using 5 taps. float4 Neighbor1 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 0, -1)); float4 Neighbor3 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2(-1, 0)); float4 Neighbor4 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0); float4 Neighbor5 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 1, 0)); float4 Neighbor7 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 0, 1)); Neighbor1.rgb = RGBToYCoCg(Neighbor1.rgb); Neighbor3.rgb = RGBToYCoCg(Neighbor3.rgb); Neighbor4.rgb = RGBToYCoCg(Neighbor4.rgb); Neighbor5.rgb = RGBToYCoCg(Neighbor5.rgb); Neighbor7.rgb = RGBToYCoCg(Neighbor7.rgb); #if AA_FILTERED_CONTRAST_LIMIT && AA_TONE float Luma_m1, Luma_m2; Luma_m1 = Neighbor1.x; Luma_m2 = Neighbor1.x * Neighbor1.x; Luma_m1 += Neighbor3.x; Luma_m2 += Neighbor3.x * Neighbor3.x; Luma_m1 += Neighbor4.x; Luma_m2 += Neighbor4.x * Neighbor4.x; Luma_m1 += Neighbor5.x; Luma_m2 += Neighbor5.x * Neighbor5.x; Luma_m1 += Neighbor7.x; Luma_m2 += Neighbor7.x * Neighbor7.x; float LumaAvg = Luma_m1 * (1.0 / 5.0); float LumaVar = abs( Luma_m2 * (1.0 / 5.0) - Pow2( LumaAvg ) ); #else float LumaVar = 0; #endif #if AA_TONE float NeighborWeight1 = HdrWeightY(Neighbor1.x, InExposureScale); float NeighborWeight3 = HdrWeightY(Neighbor3.x, InExposureScale); float NeighborWeight4 = HdrWeightY(Neighbor4.x, InExposureScale); float NeighborWeight5 = HdrWeightY(Neighbor5.x, InExposureScale); float NeighborWeight7 = HdrWeightY(Neighbor7.x, InExposureScale); Neighbor1.xyz *= NeighborWeight1; Neighbor3.xyz *= NeighborWeight3; Neighbor4.xyz *= NeighborWeight4; Neighbor5.xyz *= NeighborWeight5; Neighbor7.xyz *= NeighborWeight7; #else float NeighborWeight1 = 1; float NeighborWeight3 = 1; float NeighborWeight4 = 1; float NeighborWeight5 = 1; float NeighborWeight7 = 1; #endif #if AA_FILTERED #if AA_ACCURATE_ALPHA float NeighborFilteringWeight1 = NeighborWeight1 * PlusWeights[0]; float NeighborFilteringWeight3 = NeighborWeight3 * PlusWeights[1]; float NeighborFilteringWeight4 = NeighborWeight4 * PlusWeights[2]; float NeighborFilteringWeight5 = NeighborWeight5 * PlusWeights[3]; float NeighborFilteringWeight7 = NeighborWeight7 * PlusWeights[4]; float FilteredWeight = NeighborFilteringWeight1 + NeighborFilteringWeight3 + NeighborFilteringWeight4 + NeighborFilteringWeight5 + NeighborFilteringWeight7; // Filter neighbors YCoCg in in HDR space. float4 Filtered; Filtered.xyz = Neighbor1.xyz * PlusWeights[0] + Neighbor3.xyz * PlusWeights[1] + Neighbor4.xyz * PlusWeights[2] + Neighbor5.xyz * PlusWeights[3] + Neighbor7.xyz * PlusWeights[4]; // Filter neighbors alpha in linear space according to HDR space weights. Filtered.a = ( Neighbor1.a * NeighborFilteringWeight1 + Neighbor3.a * NeighborFilteringWeight3 + Neighbor4.a * NeighborFilteringWeight4 + Neighbor5.a * NeighborFilteringWeight5 + Neighbor7.a * NeighborFilteringWeight7) / FilteredWeight; #else // Filter neighbors YCoCg and alpha. Might be in linear or HDR space. float4 Filtered = Neighbor1 * PlusWeights[0] + Neighbor3 * PlusWeights[1] + Neighbor4 * PlusWeights[2] + Neighbor5 * PlusWeights[3] + Neighbor7 * PlusWeights[4]; if( LumaVar > 1 ) Filtered = Neighbor4; #endif #if AA_BORDER // Use unfiltered for 1 pixel border. float2 TestPos = abs(ScreenPos); // Add 1 pixel and check if off screen. TestPos += ViewportSize.zw * 2.0; bool FilteredOffScreen = max(TestPos.x, TestPos.y) >= 1.0; if(FilteredOffScreen) { Filtered = Neighbor4; #if AA_ACCURATE_ALPHA FilteredWeight = NeighborWeight4; #endif } #endif #else // AA_FILTERED // Unfiltered. float4 Filtered = Neighbor4; #if AA_ACCURATE_ALPHA float FilteredWeight = NeighborWeight4; #endif #endif // AA_FILTERED float4 NeighborMin; float4 NeighborMax; NeighborMin.xyz = min(min(min(Neighbor1.xyz, Neighbor3.xyz), min(Neighbor4.xyz, Neighbor5.xyz)), Neighbor7.xyz); NeighborMax.xyz = max(max(max(Neighbor1.xyz, Neighbor3.xyz), max(Neighbor4.xyz, Neighbor5.xyz)), Neighbor7.xyz); NeighborMin.w = Neighbor1.w; NeighborMax.w = Neighbor1.w; float NeighborMinAlphaWeight = NeighborWeight1; float NeighborMaxAlphaWeight = NeighborWeight1; WeightTrackedAlphaClamping( NeighborMin.a, NeighborMinAlphaWeight, NeighborMax.a, NeighborMaxAlphaWeight, Neighbor3.a, NeighborWeight3); WeightTrackedAlphaClamping( NeighborMin.a, NeighborMinAlphaWeight, NeighborMax.a, NeighborMaxAlphaWeight, Neighbor4.a, NeighborWeight4); WeightTrackedAlphaClamping( NeighborMin.a, NeighborMinAlphaWeight, NeighborMax.a, NeighborMaxAlphaWeight, Neighbor5.a, NeighborWeight5); WeightTrackedAlphaClamping( NeighborMin.a, NeighborMinAlphaWeight, NeighborMax.a, NeighborMaxAlphaWeight, Neighbor7.a, NeighborWeight7); #else float4 Neighbor0 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2(-1, -1)); float4 Neighbor1 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 0, -1)); float4 Neighbor2 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 1, -1)); float4 Neighbor3 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2(-1, 0)); float4 Neighbor4 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0); float4 Neighbor5 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 1, 0)); float4 Neighbor6 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2(-1, 1)); float4 Neighbor7 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 0, 1)); float4 Neighbor8 = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0, int2( 1, 1)); Neighbor0.rgb *= HdrWeight4(Neighbor0.rgb, InExposureScale); Neighbor1.rgb *= HdrWeight4(Neighbor1.rgb, InExposureScale); Neighbor2.rgb *= HdrWeight4(Neighbor2.rgb, InExposureScale); Neighbor3.rgb *= HdrWeight4(Neighbor3.rgb, InExposureScale); Neighbor4.rgb *= HdrWeight4(Neighbor4.rgb, InExposureScale); Neighbor5.rgb *= HdrWeight4(Neighbor5.rgb, InExposureScale); Neighbor6.rgb *= HdrWeight4(Neighbor6.rgb, InExposureScale); Neighbor7.rgb *= HdrWeight4(Neighbor7.rgb, InExposureScale); Neighbor8.rgb *= HdrWeight4(Neighbor8.rgb, InExposureScale); #if AA_FILTERED float4 Filtered = Neighbor0 * SampleWeights[0] + Neighbor1 * SampleWeights[1] + Neighbor2 * SampleWeights[2] + Neighbor3 * SampleWeights[3] + Neighbor4 * SampleWeights[4] + Neighbor5 * SampleWeights[5] + Neighbor6 * SampleWeights[6] + Neighbor7 * SampleWeights[7] + Neighbor8 * SampleWeights[8]; #if AA_BORDER // Use unfiltered for 1 pixel border. float2 TestPos = abs(ScreenPos); // Add 1 pixel and check if off screen. TestPos += ViewportSize.zw * 2.0; bool FilteredOffScreen = max(TestPos.x, TestPos.y) >= 1.0; if(FilteredOffScreen) { Filtered = Neighbor4; } #endif #else // Unfiltered. float4 Filtered = Neighbor4; #endif #if AA_ROUND float4 NeighborMin2 = min(min(Neighbor0, Neighbor2), min(Neighbor6, Neighbor8)); float4 NeighborMax2 = max(max(Neighbor0, Neighbor2), max(Neighbor6, Neighbor8)); float4 NeighborMin = min(min(min(Neighbor1, Neighbor3), min(Neighbor4, Neighbor5)), Neighbor7); float4 NeighborMax = max(max(max(Neighbor1, Neighbor3), max(Neighbor4, Neighbor5)), Neighbor7); NeighborMin2 = min(NeighborMin2, NeighborMin); NeighborMax2 = max(NeighborMax2, NeighborMax); NeighborMin = NeighborMin * 0.5 + NeighborMin2 * 0.5; NeighborMax = NeighborMax * 0.5 + NeighborMax2 * 0.5; float4 m1, m2; m1 = Neighbor0; m2 = Neighbor0 * Neighbor0; m1 += Neighbor1; m2 += Neighbor1 * Neighbor1; m1 += Neighbor2; m2 += Neighbor2 * Neighbor2; m1 += Neighbor3; m2 += Neighbor3 * Neighbor3; m1 += Neighbor4; m2 += Neighbor4 * Neighbor4; m1 += Neighbor5; m2 += Neighbor5 * Neighbor5; m1 += Neighbor6; m2 += Neighbor6 * Neighbor6; m1 += Neighbor7; m2 += Neighbor7 * Neighbor7; float4 mu = m1 * (1.0 / 8.0); float4 Sigma = sqrt( abs(m2 * (1.0 / 8.0) - mu * mu) ); NeighborMin = mu - 1.0 * Sigma; NeighborMax = mu + 1.0 * Sigma; #else float4 NeighborMin = min(min( min(min(Neighbor0, Neighbor1), min(Neighbor2, Neighbor3)), min(min(Neighbor4, Neighbor5), min(Neighbor6, Neighbor7))), Neighbor8); float4 NeighborMax = max(max( max(max(Neighbor0, Neighbor1), max(Neighbor2, Neighbor3)), max(max(Neighbor4, Neighbor5), max(Neighbor6, Neighbor7))), Neighbor8); #endif #endif // FETCH HISTORY // ------------- #if AA_BICUBIC float4 HistoryColor = Texture2DSampleBicubic( PostprocessInput1, PostprocessInput1Sampler, BackN.xy, PostprocessInput1Size.xy ); #else float4 HistoryColor = PostprocessInput1.SampleLevel(PostprocessInput1Sampler, BackN.xy, 0); #endif #if AA_YCOCG HistoryColor.rgb = RGBToYCoCg(HistoryColor.rgb); #if AA_TONE float HistoryWeight = HdrWeightY(HistoryColor.x, InExposureScale); HistoryColor.xyz *= HistoryWeight; #endif #else HistoryColor.rgb *= HdrWeight4(HistoryColor.rgb, InExposureScale); #endif #if AA_DYNAMIC_ANTIGHOST && AA_DYNAMIC && !AA_ALPHA bool Dynamic1 = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV, 0, int2( 0, -1)).x > 0; bool Dynamic3 = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV, 0, int2(-1, 0)).x > 0; bool Dynamic4 = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV, 0).x > 0; bool Dynamic5 = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV, 0, int2( 1, 0)).x > 0; bool Dynamic7 = PostprocessInput3.SampleLevel(PostprocessInput3Sampler, UV, 0, int2( 0, 1)).x > 0; bool Dynamic = Dynamic1 || Dynamic3 || Dynamic4 || Dynamic5 || Dynamic7; if( !Dynamic && HistoryColor.a > 0 ) { HistoryColor = Filtered; HistoryColor.a = 0; } #endif // FIND LUMA OF CLAMPED HISTORY // ---------------------------- // Save off luma of history before the clamp. #if AA_YCOCG float LumaMin = NeighborMin.x; float LumaMax = NeighborMax.x; float LumaHistory = HistoryColor.x; #else float LumaMin = Luma4(NeighborMin.rgb); float LumaMax = Luma4(NeighborMax.rgb); float LumaHistory = Luma4(HistoryColor.rgb); #endif float LumaContrast = LumaMax - LumaMin; #if AA_CLAMP #if AA_YCOCG HistoryColor.rgb = clamp(HistoryColor.rgb, NeighborMin.rgb, NeighborMax.rgb); #if AA_ALPHA #if AA_ACCURATE_ALPHA // Clamp history's alpha with sample weight tracking. if (HistoryColor.a < NeighborMin.a) { HistoryColor.a = NeighborMin.a; HistoryWeight = NeighborMinAlphaWeight; } else if (HistoryColor.a > NeighborMax.a) { HistoryColor.a = NeighborMax.a; HistoryWeight = NeighborMaxAlphaWeight; } #else HistoryColor.a = clamp(HistoryColor.a, NeighborMin.a, NeighborMax.a); #endif #endif #else #if AA_AABB // Clamp history, this uses color AABB intersection for tighter fit. float ClampBlend = HistoryClamp(HistoryColor.rgb, Filtered.rgb, NeighborMin.rgb, NeighborMax.rgb); #if AA_ALPHA HistoryColor.rgba = lerp(HistoryColor.rgba, Filtered.rgba, ClampBlend); #else HistoryColor.rgb = lerp(HistoryColor.rgb, Filtered.rgb, ClampBlend); #endif #else HistoryColor = clamp(HistoryColor, NeighborMin, NeighborMax); #endif #endif #endif // ADD BACK IN ALIASING TO SHARPEN // ------------------------------- #if AA_FILTERED && !AA_BICUBIC // Blend in non-filtered based on the amount of sub-pixel motion. float AddAliasing = saturate(HistoryBlur) * 0.5; float LumaContrastFactor = 32.0; #if AA_YCOCG // 1/4 as bright. LumaContrastFactor *= 4.0; #endif AddAliasing = saturate(AddAliasing + rcp(1.0 + LumaContrast * LumaContrastFactor)); Filtered.rgb = lerp(Filtered.rgb, Neighbor4.rgb, AddAliasing); #endif #if AA_YCOCG float LumaFiltered = Filtered.x; #else float LumaFiltered = Luma4(Filtered.rgb); #endif // COMPUTE BLEND AMOUNT // -------------------- float DistToClamp = min(abs(LumaMin-LumaHistory), abs(LumaMax-LumaHistory)); #if AA_BICUBIC float HistoryFactor = CurrentFrameWeight * 3.125f * DistToClamp; #else float HistoryAmount = CurrentFrameWeight * 3.125f + HistoryBlur * (1.0/8.0); float HistoryFactor = DistToClamp * HistoryAmount * (1.0 + HistoryBlur * HistoryAmount * 8.0); #endif float BlendFinal = saturate(HistoryFactor / (DistToClamp + LumaMax - LumaMin)); #if AA_TONE BlendFinal = CurrentFrameWeight; #endif #if RESPONSIVE // Responsive forces 1/4 of new frame. BlendFinal = 1.0/4.0; #elif AA_NAN && (COMPILER_GLSL || COMPILER_METAL) // The current Metal & GLSL compilers don't handle saturate(NaN) -> 0, instead they return NaN/INF. BlendFinal = -min(-BlendFinal, 0.0); #endif // Offscreen feedback resets. #if AA_LERP float FixedLerp = 1.0/float(AA_LERP); #endif if(OffScreen) { HistoryColor = Filtered; #if AA_YCOCG && AA_ACCURATE_ALPHA HistoryWeight = FilteredWeight; #endif #if !AA_ALPHA HistoryColor.a = 0.0; #endif #if AA_LERP FixedLerp = 1.0; #endif } // DO FINAL BLEND BETWEEN HISTORY AND FILTERED COLOR // ------------------------------------------------- #if (AA_LERP == 0) #if AA_ALPHA #if AA_ACCURATE_ALPHA HistoryColor.a *= HistoryWeight; Filtered.a *= FilteredWeight; #endif OutColor = lerp(HistoryColor, Filtered, BlendFinal); #if AA_YCOCG && AA_ACCURATE_ALPHA float BlendWeight = lerp(HistoryWeight, FilteredWeight, BlendFinal); #elif AA_FORCE_ALPHA_CLAMP OutColor.a = clamp(HistoryColor.a, NeighborMin.a, NeighborMax.a); #endif #else // Blend in linear to hit luma target. OutColor.rgb = lerp(HistoryColor.rgb, Filtered.rgb, BlendFinal); #if RESPONSIVE OutColor.a = max(HistoryColor.a, 1.0/2.0); #endif #endif #else OutColor = lerp(HistoryColor, Filtered, FixedLerp); #endif #if AA_YCOCG #if AA_TONE OutColor.xyz *= HdrWeightInvY(OutColor.x, InExposureScale); #endif #if AA_ACCURATE_ALPHA OutColor.a *= 1 / BlendWeight; #if AA_FORCE_ALPHA_CLAMP OutColor.a = clamp(OutColor.a, NeighborMin.a, NeighborMax.a); #endif #endif OutColor.rgb = YCoCgToRGB(OutColor.rgb); #else // Convert back into linear. OutColor.rgb *= HdrWeightInv4(OutColor.rgb, InExposureScale); #endif #if AA_NAN // Transform NaNs to black, transform negative colors to black. OutColor.rgb = -min(-OutColor.rgb, 0.0); #endif #if AA_DYNAMIC_ANTIGHOST && AA_DYNAMIC && !AA_ALPHA OutColor.a = Dynamic4 ? 1 : 0; #elif !AA_ALPHA // Zero out to remove any prior computation of alpha OutColor.a = 0; #endif #undef AA_AABB #undef AA_ALPHA #undef AA_CROSS #undef AA_DYNAMIC #undef AA_FILTERED #undef AA_FILTERED_CONTRAST_LIMIT #undef AA_LERP #undef AA_ROUND #undef AA_NAN #undef AA_BORDER #undef AA_FORCE_ALPHA_CLAMP #undef AA_YCOCG #undef AA_BICUBIC #undef AA_TONE #undef AA_CLAMP #undef AA_DYNAMIC_ANTIGHOST #undef AA_ACCURATE_ALPHA