From 8e937abe6622b6d51f6c918072248ca32888d1af Mon Sep 17 00:00:00 2001
From: Adrian Courreges <a.courreges@gmail.com>
Date: Sat, 1 Dec 2018 10:28:04 +0900
Subject: [PATCH] GatherDOF integration (drop-in replacement for BokehDOF)

Enable with "r.GatherDOF.Enable 1".

Full details and information: http://www.adriancourreges.com/blog/2018/12/02/ue4-optimized-post-effects/
---
 Engine/Shaders/Private/PostProcessGatherDOF.usf    |  574 +++++++
 .../Private/PostProcess/PostProcessBokehDOF.cpp    | 1644 ++++++++++++++++++++
 .../Private/PostProcess/PostProcessBokehDOF.h      |  157 ++
 .../Private/PostProcess/PostProcessing.cpp         |  266 +++-
 4 files changed, 2610 insertions(+), 31 deletions(-)
 create mode 100644 Engine/Shaders/Private/PostProcessGatherDOF.usf

diff --git a/Engine/Shaders/Private/PostProcessGatherDOF.usf b/Engine/Shaders/Private/PostProcessGatherDOF.usf
new file mode 100644
index 00000000000..48bb5bd61c9
--- /dev/null
+++ b/Engine/Shaders/Private/PostProcessGatherDOF.usf
@@ -0,0 +1,574 @@
+/*=============================================================================
+	PostProcessGatherDOF.usf: Gather-type depth of field effect.
+	
+	Inspired by Crytek 3 technique:
+	http://www.crytek.com/download/Sousa_Graphics_Gems_CryENGINE3.pdf 
+	
+	Supported bokeh shapes: disk, any N-sided polygon (hexagon, octagon...)
+	
+	Overview:
+		- Work at half resolution, separate far field and near field based on CoC
+		- Flat-kernel blur of each field, with the bokeh shape desired using
+		  a fixed number of taps (will create gaps at high radius). 
+		  6x6 taps is an ok figure for a scene at 720p.
+		- Floodfill any gaps created by lack of sampling in the previous pass.
+		  3x3 taps can be enough.
+		- Special treatment for the near field, to have natural bleeding of
+		  near objects onto the layers beneath:
+			. Create a tile map of near CoC, with several downscales, keeping
+			  only max near CoC for the entire tile
+			. Bleed opaque tiles onto their transparent neighbors
+		- Composite the 3 layers: far / in-focus / near
+	
+=============================================================================*/
+
+#include "Common.ush"
+#include "PostProcessCommon.ush"
+#include "DepthOfFieldCommon.ush"
+#include "SeparateTranslucency.ush"
+
+// High / low quality knob
+#if HIGH_QUALITY
+
+#define BLUR_TAP_COUNT 8
+#define FLOODFILL_TAP_COUNT 4
+
+#else
+	
+#define BLUR_TAP_COUNT 6
+#define FLOODFILL_TAP_COUNT 3
+
+#endif // HIGH_QUALITY
+
+// .x : size of the bokeh blur radius in texel space
+// .y : rotation in radius to apply to the bokeh shape
+// .z : Number of edge of the polygon (number of blades). 0: circle. 4: square, 6: hexagon...
+float4 KernelSize;
+
+// Maps a unit square in [0, 1] to a unit disk in [-1, 1]
+// Returns polar coordinates (radius, angle)
+// Shirley 97 "A Low Distortion Map Between Disk and Square"
+float2 UnitSquareToUnitDiskPolar(float2 uv)
+{
+	float radius;
+	float angle;
+
+	const float PI_BY_2 = 1.5707963f; // PI / 2
+	const float PI_BY_4 = 0.785398f;  // PI / 4
+	const float EPSILON = 0.000001f;
+	
+	// Remap [0, 1] to [-1, 1] centered
+	float a = (2.0f * uv.x) - 1.0f;
+	float b = (2.0f * uv.y) - 1.0f;
+	
+	// Morph to unit disk
+	if (abs(a) > abs(b)) 
+	{
+		// First region (left and right quadrants of the disk)
+		radius = a;
+		angle = b / (a + EPSILON) * PI_BY_4;
+	} 
+	else 
+	{
+		// Second region (top and bottom quadrants of the disk)
+		radius = b;
+		angle = PI_BY_2 - (a / (b + EPSILON) * PI_BY_4);
+	}
+
+	if (radius < 0)
+	{
+		radius *= -1.0f;
+		angle += PI;
+	}
+
+	return float2(radius, angle);
+}
+
+// Maps a unit square in [0, 1] to a unit disk in [-1, 1]
+// Returns new cartesian coordinates (u,v) 
+float2 SquareToDiskMapping(float2 uv) {
+	float2 PolarCoord = UnitSquareToUnitDiskPolar(uv);
+	return float2(PolarCoord.x * cos(PolarCoord.y), PolarCoord.x * sin(PolarCoord.y));
+}
+
+// Remap a unit square in [0, 1] to a unit polygon in [-1, 1]
+// Returns new cartesian coordinates (u,v) 
+float2 SquareToPolygonMapping(float2 uv) {
+	const float N = KernelSize.z; // Edge count of the polygon. 
+	float2 PolarCoord = UnitSquareToUnitDiskPolar(uv); // (radius, angle)
+	
+	if (N >= 3.0f) 
+	{
+		// Re-scale radius to match a polygon shape
+		// http://www.crytek.com/download/Sousa_Graphics_Gems_CryENGINE3.pdf
+		PolarCoord.x *= ( cos(PI / N) / ( cos(PolarCoord.y - (2.0f * PI / N) * floor((N*PolarCoord.y + PI) / 2.0f / PI ) )));
+	
+		// Apply a rotation to the polygon shape. 
+		PolarCoord.y += KernelSize.y; 
+	}
+	
+	return float2(PolarCoord.x * cos(PolarCoord.y), PolarCoord.x * sin(PolarCoord.y));
+}
+
+// TODO Taken from BokehDOF. Centralize!
+// @return x:layer in front of focal plane, y: layer behind focal plane  1-x-y:layer in focus
+float2 ComputeLayerContributions(float Depth)
+{
+	float Front = saturate((View.DepthOfFieldFocalDistance - Depth) / View.DepthOfFieldNearTransitionRegion);
+	float Back = saturate((Depth - View.DepthOfFieldFocalDistance - View.DepthOfFieldFocalRegion) / max(View.DepthOfFieldFarTransitionRegion, 0.0001f));
+
+	return float2(Front, Back);
+}
+
+// TODO Taken from BokehDOF. Centralize!
+float4 CommonDOFSetup(in float2 CenterUV, out bool bFrontLayer, out float4 Mask )
+{
+	float2 Offset = PostprocessInput0Size.zw;
+
+	float2 UV[4];
+
+	// no filtering (2x2 kernel) to get no leaking in Depth of Field
+	UV[0] = CenterUV + Offset * float2(-0.5f, -0.5f);
+	UV[1] = CenterUV + Offset * float2( 0.5f, -0.5f);
+	UV[2] = CenterUV + Offset * float2(-0.5f,  0.5f);
+	UV[3] = CenterUV + Offset * float2( 0.5f,  0.5f);
+
+	float4 ColorAndDepth[4];
+	float2 Layer[4];
+
+	UNROLL for(uint i = 0; i < 4; ++i)
+	{
+		// clamping to a small number fixes black dots appearing (denorms?, 0 doesn't fix it)
+		ColorAndDepth[i].rgb = max(float3(0.0001f, 0.0001f, 0.0001f), Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, UV[i]).rgb);
+		ColorAndDepth[i].a = CalcSceneDepth(UV[i]);
+		Layer[i] = ComputeLayerContributions(ColorAndDepth[i].a);
+	}
+
+	float2 LayerSum = Layer[0] + Layer[1] + Layer[2] + Layer[3];
+	bFrontLayer = LayerSum.x > LayerSum.y;
+
+	Mask = bFrontLayer ?
+		float4(Layer[0].x, Layer[1].x, Layer[2].x, Layer[3].x) :
+		float4(Layer[0].y, Layer[1].y, Layer[2].y, Layer[3].y);
+
+	float SumMask = dot(Mask, 1);
+
+	float4 OutColor;
+
+	FLATTEN if(SumMask > 0.001f)
+	{
+		OutColor = (
+			ColorAndDepth[0] * Mask.x +
+			ColorAndDepth[1] * Mask.y +
+			ColorAndDepth[2] * Mask.z +
+			ColorAndDepth[3] * Mask.w ) / SumMask;
+	}
+	else
+	{
+		OutColor = ColorAndDepth[0];
+	}
+
+	return OutColor;
+}
+
+// Downsample scene to half res, extract near field and far field 
+void MainSetupPS(in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float4 OutColor0 : SV_Target0, // Far color + CoC
+	out float4 OutColor1 : SV_Target1, // Near color + CoC
+	out float  OutColor2 : SV_Target2  // Near Max CoC tilemap
+	) 
+{
+	// unused for this pass
+	bool bFrontLayer;
+	float4 Mask;
+
+	float4 SceneColorAndDepth = CommonDOFSetup(UVAndScreenPos.xy, bFrontLayer, Mask);
+
+	// clamp to avoid artifacts from exceeding fp16 through framebuffer blending of multiple very bright lights
+	SceneColorAndDepth.rgb = min(float3(256 * 256, 256 * 256, 256 * 256), SceneColorAndDepth.rgb);
+	
+	float SceneDepth = SceneColorAndDepth.a;
+	float CircleOfConfusion = ComputeCircleOfConfusion(SceneDepth);
+	
+	if (SceneDepth < View.DepthOfFieldFocalDistance) {
+		// Near
+		OutColor0 = float4(0, 0, 0, 0);
+		OutColor1 = float4(SceneColorAndDepth.rgb, CircleOfConfusion);
+		OutColor2 = CircleOfConfusion; // Separate CoC to build Max TileMap
+	} 
+	else if (SceneDepth >= View.DepthOfFieldFocalDistance + View.DepthOfFieldFocalRegion)
+	{
+		// Far
+		OutColor0 = float4(SceneColorAndDepth.rgb, CircleOfConfusion);
+		OutColor1 = float4(0, 0, 0, 0);
+		OutColor2 = 0.0f;
+	}
+	else
+	{
+		// In-focus (inside focal region)
+		OutColor0 = float4(0, 0, 0, 0);
+		OutColor1 = float4(0, 0, 0, 0);
+		OutColor2 = 0.0f;
+	}
+}
+
+
+// This shader is useful to read BokehDOF setup buffer (especially post TAA) and prepare
+// the near / far fields for GatherDOF. We now extract all directly from the full-res scene. 
+// Target0: Far field
+// Target1: Near field
+// Target2: Near CoC tilemap
+void MainExtractAreaPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float4 OutColor0 : SV_Target0,
+	out float4 OutColor1 : SV_Target1,
+	out float  OutColor2 : SV_Target2)
+{
+	float4 SceneColorAndDepth = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, UVAndScreenPos.xy, 0);
+	float SceneDepth = SceneColorAndDepth.a;
+	float CircleOfConfusion = ComputeCircleOfConfusion(SceneDepth);
+	
+	if (SceneDepth < View.DepthOfFieldFocalDistance) {
+		// Near
+		OutColor0 = float4(0, 0, 0, 0);
+		OutColor1 = float4(SceneColorAndDepth.rgb, CircleOfConfusion);
+		OutColor2 = CircleOfConfusion; // Separate CoC to build Max TileMap
+	} 
+	else if (SceneDepth >= View.DepthOfFieldFocalDistance + View.DepthOfFieldFocalRegion)
+	{
+		// Far
+		OutColor0 = float4(SceneColorAndDepth.rgb, CircleOfConfusion);
+		OutColor1 = float4(0, 0, 0, 0);
+		OutColor2 = 0.0f;
+	}
+	else
+	{
+		// In-focus (inside focal region)
+		OutColor0 = float4(0, 0, 0, 0);
+		OutColor1 = float4(0, 0, 0, 0);
+		OutColor2 = 0.0f;
+	}
+}
+
+// Blur far field, applying a grid pattern of samples remapped to a bokeh shape
+void MainBlurFarPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float4 OutColor : SV_Target0)
+{
+	
+	float4 PixelColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, UVAndScreenPos.xy, 0);
+	float PixelCoC = PixelColor.w;
+
+	float3 ResultColor = 0;
+	float Weight = 0;
+	
+	int TAP_COUNT = BLUR_TAP_COUNT; // Higher means less noise and make floodfilling easier after
+	
+	// Multiplying by PixelCoC guarantees a smooth evolution of the blur radius
+	// especially visible on plane (like the floor) where CoC slowly grows with the distance.
+	// This makes all the difference between a natural bokeh and some noticeable
+	// in-focus and out-of-focus layer blending
+	float radius = KernelSize.x * PixelCoC;
+	
+	if (PixelCoC > 0) { // Ignore any pixel not belonging to far field
+	
+		// Weighted average of the texture samples inside the bokeh pattern
+		// High radius and low sample count can create "gaps" which are fixed later (floodfill).
+		for (int u = 0; u < TAP_COUNT; ++u)
+		{
+			for (int v = 0; v < TAP_COUNT; ++v)
+			{
+				float2 uv = float2(u, v) / (TAP_COUNT - 1); // map to [0, 1]
+				uv = SquareToPolygonMapping( uv ) * PostprocessInput0Size.zw; // map to bokeh shape, then to texel size
+				uv = UVAndScreenPos.xy + radius * uv; 
+				
+				float4 tapColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, uv, 0);
+				
+				// Weighted by CoC. Gives more influence to taps with a CoC higher than us.
+				float TapWeight = tapColor.w * saturate(1.0f - (PixelCoC - tapColor.w)); 
+				
+				ResultColor +=  tapColor.xyz * TapWeight; 
+				Weight += TapWeight;
+			}
+		}
+		if (Weight > 0) ResultColor /= Weight;
+		Weight = Weight / TAP_COUNT / TAP_COUNT;
+	}
+	
+	Weight = saturate(Weight * 10); // From CoC 0.1, completely rely on the far field layer and stop lerping with in-focus layer
+	OutColor = float4(ResultColor, Weight);
+}
+
+// Box downscale the near CoC tilemap, keeping only the max value of the 4 taps
+void MainDownscaleTMPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float OutColor : SV_Target0)
+{
+	float2 tapUV = UVAndScreenPos.xy;
+	
+	float4 CoCs[4];
+		
+	CoCs[0] = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV                                                  , 0);
+	CoCs[1] = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV + float2( 0.5f,  0.0f) * PostprocessInput0Size.zw, 0);
+	CoCs[2] = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV + float2( 0.0f,  0.5f) * PostprocessInput0Size.zw, 0);
+	CoCs[3] = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV + float2( 0.5f,  0.5f) * PostprocessInput0Size.zw, 0);
+
+	OutColor = max( CoCs[0], max( CoCs[1], max( CoCs[2], CoCs[3] ) ) );
+}
+
+// Creates a halo for the tilemap, so we can have nice bokeh shape spreading outside the original near field area
+void MainNearHaloPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float OutColor : SV_Target0)
+{
+	float2 tapUV = UVAndScreenPos.xy;
+	
+	float PixelColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV, 0); 
+	
+	if (PixelColor == 0) // Only fill the empty areas around near field
+	{
+		PixelColor = 0;
+		float Weight = 0;
+		int RADIUS_TAPS = 4; // 8x8 taps, but shouldn't be heavy at such low resolution
+		for (int u = -RADIUS_TAPS; u <= RADIUS_TAPS; ++u)
+		{
+			for (int v = -RADIUS_TAPS; v <= RADIUS_TAPS; ++v)
+			{
+				float tapValue = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV + float2(u,v) * PostprocessInput0Size.zw, 0); 
+				float tapWeight = tapValue == 0.0f? 0.0f : 1.0f;
+				PixelColor += tapWeight * tapValue;
+				Weight += tapWeight;
+			}
+		}
+		PixelColor /= (Weight + 0.000001f);
+	}
+	
+	OutColor = PixelColor;
+}
+
+// Makes near color bleed into transparent texel 
+// (poor-man way since pre-multiplied alpha doesn't play nice with alpha boost in the main blur pass)
+void MainNearBorderPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float4 OutColor : SV_Target0)
+{
+	float2 tapUV = UVAndScreenPos.xy;
+	
+	float4 PixelColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV, 0); 
+	
+	if (PixelColor.w == 0) // Only fill the empty areas around near field
+	{
+		PixelColor = 0;
+		float Weight = 0;
+		int RADIUS_TAPS = 1;
+		for (int u = -RADIUS_TAPS; u <= RADIUS_TAPS; ++u)
+		{
+			for (int v = -RADIUS_TAPS; v <= RADIUS_TAPS; ++v)
+			{
+				float4 tapValue = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, tapUV + float2(u,v) * PostprocessInput0Size.zw, 0); 
+				float tapWeight = tapValue.w == 0.0f? 0.0f : 1.0f;
+				PixelColor += tapWeight * tapValue;
+				Weight += tapWeight;
+			}
+		}
+		PixelColor /= (Weight + 0.0000001f);
+		PixelColor.w = 0;
+	}
+	
+	OutColor = PixelColor;
+}
+
+
+// Blur near field
+void MainBlurNearPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float4 OutColor : SV_Target0)
+{
+	
+	float4 PixelColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, UVAndScreenPos.xy, 0);
+	float PixelCoC = Texture2DSampleLevel(PostprocessInput1, PostprocessInput1Sampler, UVAndScreenPos.xy, 0); 
+	
+	float3 ResultColor = 0;
+	float Weight = 0;
+	int TAP_COUNT = BLUR_TAP_COUNT; // Higher means less noise and make floodfilling easier after
+	
+	float radius = KernelSize.x * 0.7f * PixelCoC; 
+	
+	if (PixelCoC > 0) { // Early exit based on MaxCoC tilemap
+		for (int u = 0; u < TAP_COUNT; ++u)
+		{
+			for (int v = 0; v < TAP_COUNT; ++v)
+			{
+				float2 uv = float2(u, v) / (TAP_COUNT - 1);
+				uv = SquareToPolygonMapping( uv ) * PostprocessInput0Size.zw; // map to shape, to texel size
+				uv = UVAndScreenPos.xy + radius * uv;
+
+				float4 tapColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, uv, 0);
+				float TapWeight = saturate(tapColor.w * 10.0f); // From CoC 0.1 rely only on the near field and stop lerping with in-focus area 
+				
+				ResultColor +=  tapColor.xyz * TapWeight; 
+				Weight += TapWeight;
+			}
+		}
+		
+		ResultColor /= (Weight + 0.0000001f);
+		Weight /= (TAP_COUNT * TAP_COUNT);
+	}
+
+	OutColor = float4(ResultColor, Weight);
+}
+
+
+// Performs a floodfill to fill the "gaps" which can appear between blur samples.
+void MainFloodfillPS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	out float4 OutColor : SV_Target0)
+{
+	float4 PixelColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, UVAndScreenPos.xy, 0);
+	float PixelCoC = PixelColor.w;
+
+#if COC_TILEMAP	
+	// Use max CoC tilemap instead of the original pixel CoC for near field
+	PixelCoC = Texture2DSampleLevel(PostprocessInput1, PostprocessInput1Sampler, UVAndScreenPos.xy, 0);
+#endif
+
+	float4 ResultColor = PixelColor;
+	
+	float radius = PixelCoC * 1.75f * KernelSize.x  / 15.0f;
+	
+	int TAP_COUNT = FLOODFILL_TAP_COUNT; // Higher count improves the floodfill
+	
+	if (PixelCoC > 0) { // Early out if we're outside the field
+	
+		// McIntosh12 http://ivizlab.sfu.ca/papers/cgf2012.pdf
+		// Keep the maximum of all the samples
+		for (int u = 0; u < TAP_COUNT; ++u)
+		{
+			for (int v = 0; v < TAP_COUNT; ++v)
+			{
+				float2 uv = float2(u, v) / (TAP_COUNT - 1); // map to [0, 1]
+				uv = SquareToPolygonMapping( uv ) * PostprocessInput0Size.zw; // map to bokeh shape, then to texel size
+				uv = UVAndScreenPos.xy + radius * uv;
+				
+				float4 tapColor = Texture2DSampleLevel(PostprocessInput0, PostprocessInput0Sampler, uv, 0);
+				ResultColor = max(ResultColor, tapColor);
+			}
+		}
+	}
+#if COC_TILEMAP		
+	OutColor = ResultColor;
+#else
+	// do not touch alpha of far field
+	OutColor = float4(ResultColor.xyz, PixelCoC);
+#endif
+}
+
+// Blend in-focus / far / near layers on the top of each others
+float3 GetCombinedLayerColor(float2 uv, float4 SvPosition) 
+{
+	// Set FadePower to 1.0f in the next line to be physically correct,
+	// but some in-focus silhouette might become visible at the border of the
+	// near field, where the gradient smoothly fades alpha to 0. [0 -> 0.5 -> 1]
+	// Limitation of screen-space effect: we don't have access to the actual color occluded by near field meshes.
+	// This makes the near-field fading gradient more aggressive [0 -> 1 -> 1] to hide any sharp silhouette.
+	float FadePower = 2.0f; 
+
+#if 1 // Optimized version using early-out to avoid unnecessary texture fetches -> ~25% speed-up in reference scene.
+	
+	float4 NearColor = Texture2DSampleLevel(PostprocessInput2, PostprocessInput2Sampler, uv, 0);
+	
+	NearColor.w = saturate(NearColor.w * FadePower);
+	
+	// Pure near field, early exit
+	if (NearColor.w == 1.0f) 
+	{
+		return NearColor.rgb;
+	}
+	
+	float4 FarColor = Texture2DSampleLevel(PostprocessInput1, PostprocessInput1Sampler, uv, 0);
+	
+	// Original CoC to guarantee crisp edge of in-focus over far-field
+	float2 PixelPosCenter = SvPosition.xy;
+	float2 FullResUV = PixelPosCenter * PostprocessInput0Size.zw;
+	float SceneDepth = CalcSceneDepth(FullResUV);
+	
+	bool isInFocus = (SceneDepth >= View.DepthOfFieldFocalDistance) &&
+						(SceneDepth < View.DepthOfFieldFocalDistance + View.DepthOfFieldFocalRegion);
+	
+	if (!isInFocus) {
+		// Pure far field without any bleeding from near field, early exit
+		if (FarColor.w == 1.0f && NearColor.w == 0.0f )
+		{
+			return FarColor.rgb;
+		}
+		
+		// Blending only between far and near
+		if (FarColor.w == 1.0f) {
+			return float3( NearColor.w * (NearColor.rgb) + (1.0f - NearColor.w) * FarColor.rgb );
+		}
+	} else {
+		// Pixel was originally in focus, background should never bleed onto it
+		FarColor.w = 0;
+	}
+	
+	// Worst case: 3 layer merge
+	float3 FocusColor = Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, uv).rgb;
+	
+	float3 Result = FarColor.w * FarColor.rgb + (1.0f - FarColor.w) * FocusColor;
+	Result =  NearColor.w * (NearColor) + (1.0f - NearColor.w) * Result;
+	
+	return Result;
+	
+#else // Original generic version
+	
+	float3 FocusColor = Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, uv).rgb;
+	float4 FarColor = Texture2DSampleLevel(PostprocessInput1, PostprocessInput1Sampler, uv, 0);
+	float4 NearColor = Texture2DSampleLevel(PostprocessInput2, PostprocessInput2Sampler, uv, 0);
+	
+	// Original CoC to guarantee crisp edge of in-focus over far-field
+	float2 PixelPosCenter = SvPosition.xy;
+	float2 FullResUV = PixelPosCenter * PostprocessInput0Size.zw;
+	float SceneDepth = CalcSceneDepth(FullResUV);
+	
+	bool isInFocus = (SceneDepth >= View.DepthOfFieldFocalDistance) &&
+						(SceneDepth < View.DepthOfFieldFocalDistance + View.DepthOfFieldFocalRegion);
+	if (isInFocus) FarColor.w = 0;
+	
+	// Alpha composite far field on the top of the original scene.
+	float3 Result = FarColor.w * FarColor.rgb + (1.0f - FarColor.w) * FocusColor;
+	
+	// Alpha composite on the near field
+	if (NearColor.w > 0) {
+		float blendFactor = saturate(NearColor.w * FadePower);
+		Result =  blendFactor * (NearColor.rgb) + (1.0f - blendFactor) * Result;
+	}
+	
+	return Result;
+#endif		
+	
+}
+
+
+void MainRecombinePS(
+	in noperspective float4 UVAndScreenPos : TEXCOORD0, 
+	float4 SvPosition : SV_POSITION,
+	out float4 OutColor : SV_Target0)
+{	
+	float2 uv = UVAndScreenPos;
+	
+#if RECOMBINE_METHOD == 2 // Separate translucency
+	// SceneColor in full res
+	float2 PixelPosCenter = SvPosition.xy;
+	float2 FullResUV = PixelPosCenter * PostprocessInput0Size.zw;
+	float4 SeparateTranslucency = UpsampleSeparateTranslucency(SvPosition.xy, FullResUV, PostprocessInput3, PostprocessInput3Size.zw);
+#endif	
+
+	float3 RecombinedLayersColor = GetCombinedLayerColor(uv, SvPosition);
+	
+#if RECOMBINE_METHOD == 2
+		// Separate translucency as premultiplied alpha
+		RecombinedLayersColor.rgb = RecombinedLayersColor.rgb * SeparateTranslucency.a + SeparateTranslucency.rgb;
+#endif
+	
+	OutColor = float4(RecombinedLayersColor, 1.0f);
+}
\ No newline at end of file
diff --git a/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.cpp b/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.cpp
index e43ed9e8640..78a6f2866bb 100644
--- a/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.cpp
+++ b/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.cpp
@@ -931,3 +931,1647 @@ FPooledRenderTargetDesc FRCPassPostProcessBokehDOF::ComputeOutputDesc(EPassOutpu
 
 	return Ret;
 }
+
+//-------------------------------------------
+// Gather Bokeh Depth of Field
+//-------------------------------------------
+
+/** Encapsulates the post processing depth of field setup pixel shader. */
+class PostProcessGatherDOFSetupPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessGatherDOFSetupPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	/** Default constructor. */
+	PostProcessGatherDOFSetupPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+
+	/** Initialization constructor. */
+	PostProcessGatherDOFSetupPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+
+			// in rendertarget pixels (half res to scene color)
+			FRenderingCompositeOutput* Output = Context.Pass->GetOutput(ePId_Output0);
+
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainSetupPS");
+	}
+};
+
+IMPLEMENT_SHADER_TYPE3(PostProcessGatherDOFSetupPS, SF_Pixel);
+
+void FRCPassPostProcessGatherDOFSetup::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, GatherDOFSetup);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = FIntRect::DivideAndRoundUp(SrcRect, 2);
+
+	const FSceneRenderTargetItem& DestRenderTarget0 = PassOutputs[0].RequestSurface(Context);
+	const FSceneRenderTargetItem& DestRenderTarget1 = PassOutputs[1].RequestSurface(Context);
+	const FSceneRenderTargetItem& DestRenderTarget2 = PassOutputs[2].RequestSurface(Context);
+
+	// Set the view family's render target/viewport.
+	FTextureRHIParamRef RenderTargets[3] =
+	{
+		DestRenderTarget0.TargetableTexture,
+		DestRenderTarget1.TargetableTexture,
+		DestRenderTarget2.TargetableTexture
+	};
+	//@todo Ronin find a way to use the same codepath for all platforms.
+	const EShaderPlatform ShaderPlatform = GShaderPlatformForFeatureLevel[Context.GetFeatureLevel()];
+	if (IsVulkanMobilePlatform(ShaderPlatform))
+	{
+		SetRenderTargets(Context.RHICmdList, 3, RenderTargets, FTextureRHIParamRef(), ESimpleRenderTargetMode::EClearColorAndDepth, FExclusiveDepthStencil());
+	}
+	else
+	{
+		SetRenderTargets(Context.RHICmdList, 3, RenderTargets, FTextureRHIParamRef(), 0, NULL);
+	}
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessGatherDOFSetupPS> PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context);
+	}
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget0.TargetableTexture, DestRenderTarget0.ShaderResourceTexture, false, FResolveParams());
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget1.TargetableTexture, DestRenderTarget1.ShaderResourceTexture, false, FResolveParams());
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget2.TargetableTexture, DestRenderTarget2.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFSetup::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	Ret.Extent /= 2;
+	Ret.Extent.X = FMath::Max(1, Ret.Extent.X);
+	Ret.Extent.Y = FMath::Max(1, Ret.Extent.Y);
+
+	switch (InPassOutputId) {
+
+	case ePId_Output0:
+		// Far
+		Ret.Format = PF_FloatRGBA;
+		Ret.DebugName = TEXT("GatherDOFSetupFar");
+		break;
+
+	case ePId_Output1:
+		// Near
+		Ret.Format = PF_FloatRGBA;
+		Ret.DebugName = TEXT("GatherDOFSetupNear");
+		break;
+
+	case ePId_Output2:
+		// Max Near CoC tilemap
+		Ret.Format = PF_G8;
+		Ret.DebugName = TEXT("GatherDOFSetupNearTilemap");
+		break;
+	}
+
+	return Ret;
+}
+
+
+
+
+//----------------------------------------------
+// Extracting near and far fields
+//----------------------------------------------
+
+class PostProcessBokehDOFGatherExtractAreaPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherExtractAreaPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherExtractAreaPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherExtractAreaPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+
+			// in rendertarget pixels (half res to scene color)
+			FRenderingCompositeOutput* Output = Context.Pass->GetOutput(ePId_Output0);
+
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainExtractAreaPS");
+	}
+};
+
+IMPLEMENT_SHADER_TYPE3(PostProcessBokehDOFGatherExtractAreaPS, SF_Pixel);
+
+
+void FRCPassPostProcessGatherDOFExtractArea::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherExtractArea);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget0 = PassOutputs[0].RequestSurface(Context);
+	const FSceneRenderTargetItem& DestRenderTarget1 = PassOutputs[1].RequestSurface(Context);
+	const FSceneRenderTargetItem& DestRenderTarget2 = PassOutputs[2].RequestSurface(Context);
+
+	// Set the view family's render target/viewport.
+	FTextureRHIParamRef RenderTargets[3] =
+	{
+		DestRenderTarget0.TargetableTexture,
+		DestRenderTarget1.TargetableTexture,
+		DestRenderTarget2.TargetableTexture
+	};
+	//@todo Ronin find a way to use the same codepath for all platforms.
+	const EShaderPlatform ShaderPlatform = GShaderPlatformForFeatureLevel[Context.GetFeatureLevel()];
+	if (IsVulkanMobilePlatform(ShaderPlatform))
+	{
+		SetRenderTargets(Context.RHICmdList, 3, RenderTargets, FTextureRHIParamRef(), ESimpleRenderTargetMode::EClearColorAndDepth, FExclusiveDepthStencil());
+	}
+	else
+	{
+		SetRenderTargets(Context.RHICmdList, 3, RenderTargets, FTextureRHIParamRef(), 0, NULL);
+	}
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherExtractAreaPS> PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context);
+	}
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget0.TargetableTexture, DestRenderTarget0.ShaderResourceTexture, false, FResolveParams());
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget1.TargetableTexture, DestRenderTarget1.ShaderResourceTexture, false, FResolveParams());
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget2.TargetableTexture, DestRenderTarget2.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFExtractArea::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	// more precision for additive blending
+	Ret.Format = PF_FloatRGBA;
+
+
+
+	// we need space for the front part and the back part
+	////Ret.Extent.Y = HalfRes * 2 + SafetyBorder;
+	Ret.DebugName = (InPassOutputId == ePId_Output0) ? TEXT("GatherDOFExtract_Far") : TEXT("GatherDOFExtract_Near");
+
+	// Extra target with near depth for tiling
+	if (InPassOutputId == ePId_Output2) {
+		Ret.Format = PF_G8;
+		Ret.DebugName = TEXT("GatherDOFNearTileMap");
+	}
+
+	return Ret;
+}
+
+
+//----------------------------------------------
+// Downscale Tilemap
+//----------------------------------------------
+
+class PostProcessBokehDOFGatherDownscaleTMPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherDownscaleTMPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherDownscaleTMPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherDownscaleTMPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainDownscaleTMPS");
+	}
+};
+
+IMPLEMENT_SHADER_TYPE3(PostProcessBokehDOFGatherDownscaleTMPS, SF_Pixel);
+
+
+void FRCPassPostProcessGatherDOFDownscaleTM::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherDownscaleTM);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = FIntRect::DivideAndRoundUp(SrcRect, 2);
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherDownscaleTMPS> PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context);
+	}
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFDownscaleTM::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	Ret.Extent /= 2;
+	Ret.Extent.X = FMath::Max(1, Ret.Extent.X);
+	Ret.Extent.Y = FMath::Max(1, Ret.Extent.Y);
+
+	// more precision for additive blending
+	Ret.Format = PF_G8;
+
+	Ret.DebugName = TEXT("GatherDOFDownscaleTM");
+
+	return Ret;
+}
+
+//-------------------------------------------------------------
+// Make Max CoC tilemap bleed onto transparent neighborhood
+//-------------------------------------------------------------
+
+class PostProcessBokehDOFGatherNearHaloPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherNearHaloPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherNearHaloPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherNearHaloPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainNearHaloPS");
+	}
+};
+
+IMPLEMENT_SHADER_TYPE3(PostProcessBokehDOFGatherNearHaloPS, SF_Pixel);
+
+
+void FRCPassPostProcessGatherDOFNearHalo::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherNearHalo);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherNearHaloPS> PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context);
+	}
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFNearHalo::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	// more precision for additive blending
+	Ret.Format = PF_G8;
+
+	Ret.DebugName = TEXT("GatherDOFNearHalo");
+
+	return Ret;
+}
+
+//-------------------------------------------------------------
+// 1-pixel border to make color bleed on transparent pixel
+// Poor-man way since pre-multiplied alpha doesn't play nice with 
+// alpha boost in the main blur pass.
+//-------------------------------------------------------------
+
+class PostProcessBokehDOFGatherNearBorderPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherNearBorderPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherNearBorderPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherNearBorderPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainNearBorderPS");
+	}
+};
+
+IMPLEMENT_SHADER_TYPE3(PostProcessBokehDOFGatherNearBorderPS, SF_Pixel);
+
+
+void FRCPassPostProcessGatherDOFNearBorder::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherNearBorder);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherNearBorderPS> PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context);
+	}
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFNearBorder::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	// more precision for additive blending
+	Ret.Format = PF_FloatRGBA;
+
+	Ret.DebugName = TEXT("GatherDOFNearBorder");
+
+	return Ret;
+}
+
+// Returns quality level for GatherDOF
+static uint32 GetGatherDOFQuality()
+{
+	static const auto DofQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GatherDOF.Quality"));
+	return (DofQuality->GetValueOnRenderThread() > 0) ? 1 : 0;
+}
+
+// Returns sprite rotation for GatherDOF
+static float GetGatherDOFRotation()
+{
+	static const auto DofRotation = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.GatherDOF.Rotation"));
+	return DofRotation->GetValueOnRenderThread() / 180.0f * PI;
+}
+
+// Returns the number of edges for the bokeh shape
+static float GetGatherDOFEdgeCount()
+{
+	static const auto DofEdgeCount = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GatherDOF.EdgeCount"));
+	return DofEdgeCount->GetValueOnRenderThread();
+}
+
+static float GetGatherDOFRadiusScale()
+{
+	static const auto RadiusScale = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.GatherDOF.RadiusScale"));
+	return RadiusScale->GetValueOnRenderThread() * 0.6f; // 0.46876f; // Allows keeping 1 as default CVar value
+}
+
+//----------------------------------------------
+// Blur one field
+//----------------------------------------------
+
+template <uint32 HighQuality>
+class PostProcessBokehDOFGatherBlurFarPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherBlurFarPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
+	{
+		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
+		OutEnvironment.SetDefine(TEXT("HIGH_QUALITY"), HighQuality);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherBlurFarPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+	FShaderParameter KernelSize;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherBlurFarPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+		KernelSize.Bind(Initializer.ParameterMap, TEXT("KernelSize"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams << KernelSize;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context, float PixelKernelSize)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+
+			FVector4 KernelSizeValue(PixelKernelSize * GetGatherDOFRadiusScale(), GetGatherDOFRotation(), GetGatherDOFEdgeCount(), 0);
+			SetShaderValue(Context.RHICmdList, ShaderRHI, KernelSize, KernelSizeValue);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainBlurFarPS");
+	}
+};
+
+// #define avoids a lot of code duplication
+#define VARIATION1(A) typedef PostProcessBokehDOFGatherBlurFarPS<A> PostProcessBokehDOFGatherBlurFarPS##A; \
+	IMPLEMENT_SHADER_TYPE2(PostProcessBokehDOFGatherBlurFarPS##A, SF_Pixel);
+
+VARIATION1(0)			VARIATION1(1)
+#undef VARIATION1
+
+template <uint32 HighQuality>
+void FRCPassPostProcessGatherDOFBlurFar::SetShader(const FRenderingCompositePassContext& Context, float PixelKernelSize)
+{
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherBlurFarPS<HighQuality> > PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context, PixelKernelSize);
+	}
+}
+
+void FRCPassPostProcessGatherDOFBlurFar::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherBlurFar);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	uint32 ScaleToFullRes = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+	FIntRect LocalViewRect = View.ViewRect / ScaleToFullRes;
+	FIntPoint LocalViewSize = LocalViewRect.Size();
+	float PixelKernelSize = Context.View.FinalPostProcessSettings.DepthOfFieldMaxBokehSize / 100.0f * LocalViewSize.X;
+
+	if (GetGatherDOFQuality() > 0)
+	{
+		SetShader<1>(Context, PixelKernelSize);
+	}
+	else
+	{
+		SetShader<0>(Context, PixelKernelSize);
+	}
+
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFBlurFar::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	// more precision for additive blending
+	Ret.Format = PF_FloatRGBA;
+
+	Ret.DebugName = TEXT("GatherDOFBlurFar");
+
+	return Ret;
+}
+
+
+//----------------------------------------------
+// Blur near field
+//----------------------------------------------
+
+template <uint32 HighQuality>
+class PostProcessBokehDOFGatherBlurNearPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherBlurNearPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
+	{
+		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
+		OutEnvironment.SetDefine(TEXT("HIGH_QUALITY"), HighQuality);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherBlurNearPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+	FShaderParameter KernelSize;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherBlurNearPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+		KernelSize.Bind(Initializer.ParameterMap, TEXT("KernelSize"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams << KernelSize;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context, float PixelKernelSize)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+
+			FVector4 KernelSizeValue(PixelKernelSize * GetGatherDOFRadiusScale(), GetGatherDOFRotation(), GetGatherDOFEdgeCount(), 0);
+			SetShaderValue(Context.RHICmdList, ShaderRHI, KernelSize, KernelSizeValue);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainBlurNearPS");
+	}
+};
+
+// #define avoids a lot of code duplication
+#define VARIATION1(A) typedef PostProcessBokehDOFGatherBlurNearPS<A> PostProcessBokehDOFGatherBlurNearPS##A; \
+	IMPLEMENT_SHADER_TYPE2(PostProcessBokehDOFGatherBlurNearPS##A, SF_Pixel);
+
+VARIATION1(0)			VARIATION1(1)
+#undef VARIATION1
+
+template <uint32 HighQuality>
+void FRCPassPostProcessGatherDOFBlurNear::SetShader(const FRenderingCompositePassContext& Context, float PixelKernelSize)
+{
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherBlurNearPS<HighQuality> > PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context, PixelKernelSize);
+	}
+}
+
+void FRCPassPostProcessGatherDOFBlurNear::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherBlurNear);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	uint32 ScaleToFullRes = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+	FIntRect LocalViewRect = View.ViewRect / ScaleToFullRes;
+	FIntPoint LocalViewSize = LocalViewRect.Size();
+	float PixelKernelSize = Context.View.FinalPostProcessSettings.DepthOfFieldMaxBokehSize / 100.0f * LocalViewSize.X;
+
+	if (GetGatherDOFQuality() > 0)
+	{
+		SetShader<1>(Context, PixelKernelSize);
+	}
+	else
+	{
+		SetShader<0>(Context, PixelKernelSize);
+	}
+
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFBlurNear::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	// more precision for additive blending
+	Ret.Format = PF_FloatRGBA;
+
+	Ret.DebugName = TEXT("GatherDOFBlurNear");
+
+	return Ret;
+}
+
+//----------------------------------------------
+// Floodfill gaps in the blur sampling
+//----------------------------------------------
+
+template <uint32 HighQuality>
+class PostProcessBokehDOFGatherFloodfillPS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherFloodfillPS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
+	{
+		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
+		OutEnvironment.SetDefine(TEXT("HIGH_QUALITY"), HighQuality & 0x1);
+		OutEnvironment.SetDefine(TEXT("COC_TILEMAP"),  (HighQuality & 0x2)? 1 : 0);
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherFloodfillPS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+	FShaderParameter KernelSize;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherFloodfillPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+		KernelSize.Bind(Initializer.ParameterMap, TEXT("KernelSize"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams << KernelSize;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context, float PixelKernelSize)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+
+			FVector4 KernelSizeValue(PixelKernelSize * GetGatherDOFRadiusScale(), GetGatherDOFRotation(), GetGatherDOFEdgeCount(), 0);
+			SetShaderValue(Context.RHICmdList, ShaderRHI, KernelSize, KernelSizeValue);
+		}
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainFloodfillPS");
+	}
+};
+
+// #define avoids a lot of code duplication
+#define VARIATION1(A) typedef PostProcessBokehDOFGatherFloodfillPS<A> PostProcessBokehDOFGatherFloodfillPS##A; \
+	IMPLEMENT_SHADER_TYPE2(PostProcessBokehDOFGatherFloodfillPS##A, SF_Pixel);
+
+VARIATION1(0)			VARIATION1(1)			VARIATION1(2)			VARIATION1(3)
+#undef VARIATION1
+
+template <uint32 HighQuality>
+void FRCPassPostProcessGatherDOFFloodfill::SetShader(const FRenderingCompositePassContext& Context, float PixelKernelSize)
+{
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherFloodfillPS<HighQuality> > PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context, PixelKernelSize);
+	}
+}
+
+void FRCPassPostProcessGatherDOFFloodfill::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherFloodfill);
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, DestSize.X, DestSize.Y, 1.0f);
+
+	uint32 ScaleToFullRes = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+	FIntRect LocalViewRect = View.ViewRect / ScaleToFullRes;
+	FIntPoint LocalViewSize = LocalViewRect.Size();
+	float PixelKernelSize = Context.View.FinalPostProcessSettings.DepthOfFieldMaxBokehSize / 100.0f * LocalViewSize.X;
+
+	if (GetGatherDOFQuality() > 0)
+	{
+		if (bUseCoCTilemap) SetShader<3>(Context, PixelKernelSize); else SetShader<1>(Context, PixelKernelSize);
+	}
+	else
+	{
+		//SetShader<0>(Context, PixelKernelSize);
+		if (bUseCoCTilemap) SetShader<2>(Context, PixelKernelSize); else SetShader<0>(Context, PixelKernelSize);
+	}
+
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFFloodfill::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.Reset();
+	// more precision for additive blending
+	Ret.Format = PF_FloatRGBA;
+
+	Ret.DebugName = TEXT("GatherDOFFloodfill");
+
+	return Ret;
+}
+
+//-----------------------------------------------------------
+// Recombine far + in-focus + near + separate translucency
+//-----------------------------------------------------------
+
+template <uint32 Method>
+class PostProcessBokehDOFGatherRecombinePS : public FGlobalShader
+{
+	DECLARE_SHADER_TYPE(PostProcessBokehDOFGatherRecombinePS, Global);
+
+	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
+	{
+		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
+	}
+
+	static int32 GetCombineFeatureMethod()
+	{
+		if (Method <= 2)
+		{
+			return Method;
+		}
+		else
+		{
+			return Method - 1;
+		}
+	}
+
+	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
+	{
+		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
+		OutEnvironment.SetDefine(TEXT("RECOMBINE_METHOD"), GetCombineFeatureMethod());
+	}
+
+	/** Default constructor. */
+	PostProcessBokehDOFGatherRecombinePS() {}
+
+public:
+	FPostProcessPassParameters PostprocessParameter;
+	FSceneTextureShaderParameters SceneTextureParameters;
+	FShaderParameter DepthOfFieldParams;
+	FShaderParameter SeparateTranslucencyResMultParam;
+	FShaderResourceParameter BilinearClampedSampler;
+	FShaderResourceParameter PointClampedSampler;
+
+	/** Initialization constructor. */
+	PostProcessBokehDOFGatherRecombinePS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
+		: FGlobalShader(Initializer)
+	{
+		PostprocessParameter.Bind(Initializer.ParameterMap);
+		SceneTextureParameters.Bind(Initializer);
+		DepthOfFieldParams.Bind(Initializer.ParameterMap, TEXT("DepthOfFieldParams"));
+		SeparateTranslucencyResMultParam.Bind(Initializer.ParameterMap, TEXT("SeparateTranslucencyResMult"));
+		BilinearClampedSampler.Bind(Initializer.ParameterMap, TEXT("BilinearClampedSampler"));
+		PointClampedSampler.Bind(Initializer.ParameterMap, TEXT("PointClampedSampler"));
+	}
+
+	// FShader interface.
+	virtual bool Serialize(FArchive& Ar) override
+	{
+		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
+		Ar << PostprocessParameter << SceneTextureParameters << DepthOfFieldParams << SeparateTranslucencyResMultParam << BilinearClampedSampler << PointClampedSampler;
+		return bShaderHasOutdatedParameters;
+	}
+
+	void SetParameters(const FRenderingCompositePassContext& Context)
+	{
+		const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();
+
+		FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
+
+		SceneTextureParameters.Set(Context.RHICmdList, ShaderRHI, Context.View.FeatureLevel, ESceneTextureSetupMode::All);
+		PostprocessParameter.SetPS(Context.RHICmdList, ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+
+		FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(Context.RHICmdList);
+		FIntPoint OutScaledSize;
+		float OutScale;
+		SceneContext.GetSeparateTranslucencyDimensions(OutScaledSize, OutScale);
+
+		SetShaderValue(Context.RHICmdList, ShaderRHI, SeparateTranslucencyResMultParam, FVector4(OutScale, OutScale, OutScale, OutScale));
+
+		{
+			FVector4 DepthOfFieldParamValues[2];
+			FRCPassPostProcessBokehDOF::ComputeDepthOfFieldParams(Context, DepthOfFieldParamValues);
+			SetShaderValueArray(Context.RHICmdList, ShaderRHI, DepthOfFieldParams, DepthOfFieldParamValues, 2);
+		}
+
+		SetSamplerParameter(Context.RHICmdList, ShaderRHI, BilinearClampedSampler, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+		SetSamplerParameter(Context.RHICmdList, ShaderRHI, PointClampedSampler, TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
+	}
+
+	static const TCHAR* GetSourceFilename()
+	{
+		return TEXT("/Engine/Private/PostProcessGatherDOF.usf");
+	}
+
+	static const TCHAR* GetFunctionName()
+	{
+		return TEXT("MainRecombinePS");
+	}
+};
+
+// #define avoids a lot of code duplication
+#define VARIATION1(A) typedef PostProcessBokehDOFGatherRecombinePS<A> PostProcessBokehDOFGatherRecombinePS##A; \
+	IMPLEMENT_SHADER_TYPE2(PostProcessBokehDOFGatherRecombinePS##A, SF_Pixel);
+
+VARIATION1(1)			VARIATION1(2)
+#undef VARIATION1
+
+template <uint32 Method>
+void FRCPassPostProcessGatherDOFRecombine::SetShader(const FRenderingCompositePassContext& Context)
+{
+	FGraphicsPipelineStateInitializer GraphicsPSOInit;
+	Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
+
+	// set the state
+	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
+	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
+	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
+
+	// setup shader
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
+	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
+	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
+
+	{
+		TShaderMapRef<PostProcessBokehDOFGatherRecombinePS<Method> > PixelShader(Context.GetShaderMap());
+
+		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
+		SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);
+
+		VertexShader->SetParameters(Context);
+		PixelShader->SetParameters(Context);
+	}
+}
+
+void FRCPassPostProcessGatherDOFRecombine::Process(FRenderingCompositePassContext& Context)
+{
+	SCOPED_DRAW_EVENT(Context.RHICmdList, BokehGatherRecombine);
+
+	uint32 Method = 1;
+
+	// Separate translucency provided
+	if (GetInput(ePId_Input3)->GetPass()) {
+		Method = 2;
+	}
+
+	const FPooledRenderTargetDesc* InputDesc = GetInputDesc(ePId_Input0);
+
+	if (!InputDesc)
+	{
+		// input is not hooked up correctly
+		return;
+	}
+
+	const FViewInfo& View = Context.View;
+	const FSceneViewFamily& ViewFamily = *(View.Family);
+
+	FIntPoint SrcSize = InputDesc->Extent;
+	FIntPoint DestSize = PassOutputs[0].RenderTargetDesc.Extent;
+
+	// e.g. 4 means the input texture is 4x smaller than the buffer size
+	uint32 ScaleFactor = FSceneRenderTargets::Get(Context.RHICmdList).GetBufferSizeXY().X / SrcSize.X;
+
+	FIntRect SrcRect = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleFactor);
+	FIntRect DestRect = SrcRect;
+
+	const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurface(Context);
+
+	SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());
+
+	Context.SetViewportAndCallRHI(0, 0, 0.0f, PassOutputs[0].RenderTargetDesc.Extent.X, PassOutputs[0].RenderTargetDesc.Extent.Y, 1.0f);
+
+	switch (Method)
+	{
+		case 1: SetShader<1>(Context); break;
+		case 2: SetShader<2>(Context); break;
+		default:
+		check(0);
+	}
+
+	TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
+
+	DrawPostProcessPass(
+		Context.RHICmdList,
+		DestRect.Min.X, DestRect.Min.Y,
+		DestRect.Width(), DestRect.Height(),
+		SrcRect.Min.X, SrcRect.Min.Y,
+		SrcRect.Width(), SrcRect.Height(),
+		DestSize,
+		SrcSize,
+		*VertexShader,
+		View.StereoPass,
+		Context.HasHmdMesh(),
+		EDRF_UseTriangleOptimization);
+
+	Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
+}
+
+FPooledRenderTargetDesc FRCPassPostProcessGatherDOFRecombine::ComputeOutputDesc(EPassOutputId InPassOutputId) const
+{
+	FPooledRenderTargetDesc Ret = GetInput(ePId_Input0)->GetOutput()->RenderTargetDesc;
+
+	Ret.DebugName = TEXT("GatherDOFRecombine");
+
+	return Ret;
+}
diff --git a/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.h b/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.h
index 2ca0ad3aa06..0658da05229 100644
--- a/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.h
+++ b/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessBokehDOF.h
@@ -85,3 +85,160 @@ private:
 	// border between front and back layer as we don't use viewports (only possible with GS)
 	const static uint32 SafetyBorder = 40;
 };
+
+//------------------------------------------------------------------
+//  GatherDOF 
+//------------------------------------------------------------------
+
+// From full-res scene, extract near and far field + max NeaCoC tilemap
+// ePId_Input0: Color input
+// ePId_Input1: Depth input
+// ePId_Output0: Far field color + CoC
+// ePId_Output1: Near field CoC + CoC
+// ePId_Output2: Near Max CoC tilemap
+class FRCPassPostProcessGatherDOFSetup : public TRenderingCompositePassBase<2, 3>
+{
+public:
+	// interface FRenderingCompositePass ---------
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+};
+
+// Extract near field / far field from half-res input scene (from BokehDOFSetup)
+// ePId_Input0: Half res scene with depth in alpha
+// ePId_Output1: far field  (color + CoC)
+// ePId_Output2: near field (color + CoC)
+class FRCPassPostProcessGatherDOFExtractArea : public TRenderingCompositePassBase<1, 3>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	//static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+};
+
+// Downscale max NearCoC tilemap
+class FRCPassPostProcessGatherDOFDownscaleTM : public TRenderingCompositePassBase<1, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+};
+
+// Halo for near field tilemap
+class FRCPassPostProcessGatherDOFNearHalo : public TRenderingCompositePassBase<1, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+};
+
+// Color border for near field
+class FRCPassPostProcessGatherDOFNearBorder : public TRenderingCompositePassBase<1, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+};
+
+// Blur far field with a flat-kernel following a bokeh shape
+// ePId_Input0: far field color + CoC
+// ePId_Output1: blurred field color + alpha
+class FRCPassPostProcessGatherDOFBlurFar : public TRenderingCompositePassBase<1, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+private:
+	template <uint32 HighQuality>
+	static void SetShader(const FRenderingCompositePassContext& Context, float PixelKernelSize);
+};
+
+// Blur near field with a flat-kernel following a bokeh shape
+// ePId_Input0: near field color + CoC
+// ePId_Output1: blurred field color + alpha
+class FRCPassPostProcessGatherDOFBlurNear : public TRenderingCompositePassBase<2, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+private:
+	template <uint32 HighQuality>
+	static void SetShader(const FRenderingCompositePassContext& Context, float PixelKernelSize);
+};
+
+// Floodfill sampling gaps from a previous blur
+// ePId_Input0: near field color + alpha
+// ePId_Input1: max NearCoC tilemap (optional)
+// ePId_Output1: blurred field color + alpha
+class FRCPassPostProcessGatherDOFFloodfill : public TRenderingCompositePassBase<2, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+	FRCPassPostProcessGatherDOFFloodfill(bool useCoCTilemap)
+	{
+		bUseCoCTilemap = useCoCTilemap;
+	}
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+
+	bool bUseCoCTilemap;
+
+private:
+	template <uint32 HighQuality>
+	static void SetShader(const FRenderingCompositePassContext& Context, float PixelKernelSize);
+};
+
+// Recompose the layers far / in-focus / near
+// ePId_Input0: original full res scene
+// ePId_Input1: far field color + alpha
+// ePId_Input2: near field color + alpha
+// ePId_Input3: optional SeparateTranslucency
+// ePId_Output1: final full-res scene with DoF effect applied
+class FRCPassPostProcessGatherDOFRecombine : public TRenderingCompositePassBase<4, 1>
+{
+public:
+	// interface FRenderingCompositePass ---------
+
+	virtual void Process(FRenderingCompositePassContext& Context) override;
+	virtual void Release() override { delete this; }
+	virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
+
+	static void ComputeDepthOfFieldParams(const FRenderingCompositePassContext& Context, FVector4 Out[2]);
+private:
+	template <uint32 Method>
+	static void SetShader(const FRenderingCompositePassContext& Context);
+};
diff --git a/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp b/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
index 4fda27ad781..cba9e030402 100644
--- a/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
+++ b/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
@@ -218,6 +218,65 @@ TAutoConsoleVariable<int32> CVarUseDiaphragmDOF(
 	TEXT(" 1: Diaphragm DOF (default).\n"),
 	ECVF_RenderThreadSafe);
 
+TAutoConsoleVariable<int32> CVarUseGatherDOF(
+	TEXT("r.GatherDOF.Enable"),
+	0,
+	TEXT("Enable/disable GatherDOF override for replacing BokehDOF."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFQuality(
+	TEXT("r.GatherDOF.Quality"),
+	1,
+	TEXT("High (1) or low (0) quality for GatherDOF. This can increase tap count and reduce noise."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<float> CVarGatherDOFRadiusScale(
+	TEXT("r.GatherDOF.RadiusScale"),
+	1.0f,
+	TEXT("Scale factor to apply to the CoC radius value."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<float> CVarGatherDOFRotation(
+	TEXT("r.GatherDOF.Rotation"),
+	0.0f,
+	TEXT("Rotation to apply to the polygonal bokeh shape, in degrees."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFEdgeCount(
+	TEXT("r.GatherDOF.EdgeCount"),
+	6,
+	TEXT("Number of edges in the polygonal bokeh shape. 0 for circle, 4 for square, 6 for hexagon..."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFFloodfillFar(
+	TEXT("r.GatherDOF.Floodfill.Far"),
+	1,
+	TEXT("Apply filter to smooth-out the far field."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFFloodfillNear(
+	TEXT("r.GatherDOF.Floodfill.Near"),
+	1,
+	TEXT("Apply filter to smooth-out the near field."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFFarField(
+	TEXT("r.GatherDOF.FarField"),
+	1,
+	TEXT("Apply DoF to the far field."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFNearField(
+	TEXT("r.GatherDOF.NearField"),
+	1,
+	TEXT("Apply DoF to the near field."),
+	ECVF_RenderThreadSafe);
+
+TAutoConsoleVariable<int32> CVarGatherDOFNoTAA(
+	TEXT("r.GatherDOF.NoTAA"),
+	0,
+	TEXT("Even if Temporal-AA is globally on, do not use it for smoothing DoF. (faster)"),
+	ECVF_RenderThreadSafe);
 
 IMPLEMENT_SHADER_TYPE(,FPostProcessVS,TEXT("/Engine/Private/PostProcessBloom.usf"),TEXT("MainPostprocessCommonVS"),SF_Vertex);
 
@@ -513,44 +572,189 @@ static void AddVisualizeBloomOverlay(FPostprocessContext& Context, FRenderingCom
 
 static void AddPostProcessDepthOfFieldBokeh(FPostprocessContext& Context, FRenderingCompositeOutputRef& SeparateTranslucency, FRenderingCompositeOutputRef& VelocityInput)
 {
-	// downsample, mask out the in focus part, depth in alpha
-	const bool bIsComputePass = ShouldDoComputePostProcessing(Context.View);
-	FRenderingCompositePass* DOFSetup = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFSetup(bIsComputePass));
-	DOFSetup->SetInput(ePId_Input0, FRenderingCompositeOutputRef(Context.FinalOutput));
-	DOFSetup->SetInput(ePId_Input1, FRenderingCompositeOutputRef(Context.SceneDepth));
+	const float bUseGatherDOF = CVarUseGatherDOF.GetValueOnRenderThread();
 
-	FSceneViewState* ViewState = (FSceneViewState*)Context.View.State;
-	
-	FRenderingCompositePass* DOFInputPass = DOFSetup;
-	if( Context.View.AntiAliasingMethod == AAM_TemporalAA && ViewState )
-	{
-		FTAAPassParameters Parameters(Context.View);
-		Parameters.Pass = ETAAPassConfig::LegacyDepthOfField;
-		Parameters.bIsComputePass = bIsComputePass;
-		Parameters.SetupViewRect(Context.View, /* ResolutionDivisor = */ 2);
+	if (!bUseGatherDOF)
+	{ // Original BokehDOF flow
 
-		FRenderingCompositePass* NodeTemporalAA = Context.Graph.RegisterPass( new(FMemStack::Get()) FRCPassPostProcessTemporalAA(
-			Context, Parameters,
-			ViewState->DOFHistory,
-			&ViewState->DOFHistory) );
-		NodeTemporalAA->SetInput( ePId_Input0, DOFSetup );
-		NodeTemporalAA->SetInput( ePId_Input2, VelocityInput );
+		// downsample, mask out the in focus part, depth in alpha
+		const bool bIsComputePass = ShouldDoComputePostProcessing(Context.View);
+		FRenderingCompositePass* DOFSetup = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFSetup(bIsComputePass));
+		DOFSetup->SetInput(ePId_Input0, FRenderingCompositeOutputRef(Context.FinalOutput));
+		DOFSetup->SetInput(ePId_Input1, FRenderingCompositeOutputRef(Context.SceneDepth));
 
-		DOFInputPass = NodeTemporalAA;
-		ViewState->bDOFHistory = true;
+		FSceneViewState* ViewState = (FSceneViewState*)Context.View.State;
+
+		FRenderingCompositePass* DOFInputPass = DOFSetup;
+		if (Context.View.AntiAliasingMethod == AAM_TemporalAA && ViewState)
+		{
+			FTAAPassParameters Parameters(Context.View);
+			Parameters.Pass = ETAAPassConfig::LegacyDepthOfField;
+			Parameters.bIsComputePass = bIsComputePass;
+			Parameters.SetupViewRect(Context.View, /* ResolutionDivisor = */ 2);
+
+			FRenderingCompositePass* NodeTemporalAA = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessTemporalAA(
+				Context, Parameters,
+				ViewState->DOFHistory,
+				&ViewState->DOFHistory));
+			NodeTemporalAA->SetInput(ePId_Input0, DOFSetup);
+			NodeTemporalAA->SetInput(ePId_Input2, VelocityInput);
+
+			DOFInputPass = NodeTemporalAA;
+			ViewState->bDOFHistory = true;
+		}
+
+		FRenderingCompositePass* NodeBlurred = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOF());
+		NodeBlurred->SetInput(ePId_Input0, DOFInputPass);
+		NodeBlurred->SetInput(ePId_Input1, Context.SceneColor);
+		NodeBlurred->SetInput(ePId_Input2, Context.SceneDepth);
+
+		FRenderingCompositePass* NodeRecombined = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFRecombine(bIsComputePass));
+		NodeRecombined->SetInput(ePId_Input0, Context.FinalOutput);
+		NodeRecombined->SetInput(ePId_Input1, NodeBlurred);
+		NodeRecombined->SetInput(ePId_Input2, SeparateTranslucency);
+
+		Context.FinalOutput = FRenderingCompositeOutputRef(NodeRecombined);
 	}
+	else {
 
-	FRenderingCompositePass* NodeBlurred = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOF());
-	NodeBlurred->SetInput(ePId_Input0, DOFInputPass);
-	NodeBlurred->SetInput(ePId_Input1, Context.SceneColor);
-	NodeBlurred->SetInput(ePId_Input2, Context.SceneDepth);
+		FSceneViewState* ViewState = (FSceneViewState*)Context.View.State;
 
-	FRenderingCompositePass* NodeRecombined = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFRecombine(bIsComputePass));
-	NodeRecombined->SetInput(ePId_Input0, Context.FinalOutput);
-	NodeRecombined->SetInput(ePId_Input1, NodeBlurred);
-	NodeRecombined->SetInput(ePId_Input2, SeparateTranslucency);
+		bool doTAA = (Context.View.AntiAliasingMethod == AAM_TemporalAA && ViewState);
+		if (CVarGatherDOFNoTAA.GetValueOnRenderThread() == 1) doTAA = false;
+
+		// Gather DOF flow
+		FRenderingCompositePass* DOFinitialization;
+
+		if (!doTAA)
+		{
+			// Optimized path without support for TAA, prepare all fields in a single pass
+			// From full resolution scene and depth, extract at half-res near and far fields (color + CoC) and max NearCoC tilemap.
+			FRenderingCompositePass* DOFSetup = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFSetup());
+			DOFSetup->SetInput(ePId_Input0, FRenderingCompositeOutputRef(Context.FinalOutput));
+			DOFSetup->SetInput(ePId_Input1, FRenderingCompositeOutputRef(Context.SceneDepth));
+
+			DOFinitialization = DOFSetup;
+		}
+		else
+		{
+			// First setup phase with TAA, then field extraction
+
+			// downsample, mask out the in focus part, depth in alpha
+			const bool bIsComputePass = ShouldDoComputePostProcessing(Context.View);
+			FRenderingCompositePass* DOFSetup = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFSetup(bIsComputePass));
+			DOFSetup->SetInput(ePId_Input0, FRenderingCompositeOutputRef(Context.FinalOutput));
+			DOFSetup->SetInput(ePId_Input1, FRenderingCompositeOutputRef(Context.SceneDepth));
+
+			FRenderingCompositePass* DOFInputPass = DOFSetup;
+
+			// Performs AA
+			FTAAPassParameters Parameters(Context.View);
+			Parameters.Pass = ETAAPassConfig::LegacyDepthOfField;
+			Parameters.bIsComputePass = bIsComputePass;
+			Parameters.SetupViewRect(Context.View, /* ResolutionDivisor = */ 2);
+			//Parameters.bUseFast = true; // not worth it
+
+			FRenderingCompositePass* NodeTemporalAA = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessTemporalAA(
+				Context, Parameters,
+				ViewState->DOFHistory,
+				&ViewState->DOFHistory));
+			NodeTemporalAA->SetInput(ePId_Input0, DOFSetup);
+			NodeTemporalAA->SetInput(ePId_Input2, VelocityInput);
+
+			DOFInputPass = NodeTemporalAA;
+			ViewState->bDOFHistory = true;
+
+
+			// After TAA is applied, separate near and far field (+ near min/max tile-map)
+			FRenderingCompositePass* GatherDOFSetup = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFExtractArea());
+			GatherDOFSetup->SetInput(ePId_Input0, FRenderingCompositeOutputRef(DOFInputPass));
+
+			DOFinitialization = GatherDOFSetup;
+		}
+
+		FRenderingCompositeOutputRef DOFSetupFar(DOFinitialization, ePId_Output0);
+		FRenderingCompositeOutputRef DOFSetupNear(DOFinitialization, ePId_Output1);
+		FRenderingCompositeOutputRef DOFSetupNearTileMap(DOFinitialization, ePId_Output2);
+
+		//--------------
+		// Far field
+		//--------------
+
+		FRenderingCompositePass* FarFieldFinal = nullptr;
+
+		// Do we process far field at all
+		if (CVarGatherDOFFarField.GetValueOnRenderThread() == 1)
+		{
+			// Perform the multi-tap blur on far field
+			FRenderingCompositePass* FarBlurred = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFBlurFar());
+			FarBlurred->SetInput(ePId_Input0, DOFSetupFar);
+
+			FarFieldFinal = FarBlurred;
+
+			if (CVarGatherDOFFloodfillFar.GetValueOnRenderThread() == 1) {
+				// Floodfill to fix sampling gaps of the previous pass
+				FRenderingCompositePass* FarFloodfilled = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFFloodfill(false));
+				FarFloodfilled->SetInput(ePId_Input0, FarBlurred);
+
+				FarFieldFinal = FarFloodfilled;
+			}
+		}
+		
+
+		//--------------
+		// Near field
+		//--------------
+
+		FRenderingCompositePass* NearFieldFinal = nullptr;
+
+		// Do we process near field at all
+		if (CVarGatherDOFNearField.GetValueOnRenderThread() == 1)
+		{
+			// Downscale max NearCoC tilemap. 1/4th of original scene res.
+			FRenderingCompositePass* DownscaledTileMap = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFDownscaleTM());
+			DownscaledTileMap->SetInput(ePId_Input0, DOFSetupNearTileMap);
+
+			// Downscale tilemap. 1/8th of original scene res.
+			FRenderingCompositePass* DownscaledTileMap2 = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFDownscaleTM());
+			DownscaledTileMap2->SetInput(ePId_Input0, DownscaledTileMap);
+
+			// Halo for near field (introduces bleeding in the max NearCoC tilemap, to avoid hard edges)
+			FRenderingCompositePass* NearHaloTileMap = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFNearHalo());
+			NearHaloTileMap->SetInput(ePId_Input0, DownscaledTileMap2);
+
+			// Slight color bleeding around the border of near field (can't rely on premultiplied alpha)
+			FRenderingCompositePass* NearBorder = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFNearBorder());
+			NearBorder->SetInput(ePId_Input0, DOFSetupNear);
+
+			// Blur the near field
+			FRenderingCompositePass* NearBlurred = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFBlurNear());
+			NearBlurred->SetInput(ePId_Input0, NearBorder);
+			NearBlurred->SetInput(ePId_Input1, NearHaloTileMap);
+
+			NearFieldFinal = NearBlurred;
+
+			if (CVarGatherDOFFloodfillNear.GetValueOnRenderThread() == 1) {
+				// Floodfill to fix sampling gaps of the previous pass
+				FRenderingCompositePass* NearFloodfilled = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFFloodfill(true));
+				NearFloodfilled->SetInput(ePId_Input0, NearBlurred);
+				NearFloodfilled->SetInput(ePId_Input1, NearHaloTileMap);
+
+				NearFieldFinal = NearFloodfilled;
+			}
+		}
+
+		// Combine the 3 layers: far + in-focus + near, and optionally separate translucency
+		FRenderingCompositePass* NodeRecombined = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessGatherDOFRecombine());
+		NodeRecombined->SetInput(ePId_Input0, Context.SceneColor);
+		if (CVarGatherDOFFarField.GetValueOnRenderThread() == 1) NodeRecombined->SetInput(ePId_Input1, FarFieldFinal);
+		if (CVarGatherDOFNearField.GetValueOnRenderThread() == 1) NodeRecombined->SetInput(ePId_Input2, NearFieldFinal);
+		NodeRecombined->SetInput(ePId_Input3, SeparateTranslucency);
+
+		Context.FinalOutput = FRenderingCompositeOutputRef(NodeRecombined);
+
+	}
 
-	Context.FinalOutput = FRenderingCompositeOutputRef(NodeRecombined);
 }
 
 static bool AddPostProcessDepthOfFieldGaussian(FPostprocessContext& Context, FDepthOfFieldStats& Out, FRenderingCompositeOutputRef& VelocityInput, FRenderingCompositeOutputRef& SeparateTranslucencyRef)
-- 
2.14.1.windows.1