UE4后处理:

PostProcessSelectionOutline.usf

// Copyright Epic Games, Inc. All Rights Reserved.

#include "Common.ush"
#include "ScreenPass.ush"

#if MSAA_SAMPLE_COUNT > 1
Texture2DMS<float, MSAA_SAMPLE_COUNT> EditorPrimitivesDepth;
Texture2DMS<uint2, MSAA_SAMPLE_COUNT> EditorPrimitivesStencil;
#else
// Note: opengl compiler doesn't like Texture2D<float>
Texture2D EditorPrimitivesDepth;
Texture2D<uint2> EditorPrimitivesStencil;
#endif

Texture2D ColorTexture;
SamplerState ColorSampler;

Texture2D DepthTexture;
SamplerState DepthSampler;

SCREEN_PASS_TEXTURE_VIEWPORT(Color)
SCREEN_PASS_TEXTURE_VIEWPORT(Depth)
SCREEN_PASS_TEXTURE_VIEWPORT_TRANSFORM(ColorToDepth)

float3 OutlineColor;
float3 SubduedOutlineColor;
float SelectionHighlightIntensity;
float BSPSelectionIntensity;

struct FPixelInfo
{
	// if there is an selected object
	bool bObjectMask;

	int ObjectId;
	// if the current pixel is not occluded by some other object in front of it
	float fVisible;
	// hard version of fVisible
	bool bVisible;
};

// @param DeviceZPlane plane in screenspace .x: ddx(DeviceZ), y: ddy(DeviceZ) z:DeviceZ 
float ReconstructDeviceZ(float3 DeviceZPlane, float2 PixelOffset)
{
	return dot(DeviceZPlane, float3(PixelOffset.xy, 1));
}

// @param DeviceZPlane plane in screenspace .x: ddx(DeviceZ), y: ddy(DeviceZ) z:DeviceZ
FPixelInfo GetPixelInfo(int2 PixelPos, int SampleID, int OffsetX, int OffsetY, float3 DeviceZPlane, float2 DeviceZMinMax)
{
	FPixelInfo Ret;

	PixelPos += int2(OffsetX, OffsetY);

	// more stable on the silhouette
	float2 ReconstructionOffset = 0.5f - View.TemporalAAParams.zw;

#if MSAA_SAMPLE_COUNT > 1
	float DeviceZ = EditorPrimitivesDepth.Load(PixelPos, SampleID).r;
	int Stencil = EditorPrimitivesStencil.Load(PixelPos, SampleID) STENCIL_COMPONENT_SWIZZLE;

#if !COMPILER_GLSL && !PS4_PROFILE && !COMPILER_METAL && !COMPILER_HLSLCC
	// not yet supported on OpenGL, slightly better quality
	ReconstructionOffset += EditorPrimitivesDepth.GetSamplePosition(SampleID);
#endif

#else
	float DeviceZ = EditorPrimitivesDepth.Load(int3(PixelPos, 0)).r;
	int Stencil = EditorPrimitivesStencil.Load(int3(PixelPos, 0)) STENCIL_COMPONENT_SWIZZLE;
#endif

	float SceneDeviceZ = ReconstructDeviceZ(DeviceZPlane, ReconstructionOffset);

	// clamp SceneDeviceZ in (DeviceZMinMax.x .. DeviceZMinMax.z)
	// this avoids flicking artifacts on the silhouette by limiting the depth reconstruction error
	SceneDeviceZ = max(SceneDeviceZ, DeviceZMinMax.x);
	SceneDeviceZ = min(SceneDeviceZ, DeviceZMinMax.y);

	// test against far plane
	Ret.bObjectMask = DeviceZ != 0.0f;

	// outline even between multiple selected objects (best usability)
	Ret.ObjectId = Stencil;

#if (COMPILER_GLSL == 1 && FEATURE_LEVEL < FEATURE_LEVEL_SM4) || (COMPILER_METAL && FEATURE_LEVEL < FEATURE_LEVEL_SM4)
	// Stencil read in opengl is not supported on older versions
	Ret.ObjectId = Ret.bObjectMask ? 2 : 0;
#endif

	// Soft Bias with DeviceZ for best quality (larger bias than usual because SceneDeviceZ is only approximated)
	const float DeviceDepthFade = 0.00005f;

	// 2 to always bias over the current plane
	Ret.fVisible = saturate(2.0f - (SceneDeviceZ - DeviceZ) / DeviceDepthFade);

	Ret.bVisible = Ret.fVisible >= 0.5f;

	return Ret;
}

bool BoolXor(bool bA, bool bB)
{
	int a = (int)bA;
	int b = (int)bB;

	return (a ^ b) != 0;
}

// Computes min and max at once.
void MinMax(inout int2 Var, int Value)
{
	Var.x = min(Var.x, Value);
	Var.y = max(Var.y, Value);
}

float4 PerSample(int2 PixelPos, int SampleID, FPixelInfo CenterPixelInfo, float3 DeviceZPlane, float2 DeviceZMinMax)
{
	float4 Color = 0;

	// [0]:center, [1..4]:borders
	FPixelInfo PixelInfo[5];
	PixelInfo[0] = CenterPixelInfo;

	// Diagonal cross is thicker than vertical/horizontal cross.
	PixelInfo[1] = GetPixelInfo(PixelPos, SampleID,  1,  1, DeviceZPlane, DeviceZMinMax);
	PixelInfo[2] = GetPixelInfo(PixelPos, SampleID, -1, -1, DeviceZPlane, DeviceZMinMax);
	PixelInfo[3] = GetPixelInfo(PixelPos, SampleID,  1, -1, DeviceZPlane, DeviceZMinMax);
	PixelInfo[4] = GetPixelInfo(PixelPos, SampleID, -1,  1, DeviceZPlane, DeviceZMinMax);

	// With (.x != .y) we can detect a border around and between each object.
	int2 BorderMinMax = int2(255,0);
	{
		MinMax(BorderMinMax, PixelInfo[1].ObjectId);
		MinMax(BorderMinMax, PixelInfo[2].ObjectId);
		MinMax(BorderMinMax, PixelInfo[3].ObjectId);
		MinMax(BorderMinMax, PixelInfo[4].ObjectId);
	}

	int2 VisibleBorderMinMax = int2(255,0);
	{
		FLATTEN	if (PixelInfo[1].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[1].ObjectId);
		FLATTEN	if (PixelInfo[2].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[2].ObjectId);
		FLATTEN	if (PixelInfo[3].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[3].ObjectId);
		FLATTEN	if (PixelInfo[4].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[4].ObjectId);
	}

	// this border around the object
	bool bVisibleBorder = VisibleBorderMinMax.y != 0;
	bool bBorder = BorderMinMax.x != BorderMinMax.y;
	bool bInnerBorder = BorderMinMax.y == 4;

	// moving diagonal lines
	float PatternMask = ((PixelPos.x/2 + PixelPos.y/2) % 2) * 0.6f;

	// the contants express the two opacity values we see when the primitive is hidden
	float LowContrastPatternMask = lerp(0.2, 1.0f, PatternMask);

	float3  SelectionColor;
	FLATTEN if (VisibleBorderMinMax.x >= 128 )
	{
		SelectionColor = SubduedOutlineColor.rgb;
	}
	else
	{
		SelectionColor = OutlineColor.rgb;
	}
	FLATTEN if (bBorder)
	{
		FLATTEN if (bVisibleBorder)
		{
			// unoccluded border
			Color = float4(SelectionColor, 1);
		}
		else
		{
			// occluded border
			Color = lerp(float4(0, 0, 0, 0), float4(SelectionColor, 1), LowContrastPatternMask);
		}
	}

	FLATTEN if (bInnerBorder)
	{
		// even occluded object is filled
		// occluded object is rendered differently(flickers with TemporalAA)
		float VisibleMask = lerp(PatternMask, 1.0f, PixelInfo[0].fVisible);

		// darken occluded parts
		Color = lerp(Color, float4(SelectionColor * VisibleMask, 1), SelectionHighlightIntensity);
	}

	// highlight inner part of the object
	if (PixelInfo[0].bObjectMask)
	{
		float VisibleMask = lerp(PatternMask, 1.0f, PixelInfo[0].fVisible);
		float InnerHighlightAmount = PixelInfo[0].ObjectId == 1 ? BSPSelectionIntensity : SelectionHighlightIntensity;
		Color = lerp(Color, float4(SelectionColor, 1), VisibleMask * InnerHighlightAmount);
	}

	return Color;
}

void MainPS(
	noperspective float4 UVAndScreenPos : TEXCOORD0,
	float4 SvPosition : SV_POSITION,
	out float4 OutColor : SV_Target0)
{
	const float2 ColorUV = UVAndScreenPos.xy;
	const int2 ColorPixelPos = int2(ColorUV * Color_Extent);

	// Scout outwards from the center pixel to determine if any neighboring pixels are selected
	const int ScoutStep = 2;

#if MSAA_SAMPLE_COUNT > 1
	const int4 StencilScout = int4(
		EditorPrimitivesStencil.Load(ColorPixelPos + int2( ScoutStep,  0), 0) STENCIL_COMPONENT_SWIZZLE,
		EditorPrimitivesStencil.Load(ColorPixelPos + int2(-ScoutStep,  0), 0) STENCIL_COMPONENT_SWIZZLE,
		EditorPrimitivesStencil.Load(ColorPixelPos + int2( 0,  ScoutStep), 0) STENCIL_COMPONENT_SWIZZLE,
		EditorPrimitivesStencil.Load(ColorPixelPos + int2( 0, -ScoutStep), 0) STENCIL_COMPONENT_SWIZZLE
	);
#else
	const int4 StencilScout = int4(
		EditorPrimitivesStencil.Load(int3(ColorPixelPos + int2( ScoutStep,  0), 0)) STENCIL_COMPONENT_SWIZZLE,
		EditorPrimitivesStencil.Load(int3(ColorPixelPos + int2(-ScoutStep,  0), 0)) STENCIL_COMPONENT_SWIZZLE,
		EditorPrimitivesStencil.Load(int3(ColorPixelPos + int2( 0,  ScoutStep), 0)) STENCIL_COMPONENT_SWIZZLE,
		EditorPrimitivesStencil.Load(int3(ColorPixelPos + int2( 0, -ScoutStep), 0)) STENCIL_COMPONENT_SWIZZLE
	);
#endif

	const int ScoutSum = dot(saturate(StencilScout), 1);

	// If this sum is zero, none of our neighbors are selected pixels and we can skip all the heavy processing.
	if (ScoutSum > 0)
	{
		const float2 DepthUV = ColorUV * ColorToDepth_Scale + ColorToDepth_Bias;

		float Center = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV, 0).r;
		float Left   = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + float2(-1,  0) * Depth_ExtentInverse, 0).r;
		float Right  = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + float2( 1,  0) * Depth_ExtentInverse, 0).r;
		float Top    = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + float2( 0, -1) * Depth_ExtentInverse, 0).r;
		float Bottom = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + float2( 0,  1) * Depth_ExtentInverse, 0).r;

		// This allows to reconstruct depth with a small pixel offset without many texture lookups (4xMSAA * 5 neighbors -> 20 samples)
		// It's an approximation assuming the surface is a plane.
		float3 DeviceZPlane;
		DeviceZPlane.x = (Right - Left) / 2;
		DeviceZPlane.y = (Bottom - Top) / 2;
		DeviceZPlane.z = Center;

		float2 DeviceZMinMax;
		DeviceZMinMax.x = min(Center, min(min(Left, Right), min(Top, Bottom)));
		DeviceZMinMax.y = max(Center, max(max(Left, Right), max(Top, Bottom)));

		FPixelInfo CenterPixelInfo = GetPixelInfo(ColorPixelPos, 0, 0,  0, DeviceZPlane, DeviceZMinMax);

		float4 Sum = 0;
#if COMPILER_GLSL && FEATURE_LEVEL >= FEATURE_LEVEL_SM4
	UNROLL
#endif
		for(int SampleID = 0; SampleID < MSAA_SAMPLE_COUNT; ++SampleID)
		{
			Sum += PerSample(ColorPixelPos, SampleID, CenterPixelInfo, DeviceZPlane, DeviceZMinMax);
		}
		float4 Avg = Sum / MSAA_SAMPLE_COUNT;

		OutColor = Texture2DSample(ColorTexture, ColorSampler, ColorUV);

		// Scene color has gamma applied. Need to remove and re-apply after linear operation.
		OutColor.rgb = pow(OutColor.rgb, 2.2f );
		OutColor.rgb = lerp(OutColor.rgb, Avg.rgb, Avg.a);
		OutColor.rgb = pow(OutColor.rgb, 1.0f / 2.2f);
	}
	else
	{
		OutColor = Texture2DSample(ColorTexture, ColorSampler, ColorUV);
	}
}

PostProcessing.cpp

#if WITH_EDITOR
	if (PassSequence.IsEnabled(EPass::SelectionOutline))
	{
		FSelectionOutlineInputs PassInputs;
		PassSequence.AcceptOverrideIfLastPass(EPass::SelectionOutline, PassInputs.OverrideOutput);
		PassInputs.SceneColor = SceneColor;
		PassInputs.SceneDepth = SceneDepth;
		PassInputs.SceneTextures.SceneTextures = Inputs.SceneTextures;

		SceneColor = AddSelectionOutlinePass(GraphBuilder, View, PassInputs);
	}

	if (PassSequence.IsEnabled(EPass::EditorPrimitive))
	{
		FEditorPrimitiveInputs PassInputs;
		PassSequence.AcceptOverrideIfLastPass(EPass::EditorPrimitive, PassInputs.OverrideOutput);
		PassInputs.SceneColor = SceneColor;
		PassInputs.SceneDepth = SceneDepth;
		PassInputs.BasePassType = FEditorPrimitiveInputs::EBasePassType::Deferred;

		SceneColor = AddEditorPrimitivePass(GraphBuilder, View, PassInputs);
	}
#endif

PostProcessSelectionOutline.cpp


FScreenPassTexture AddSelectionOutlinePass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FSelectionOutlineInputs& Inputs)
{
	check(Inputs.SceneColor.IsValid());
	check(Inputs.SceneDepth.IsValid());

	RDG_EVENT_SCOPE(GraphBuilder, "EditorSelectionOutlines");

	FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(GraphBuilder.RHICmdList);
	FPersistentUniformBuffers& SceneUniformBuffers = View.Family->Scene->GetRenderScene()->UniformBuffers;
	const uint32 MsaaSampleCount = SceneContext.GetEditorMSAACompositingSampleCount();

	// Patch uniform buffers with updated state for rendering the outline mesh draw commands.
	const FViewInfo* EditorView = UpdateEditorPrimitiveView(SceneUniformBuffers, SceneContext, View, Inputs.SceneColor.ViewRect);

	FRDGTextureRef DepthStencilTexture = nullptr;

	// Generate custom depth / stencil for outline shapes.
	{
		{
			FRDGTextureDesc DepthStencilDesc = Inputs.SceneColor.Texture->Desc;
			DepthStencilDesc.Reset();
			DepthStencilDesc.Format = PF_DepthStencil;
			// This is a reversed Z depth surface, so 0.0f is the far plane.
			DepthStencilDesc.ClearValue = FClearValueBinding((float)ERHIZBuffer::FarPlane, 0);
			DepthStencilDesc.Flags = TexCreate_DepthStencilTargetable | TexCreate_ShaderResource;
			DepthStencilDesc.NumSamples = MsaaSampleCount;

			DepthStencilTexture = GraphBuilder.CreateTexture(DepthStencilDesc, TEXT("SelectionOutline"));
		}

		FSelectionOutlinePassParameters* PassParameters = GraphBuilder.AllocParameters<FSelectionOutlinePassParameters>();
		PassParameters->SceneTextures = Inputs.SceneTextures;
		PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(
			DepthStencilTexture,
			ERenderTargetLoadAction::EClear,
			ERenderTargetLoadAction::EClear,
			FExclusiveDepthStencil::DepthWrite_StencilWrite);

		const FScreenPassTextureViewport SceneColorViewport(Inputs.SceneColor);

		GraphBuilder.AddPass(
			RDG_EVENT_NAME("OutlineDepth %dx%d", SceneColorViewport.Rect.Width(), SceneColorViewport.Rect.Height()),
			PassParameters,
			ERDGPassFlags::Raster,
			[&SceneUniformBuffers, EditorView, &View, SceneColorViewport](FRHICommandList& RHICmdList)
		{
			RHICmdList.SetViewport(SceneColorViewport.Rect.Min.X, SceneColorViewport.Rect.Min.Y, 0.0f, SceneColorViewport.Rect.Max.X, SceneColorViewport.Rect.Max.Y, 1.0f);

			SceneUniformBuffers.UpdateViewUniformBufferImmediate(*EditorView->CachedViewUniformShaderParameters);

			// Run selection pass on static elements
			View.ParallelMeshDrawCommandPasses[EMeshPass::EditorSelection].DispatchDraw(nullptr, RHICmdList);

			// to get an outline around the objects if it's partly outside of the screen
			{
				FIntRect InnerRect = SceneColorViewport.Rect;

				// 1 as we have an outline that is that thick
				InnerRect.InflateRect(-1);

				// top
				RHICmdList.SetScissorRect(true, SceneColorViewport.Rect.Min.X, SceneColorViewport.Rect.Min.Y, SceneColorViewport.Rect.Max.X, InnerRect.Min.Y);
				DrawClearQuad(RHICmdList, false, FLinearColor(), true, (float)ERHIZBuffer::FarPlane, true, 0, SceneColorViewport.Extent, FIntRect());
				// bottom
				RHICmdList.SetScissorRect(true, SceneColorViewport.Rect.Min.X, InnerRect.Max.Y, SceneColorViewport.Rect.Max.X, SceneColorViewport.Rect.Max.Y);
				DrawClearQuad(RHICmdList, false, FLinearColor(), true, (float)ERHIZBuffer::FarPlane, true, 0, SceneColorViewport.Extent, FIntRect());
				// left
				RHICmdList.SetScissorRect(true, SceneColorViewport.Rect.Min.X, SceneColorViewport.Rect.Min.Y, InnerRect.Min.X, SceneColorViewport.Rect.Max.Y);
				DrawClearQuad(RHICmdList, false, FLinearColor(), true, (float)ERHIZBuffer::FarPlane, true, 0, SceneColorViewport.Extent, FIntRect());
				// right
				RHICmdList.SetScissorRect(true, InnerRect.Max.X, SceneColorViewport.Rect.Min.Y, SceneColorViewport.Rect.Max.X, SceneColorViewport.Rect.Max.Y);
				DrawClearQuad(RHICmdList, false, FLinearColor(), true, (float)ERHIZBuffer::FarPlane, true, 0, SceneColorViewport.Extent, FIntRect());

				RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
			}
		});
	}

	FScreenPassRenderTarget Output = Inputs.OverrideOutput;

	if (!Output.IsValid())
	{
		Output = FScreenPassRenderTarget::CreateFromInput(GraphBuilder, Inputs.SceneColor, View.GetOverwriteLoadAction(), TEXT("SelectionOutlineColor"));
	}

	// Render selection outlines.
	{
		const FScreenPassTextureViewport OutputViewport(Output);
		const FScreenPassTextureViewport ColorViewport(Inputs.SceneColor);
		const FScreenPassTextureViewport DepthViewport(Inputs.SceneDepth);

		FRHISamplerState* PointClampSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();

		FSelectionOutlinePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSelectionOutlinePS::FParameters>();
		PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
		PassParameters->View = View.ViewUniformBuffer;
		PassParameters->Color = GetScreenPassTextureViewportParameters(ColorViewport);
		PassParameters->Depth = GetScreenPassTextureViewportParameters(DepthViewport);
		PassParameters->ColorToDepth = GetScreenPassTextureViewportTransform(PassParameters->Color, PassParameters->Depth);
		PassParameters->ColorTexture = Inputs.SceneColor.Texture;
		PassParameters->ColorSampler = PointClampSampler;
		PassParameters->DepthTexture = Inputs.SceneDepth.Texture;
		PassParameters->DepthSampler = PointClampSampler;
		PassParameters->EditorPrimitivesDepth = DepthStencilTexture;
		PassParameters->EditorPrimitivesStencil = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateWithPixelFormat(DepthStencilTexture, PF_X24_G8));
		PassParameters->OutlineColor = FVector(View.SelectionOutlineColor);
		PassParameters->SelectionHighlightIntensity = GEngine->SelectionHighlightIntensity;
		PassParameters->SubduedOutlineColor = FVector(View.SubduedSelectionOutlineColor);
		PassParameters->BSPSelectionIntensity = GEngine->BSPSelectionHighlightIntensity;

		FSelectionOutlinePS::FPermutationDomain PermutationVector;
		PermutationVector.Set<FSelectionOutlinePS::FSampleCountDimension>(MsaaSampleCount);

		TShaderMapRef<FSelectionOutlinePS> PixelShader(View.ShaderMap, PermutationVector);

		AddDrawScreenPass(
			GraphBuilder,
			RDG_EVENT_NAME("OutlineColor %dx%d", OutputViewport.Rect.Width(), OutputViewport.Rect.Height()),
			View,
			OutputViewport,
			ColorViewport,
			PixelShader,
			PassParameters);
	}

	return MoveTemp(Output);
}
template <typename PixelShaderType>
FORCEINLINE void AddDrawScreenPass(
	FRDGBuilder& GraphBuilder,
	FRDGEventName&& PassName,
	const FViewInfo& View,
	const FScreenPassTextureViewport& OutputViewport,
	const FScreenPassTextureViewport& InputViewport,
	const TShaderRef<PixelShaderType>& PixelShader,
	typename PixelShaderType::FParameters* PixelShaderParameters,
	EScreenPassDrawFlags Flags = EScreenPassDrawFlags::None)
{
	TShaderMapRef<FScreenPassVS> VertexShader(View.ShaderMap);
	FRHIBlendState* BlendState = FScreenPassPipelineState::FDefaultBlendState::GetRHI();
	FRHIDepthStencilState* DepthStencilState = FScreenPassPipelineState::FDefaultDepthStencilState::GetRHI();
	AddDrawScreenPass(GraphBuilder, Forward<FRDGEventName&&>(PassName), View, OutputViewport, InputViewport, VertexShader, PixelShader, BlendState, DepthStencilState, PixelShaderParameters, Flags);
}

/** Render graph variant of simpler DrawScreenPass function. Clears graph resources unused by the
 *  pixel shader prior to adding the pass.
 */
template <typename PixelShaderType>
FORCEINLINE void AddDrawScreenPass(
	FRDGBuilder& GraphBuilder,
	FRDGEventName&& PassName,
	const FViewInfo& View,
	const FScreenPassTextureViewport& OutputViewport,
	const FScreenPassTextureViewport& InputViewport,
	const TShaderRef<FShader>& VertexShader,
	const TShaderRef<PixelShaderType>& PixelShader,
	FRHIBlendState* BlendState,
	FRHIDepthStencilState* DepthStencilState,
	typename PixelShaderType::FParameters* PixelShaderParameters,
	EScreenPassDrawFlags Flags = EScreenPassDrawFlags::None)
{
	check(VertexShader.IsValid());
	check(PixelShader.IsValid());
	check(PixelShaderParameters);

	ClearUnusedGraphResources(PixelShader, PixelShaderParameters);

	const FScreenPassPipelineState PipelineState(VertexShader, PixelShader, BlendState, DepthStencilState);

	GraphBuilder.AddPass(
		Forward<FRDGEventName&&>(PassName),
		PixelShaderParameters,
		ERDGPassFlags::Raster,
		[&View, OutputViewport, InputViewport, PipelineState, PixelShader, PixelShaderParameters, Flags](FRHICommandListImmediate& RHICmdList)
	{
		DrawScreenPass(RHICmdList, View, OutputViewport, InputViewport, PipelineState, Flags, [&](FRHICommandListImmediate&)
		{
			SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PixelShaderParameters);
		});
	});
}
/** More advanced variant of screen pass drawing. Supports overriding blend / depth stencil
 *  pipeline state, and providing a custom vertex shader. Shader parameters are not bound by
 *  this method, instead the user provides a setup function that is called prior to draw, but
 *  after setting the PSO. This setup function should assign shader parameters.
 */
template<typename TSetupFunction>
void DrawScreenPass(
	FRHICommandListImmediate& RHICmdList,
	const FViewInfo& View,
	const FScreenPassTextureViewport& OutputViewport,
	const FScreenPassTextureViewport& InputViewport,
	const FScreenPassPipelineState& PipelineState,
	EScreenPassDrawFlags Flags,
	TSetupFunction SetupFunction)
{
	PipelineState.Validate();

	const FIntRect InputRect = InputViewport.Rect;
	const FIntPoint InputSize = InputViewport.Extent;
	const FIntRect OutputRect = OutputViewport.Rect;
	const FIntPoint OutputSize = OutputRect.Size();

	RHICmdList.SetViewport(OutputRect.Min.X, OutputRect.Min.Y, 0.0f, OutputRect.Max.X, OutputRect.Max.Y, 1.0f);

	SetScreenPassPipelineState(RHICmdList, PipelineState);

	SetupFunction(RHICmdList);

	FIntPoint LocalOutputPos(FIntPoint::ZeroValue);
	FIntPoint LocalOutputSize(OutputSize);
	EDrawRectangleFlags DrawRectangleFlags = EDRF_UseTriangleOptimization;

	const bool bFlipYAxis = (Flags & EScreenPassDrawFlags::FlipYAxis) == EScreenPassDrawFlags::FlipYAxis;

	if (bFlipYAxis)
	{
		// Draw the quad flipped. Requires that the cull mode be disabled.
		LocalOutputPos.Y = OutputSize.Y;
		LocalOutputSize.Y = -OutputSize.Y;

		// Triangle optimization currently doesn't work when flipped.
		DrawRectangleFlags = EDRF_Default;
	}

	const bool bUseHMDHiddenAreaMask = (Flags & EScreenPassDrawFlags::AllowHMDHiddenAreaMask) == EScreenPassDrawFlags::AllowHMDHiddenAreaMask
		? View.bHMDHiddenAreaMaskActive
		: false;

	DrawPostProcessPass(
		RHICmdList,
		LocalOutputPos.X, LocalOutputPos.Y, LocalOutputSize.X, LocalOutputSize.Y,
		InputRect.Min.X, InputRect.Min.Y, InputRect.Width(), InputRect.Height(),
		OutputSize,
		InputSize,
		PipelineState.VertexShader,
		View.StereoPass,
		bUseHMDHiddenAreaMask,
		DrawRectangleFlags);
}
void DrawPostProcessPass(
	FRHICommandList& RHICmdList,
	float X,
	float Y,
	float SizeX,
	float SizeY,
	float U,
	float V,
	float SizeU,
	float SizeV,
	FIntPoint TargetSize,
	FIntPoint TextureSize,
	const TShaderRef<FShader>& VertexShader,
	EStereoscopicPass StereoView,
	bool bHasCustomMesh,
	EDrawRectangleFlags Flags)
{
	if (bHasCustomMesh && IStereoRendering::IsStereoEyePass(StereoView))
	{
		DrawHmdMesh(RHICmdList, X, Y, SizeX, SizeY, U, V, SizeU, SizeV, TargetSize, TextureSize, StereoView, VertexShader);
	}
	else
	{
		DrawRectangle(RHICmdList, X, Y, SizeX, SizeY, U, V, SizeU, SizeV, TargetSize, TextureSize, VertexShader, Flags);
	}
}

EMeshPass::EditorSelection

/**
 * Initialize scene's views.
 * Check visibility, build visible mesh commands, etc.
 */
bool FDeferredShadingSceneRenderer::InitViews(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, struct FILCUpdatePrimTaskData& ILCTaskData)
{
	SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_InitViews, FColor::Emerald);
	SCOPE_CYCLE_COUNTER(STAT_InitViewsTime);
	CSV_SCOPED_TIMING_STAT_EXCLUSIVE(InitViews_Scene);
	check(RHICmdList.IsOutsideRenderPass());

	PreVisibilityFrameSetup(RHICmdList);

	RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);

	{
		// This is to init the ViewUniformBuffer before rendering for the Niagara compute shader.
		// This needs to run before ComputeViewVisibility() is called, but the views normally initialize the ViewUniformBuffer after that (at the end of this method).
		if (FXSystem && FXSystem->RequiresEarlyViewUniformBuffer() && Views.IsValidIndex(0))
		{
			Views[0].InitRHIResources();
			FXSystem->PostInitViews(RHICmdList, Views[0].ViewUniformBuffer, Views[0].AllowGPUParticleUpdate() && !ViewFamily.EngineShowFlags.HitProxies);
		}
	}
	
	FViewVisibleCommandsPerView ViewCommandsPerView;
	ViewCommandsPerView.SetNum(Views.Num());

	ComputeViewVisibility(RHICmdList, BasePassDepthStencilAccess, ViewCommandsPerView, DynamicIndexBufferForInitViews, DynamicVertexBufferForInitViews, DynamicReadBufferForInitViews);

	RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
}
void FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FViewVisibleCommandsPerView& ViewCommandsPerView, 
	FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer)
{
	SCOPE_CYCLE_COUNTER(STAT_ViewVisibilityTime);
	SCOPED_NAMED_EVENT(FSceneRenderer_ComputeViewVisibility, FColor::Magenta);

	STAT(int32 NumProcessedPrimitives = 0);
	STAT(int32 NumCulledPrimitives = 0);
	STAT(int32 NumOccludedPrimitives = 0);

	// ISR views can't compute relevance until all views are frustum culled
		if (!bIsInstancedStereo)
		{
			SCOPE_CYCLE_COUNTER(STAT_ViewRelevance);
			ComputeAndMarkRelevanceForViewParallel(RHICmdList, Scene, View, ViewCommands, ViewBit, HasDynamicMeshElementsMasks, HasDynamicEditorMeshElementsMasks);
		}

SetupMeshPass(View, BasePassDepthStencilAccess, ViewCommands);
}
static void ComputeAndMarkRelevanceForViewParallel(
	FRHICommandListImmediate& RHICmdList,
	const FScene* Scene,
	FViewInfo& View,
	FViewCommands& ViewCommands,
	uint8 ViewBit,
	FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks,
	FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks
	)
{
	SCOPED_NAMED_EVENT(FSceneRenderer_ComputeAndMarkRelevanceForViewParallel, FColor::Blue);

	check(OutHasDynamicMeshElementsMasks.Num() == Scene->Primitives.Num());

	FFrozenSceneViewMatricesGuard FrozenMatricesGuard(View);
	const FMarkRelevantStaticMeshesForViewData ViewData(View);

	int32 NumMesh = View.StaticMeshVisibilityMap.Num();
	uint8* RESTRICT MarkMasks = (uint8*)FMemStack::Get().Alloc(NumMesh + 31 , 8); // some padding to simplify the high speed transpose
	FMemory::Memzero(MarkMasks, NumMesh + 31);

	int32 EstimateOfNumPackets = NumMesh / (FRelevancePrimSet<int32>::MaxInputPrims * 4);

	TArray<FRelevancePacket*,SceneRenderingAllocator> Packets;
	Packets.Reserve(EstimateOfNumPackets);

	bool WillExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && CVarParallelInitViews.GetValueOnRenderThread() > 0 && IsInActualRenderingThread();

	{
		FSceneSetBitIterator BitIt(View.PrimitiveVisibilityMap);
		if (BitIt)
		{

			FRelevancePacket* Packet = new(FMemStack::Get()) FRelevancePacket(
				RHICmdList,
				Scene, 
				View, 
				ViewCommands,
				ViewBit,
				ViewData,
				OutHasDynamicMeshElementsMasks,
				OutHasDynamicEditorMeshElementsMasks,
				MarkMasks);
			Packets.Add(Packet);

			while (1)
			{
				Packet->Input.AddPrim(BitIt.GetIndex());
				++BitIt;
				if (Packet->Input.IsFull() || !BitIt)
				{
					if (!BitIt)
					{
						break;
					}
					else
					{
						Packet = new(FMemStack::Get()) FRelevancePacket(
							RHICmdList,
							Scene, 
							View, 
							ViewCommands,
							ViewBit,
							ViewData,
							OutHasDynamicMeshElementsMasks,
							OutHasDynamicEditorMeshElementsMasks,
							MarkMasks);
						Packets.Add(Packet);
					}
				}
			}
		}
	}
	{
		QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_ParallelFor);
		ParallelFor(Packets.Num(), 
			[&Packets](int32 Index)
			{
				Packets[Index]->AnyThreadTask();
			},
			!WillExecuteInParallel
		);
	}
	{
		QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_RenderThreadFinalize);

		for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
		{
			int32 NumVisibleCachedMeshDrawCommands = 0;
			int32 NumDynamicBuildRequests = 0;

			for (auto Packet : Packets)
			{
				NumVisibleCachedMeshDrawCommands += Packet->DrawCommandPacket.VisibleCachedDrawCommands[PassIndex].Num();
				NumDynamicBuildRequests += Packet->DrawCommandPacket.DynamicBuildRequests[PassIndex].Num();
			}

			ViewCommands.MeshCommands[PassIndex].Reserve(NumVisibleCachedMeshDrawCommands);
			ViewCommands.DynamicMeshCommandBuildRequests[PassIndex].Reserve(NumDynamicBuildRequests);
		}

		for (auto Packet : Packets)
		{
			Packet->RenderThreadFinalize();
			Packet->~FRelevancePacket();
		}

		Packets.Empty();
	}

	QUICK_SCOPE_CYCLE_COUNTER(STAT_ComputeAndMarkRelevanceForViewParallel_TransposeMeshBits);
	check(View.StaticMeshVisibilityMap.Num() == NumMesh && 
		View.StaticMeshFadeOutDitheredLODMap.Num() == NumMesh && 
		View.StaticMeshFadeInDitheredLODMap.Num() == NumMesh
		);
	uint32* RESTRICT StaticMeshVisibilityMap_Words = View.StaticMeshVisibilityMap.GetData();
	uint32* RESTRICT StaticMeshFadeOutDitheredLODMap_Words = View.StaticMeshFadeOutDitheredLODMap.GetData();
	uint32* RESTRICT StaticMeshFadeInDitheredLODMap_Words = View.StaticMeshFadeInDitheredLODMap.GetData();
	const uint64* RESTRICT MarkMasks64 = (const uint64* RESTRICT)MarkMasks;
	const uint8* RESTRICT MarkMasks8 = MarkMasks;
	for (int32 BaseIndex = 0; BaseIndex < NumMesh; BaseIndex += 32)
	{
		uint32 StaticMeshVisibilityMap_Word = 0;
		uint32 StaticMeshFadeOutDitheredLODMap_Word = 0;
		uint32 StaticMeshFadeInDitheredLODMap_Word = 0;
		uint32 Mask = 1;
		bool bAny = false;
		for (int32 QWordIndex = 0; QWordIndex < 4; QWordIndex++)
		{
			if (*MarkMasks64++)
			{
				for (int32 ByteIndex = 0; ByteIndex < 8; ByteIndex++, Mask <<= 1, MarkMasks8++)
				{
					uint8 MaskMask = *MarkMasks8;
					StaticMeshVisibilityMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshVisibilityMapMask) ? Mask : 0;
					StaticMeshFadeOutDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask) ? Mask : 0;
					StaticMeshFadeInDitheredLODMap_Word |= (MaskMask & EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask) ? Mask : 0;
				}
				bAny = true;
			}
			else
			{
				MarkMasks8 += 8;
				Mask <<= 8;
			}
		}
		if (bAny)
		{
			checkSlow(!*StaticMeshVisibilityMap_Words && !*StaticMeshFadeOutDitheredLODMap_Words && !*StaticMeshFadeInDitheredLODMap_Words);
			*StaticMeshVisibilityMap_Words = StaticMeshVisibilityMap_Word;
			*StaticMeshFadeOutDitheredLODMap_Words = StaticMeshFadeOutDitheredLODMap_Word;
			*StaticMeshFadeInDitheredLODMap_Words = StaticMeshFadeInDitheredLODMap_Word;
		}
		StaticMeshVisibilityMap_Words++;
		StaticMeshFadeOutDitheredLODMap_Words++;
		StaticMeshFadeInDitheredLODMap_Words++;
	}
}
struct FRelevancePacket
{
	const float CurrentWorldTime;
	const float DeltaWorldTime;

	FRHICommandListImmediate& RHICmdList;
	const FScene* Scene;
	const FViewInfo& View;
	const FViewCommands& ViewCommands;
	const uint8 ViewBit;
	const FMarkRelevantStaticMeshesForViewData& ViewData;
	FPrimitiveViewMasks& OutHasDynamicMeshElementsMasks;
	FPrimitiveViewMasks& OutHasDynamicEditorMeshElementsMasks;
	uint8* RESTRICT MarkMasks;

	FRelevancePrimSet<int32> Input;
	FRelevancePrimSet<int32> RelevantStaticPrimitives;
	FRelevancePrimSet<int32> NotDrawRelevant;
	FRelevancePrimSet<int32> TranslucentSelfShadowPrimitives;
	FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleDynamicPrimitivesWithSimpleLights;
	int32 NumVisibleDynamicPrimitives;
	int32 NumVisibleDynamicEditorPrimitives;
	FMeshPassMask VisibleDynamicMeshesPassMask;
	FTranslucenyPrimCount TranslucentPrimCount;
	bool bHasDistortionPrimitives;
	bool bHasCustomDepthPrimitives;
	FRelevancePrimSet<FPrimitiveSceneInfo*> LazyUpdatePrimitives;
	FRelevancePrimSet<FPrimitiveSceneInfo*> DirtyIndirectLightingCacheBufferPrimitives;
	FRelevancePrimSet<FPrimitiveSceneInfo*> RecachedReflectionCapturePrimitives;

	TArray<FMeshDecalBatch> MeshDecalBatches;
	TArray<FVolumetricMeshBatch> VolumetricMeshBatches;
	TArray<FSkyMeshBatch> SkyMeshBatches;
	FDrawCommandRelevancePacket DrawCommandPacket;

	struct FPrimitiveLODMask
	{
		FPrimitiveLODMask()
			: PrimitiveIndex(INDEX_NONE)
		{}

		FPrimitiveLODMask(const int32 InPrimitiveIndex, const FLODMask& InLODMask)
			: PrimitiveIndex(InPrimitiveIndex)
			, LODMask(InLODMask)
		{}

		int32 PrimitiveIndex;
		FLODMask LODMask;
	};

	FRelevancePrimSet<FPrimitiveLODMask> PrimitivesLODMask; // group both lod mask with primitive index to be able to properly merge them in the view

	uint16 CombinedShadingModelMask;
	bool bUsesGlobalDistanceField;
	bool bUsesLightingChannels;
	bool bTranslucentSurfaceLighting;
	bool bUsesSceneDepth;
	bool bUsesCustomDepthStencil;
	bool bSceneHasSkyMaterial;
	bool bHasSingleLayerWaterMaterial;
	bool bHasTranslucencySeparateModulation;

	FRelevancePacket(
		FRHICommandListImmediate& InRHICmdList,
		const FScene* InScene, 
		const FViewInfo& InView, 
		const FViewCommands& InViewCommands,
		uint8 InViewBit,
		const FMarkRelevantStaticMeshesForViewData& InViewData,
		FPrimitiveViewMasks& InOutHasDynamicMeshElementsMasks,
		FPrimitiveViewMasks& InOutHasDynamicEditorMeshElementsMasks,
		uint8* InMarkMasks)

		: CurrentWorldTime(InView.Family->CurrentWorldTime)
		, DeltaWorldTime(InView.Family->DeltaWorldTime)
		, RHICmdList(InRHICmdList)
		, Scene(InScene)
		, View(InView)
		, ViewCommands(InViewCommands)
		, ViewBit(InViewBit)
		, ViewData(InViewData)
		, OutHasDynamicMeshElementsMasks(InOutHasDynamicMeshElementsMasks)
		, OutHasDynamicEditorMeshElementsMasks(InOutHasDynamicEditorMeshElementsMasks)
		, MarkMasks(InMarkMasks)
		, NumVisibleDynamicPrimitives(0)
		, NumVisibleDynamicEditorPrimitives(0)
		, bHasDistortionPrimitives(false)
		, bHasCustomDepthPrimitives(false)
		, CombinedShadingModelMask(0)
		, bUsesGlobalDistanceField(false)
		, bUsesLightingChannels(false)
		, bTranslucentSurfaceLighting(false)
		, bUsesSceneDepth(false)
		, bUsesCustomDepthStencil(false)
		, bSceneHasSkyMaterial(false)
		, bHasSingleLayerWaterMaterial(false)
		, bHasTranslucencySeparateModulation(false)
	{
	}

	void AnyThreadTask()
	{
		ComputeRelevance();
		MarkRelevant();
	}

	void ComputeRelevance()
	{
		CombinedShadingModelMask = 0;
		bSceneHasSkyMaterial = 0;
		bHasSingleLayerWaterMaterial = 0;
		bHasTranslucencySeparateModulation = 0;
		bUsesGlobalDistanceField = false;
		bUsesLightingChannels = false;
		bTranslucentSurfaceLighting = false;
		const EShadingPath ShadingPath = Scene->GetShadingPath();
		const bool bAddLightmapDensityCommands = View.Family->EngineShowFlags.LightMapDensity && AllowDebugViewmodes();

		SCOPE_CYCLE_COUNTER(STAT_ComputeViewRelevance);
		for (int32 Index = 0; Index < Input.NumPrims; Index++)
		{
			int32 BitIndex = Input.Prims[Index];
			FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene->Primitives[BitIndex];
			FPrimitiveViewRelevance& ViewRelevance = const_cast<FPrimitiveViewRelevance&>(View.PrimitiveViewRelevanceMap[BitIndex]);
			ViewRelevance = PrimitiveSceneInfo->Proxy->GetViewRelevance(&View);
			ViewRelevance.bInitializedThisFrame = true;

			const bool bStaticRelevance = ViewRelevance.bStaticRelevance;
			const bool bDrawRelevance = ViewRelevance.bDrawRelevance;
			const bool bDynamicRelevance = ViewRelevance.bDynamicRelevance;
			const bool bShadowRelevance = ViewRelevance.bShadowRelevance;
			const bool bEditorRelevance = ViewRelevance.bEditorPrimitiveRelevance;
			const bool bEditorSelectionRelevance = ViewRelevance.bEditorStaticSelectionRelevance;
			const bool bTranslucentRelevance = ViewRelevance.HasTranslucency();

			const bool bHairStrandsEnabled = ViewRelevance.bHairStrands && IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene->GetShaderPlatform());
			if (!bEditorRelevance && bHairStrandsEnabled)
			{
				++NumVisibleDynamicPrimitives;
				OutHasDynamicMeshElementsMasks[BitIndex] |= ViewBit;
			}

			if (View.bIsReflectionCapture && !PrimitiveSceneInfo->Proxy->IsVisibleInReflectionCaptures())
			{
				NotDrawRelevant.AddPrim(BitIndex);
				continue;
			}

			if (bStaticRelevance && (bDrawRelevance || bShadowRelevance))
			{
				RelevantStaticPrimitives.AddPrim(BitIndex);
			}

			if (!bDrawRelevance)
			{
				NotDrawRelevant.AddPrim(BitIndex);
				continue;
			}

			if (bEditorRelevance)
			{
				++NumVisibleDynamicEditorPrimitives;

				if (GIsEditor)
				{
					OutHasDynamicEditorMeshElementsMasks[BitIndex] |= ViewBit;
				}
			}
			else if(bDynamicRelevance)
			{
				// Keep track of visible dynamic primitives.
				++NumVisibleDynamicPrimitives;
				OutHasDynamicMeshElementsMasks[BitIndex] |= ViewBit;

				if (ViewRelevance.bHasSimpleLights)
				{
					VisibleDynamicPrimitivesWithSimpleLights.AddPrim(PrimitiveSceneInfo);
				}
			}

			if (bTranslucentRelevance && !bEditorRelevance && ViewRelevance.bRenderInMainPass)
			{
				if (View.Family->AllowTranslucencyAfterDOF())
				{
					if (ViewRelevance.bNormalTranslucency)
					{
						TranslucentPrimCount.Add(ETranslucencyPass::TPT_StandardTranslucency, ViewRelevance.bUsesSceneColorCopy);
					}

					if (ViewRelevance.bSeparateTranslucency)
					{
						TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterDOF, ViewRelevance.bUsesSceneColorCopy);
					}

					if (ViewRelevance.bSeparateTranslucencyModulate)
					{
						TranslucentPrimCount.Add(ETranslucencyPass::TPT_TranslucencyAfterDOFModulate, ViewRelevance.bUsesSceneColorCopy);
					}
				}
				else // Otherwise, everything is rendered in a single bucket. This is not related to whether DOF is currently enabled or not.
				{
					// When using all translucency, Standard and AfterDOF are sorted together instead of being rendered like 2 buckets.
					TranslucentPrimCount.Add(ETranslucencyPass::TPT_AllTranslucency, ViewRelevance.bUsesSceneColorCopy);
				}

				if (ViewRelevance.bDistortion)
				{
					bHasDistortionPrimitives = true;
				}
			}
			
			CombinedShadingModelMask |= ViewRelevance.ShadingModelMask;
			bUsesGlobalDistanceField |= ViewRelevance.bUsesGlobalDistanceField;
			bUsesLightingChannels |= ViewRelevance.bUsesLightingChannels;
			bTranslucentSurfaceLighting |= ViewRelevance.bTranslucentSurfaceLighting;
			bUsesSceneDepth |= ViewRelevance.bUsesSceneDepth;
			bUsesCustomDepthStencil |= ViewRelevance.bUsesCustomDepthStencil;
			bSceneHasSkyMaterial |= ViewRelevance.bUsesSkyMaterial;
			bHasSingleLayerWaterMaterial |= ViewRelevance.bUsesSingleLayerWaterMaterial;
			bHasTranslucencySeparateModulation |= ViewRelevance.bSeparateTranslucencyModulate;

			if (ViewRelevance.bRenderCustomDepth)
			{
				bHasCustomDepthPrimitives = true;
			}

			extern bool GUseTranslucencyShadowDepths;
			if (GUseTranslucencyShadowDepths && ViewRelevance.bTranslucentSelfShadow)
			{
				TranslucentSelfShadowPrimitives.AddPrim(BitIndex);
			}

			// INITVIEWS_TODO: Do this in a separate pass? There are no dependencies
			// here except maybe ParentPrimitives. This could be done in a 
			// low-priority background task and forgotten about.

			PrimitiveSceneInfo->LastRenderTime = CurrentWorldTime;

			// If the primitive is definitely unoccluded or if in Wireframe mode and the primitive is estimated
			// to be unoccluded, then update the primitive components's LastRenderTime 
			// on the game thread. This signals that the primitive is visible.
			if (View.PrimitiveDefinitelyUnoccludedMap[BitIndex] || (View.Family->EngineShowFlags.Wireframe && View.PrimitiveVisibilityMap[BitIndex]))
			{
				PrimitiveSceneInfo->UpdateComponentLastRenderTime(CurrentWorldTime, /*bUpdateLastRenderTimeOnScreen=*/true);
			}

			// Cache the nearest reflection proxy if needed
			if (PrimitiveSceneInfo->NeedsReflectionCaptureUpdate())
			{
				// mobile should not have any outstanding reflection capture update requests at this point, except for when lighting isn't rebuilt		
				PrimitiveSceneInfo->CacheReflectionCaptures();

				// With forward shading we need to track reflection capture cache updates
				// in order to update primitive's uniform buffer's closest reflection capture id.
				if (IsForwardShadingEnabled(Scene->GetShaderPlatform()))
				{
					RecachedReflectionCapturePrimitives.AddPrim(PrimitiveSceneInfo);
				}
			}

			if (PrimitiveSceneInfo->NeedsUniformBufferUpdate())
			{
				LazyUpdatePrimitives.AddPrim(PrimitiveSceneInfo);
			}
			if (PrimitiveSceneInfo->NeedsIndirectLightingCacheBufferUpdate())
			{
				DirtyIndirectLightingCacheBufferPrimitives.AddPrim(PrimitiveSceneInfo);
			}
		}
	}

	void MarkRelevant()
	{
		SCOPE_CYCLE_COUNTER(STAT_StaticRelevance);

		// using a local counter to reduce memory traffic
		int32 NumVisibleStaticMeshElements = 0;
		FViewInfo& WriteView = const_cast<FViewInfo&>(View);
		const FSceneViewState* ViewState = (FSceneViewState*)View.State;
		const EShadingPath ShadingPath = Scene->GetShadingPath();

		const bool bHLODActive = Scene->SceneLODHierarchy.IsActive();
		const FHLODVisibilityState* const HLODState = bHLODActive && ViewState ? &ViewState->HLODVisibilityState : nullptr;

		for (int32 StaticPrimIndex = 0, Num = RelevantStaticPrimitives.NumPrims; StaticPrimIndex < Num; ++StaticPrimIndex)
		{
			int32 PrimitiveIndex = RelevantStaticPrimitives.Prims[StaticPrimIndex];
			const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo = Scene->Primitives[PrimitiveIndex];
			const FPrimitiveBounds& Bounds = Scene->PrimitiveBounds[PrimitiveIndex];
			const FPrimitiveViewRelevance& ViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveIndex];
			const bool bIsPrimitiveDistanceCullFading = View.PrimitiveFadeUniformBufferMap[PrimitiveIndex];

			const int8 CurFirstLODIdx = PrimitiveSceneInfo->Proxy->GetCurrentFirstLODIdx_RenderThread();
			check(CurFirstLODIdx >= 0);
			float MeshScreenSizeSquared = 0;
			FLODMask LODToRender = ComputeLODForMeshes(PrimitiveSceneInfo->StaticMeshRelevances, View, Bounds.BoxSphereBounds.Origin, Bounds.BoxSphereBounds.SphereRadius, ViewData.ForcedLODLevel, MeshScreenSizeSquared, CurFirstLODIdx, ViewData.LODScale);

			PrimitivesLODMask.AddPrim(FRelevancePacket::FPrimitiveLODMask(PrimitiveIndex, LODToRender));

			const bool bIsHLODFading = HLODState ? HLODState->IsNodeFading(PrimitiveIndex) : false;
			const bool bIsHLODFadingOut = HLODState ? HLODState->IsNodeFadingOut(PrimitiveIndex) : false;
			const bool bIsLODDithered = LODToRender.IsDithered();

			float DistanceSquared = (Bounds.BoxSphereBounds.Origin - ViewData.ViewOrigin).SizeSquared();
			const float LODFactorDistanceSquared = DistanceSquared * FMath::Square(ViewData.LODScale);
			const bool bDrawShadowDepth = FMath::Square(Bounds.BoxSphereBounds.SphereRadius) > ViewData.MinScreenRadiusForCSMDepthSquared * LODFactorDistanceSquared;
			const bool bDrawDepthOnly = ViewData.bFullEarlyZPass || FMath::Square(Bounds.BoxSphereBounds.SphereRadius) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared;

			const bool bAddLightmapDensityCommands = View.Family->EngineShowFlags.LightMapDensity && AllowDebugViewmodes();
			const bool bMobileMaskedInEarlyPass = MaskedInEarlyPass(Scene->GetShaderPlatform()) && Scene->EarlyZPassMode == DDM_MaskedOnly;

			const int32 NumStaticMeshes = PrimitiveSceneInfo->StaticMeshRelevances.Num();
			for(int32 MeshIndex = 0;MeshIndex < NumStaticMeshes;MeshIndex++)
			{
				const FStaticMeshBatchRelevance& StaticMeshRelevance = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex];
				const FStaticMeshBatch& StaticMesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];

				if (LODToRender.ContainsLOD(StaticMeshRelevance.LODIndex))
				{
					uint8 MarkMask = 0;
					bool bHiddenByHLODFade = false; // Hide mesh LOD levels that HLOD is substituting

					if (bIsHLODFading)
					{
						if (bIsHLODFadingOut)
						{
							if (bIsLODDithered && LODToRender.DitheredLODIndices[1] == StaticMeshRelevance.LODIndex)
							{
								bHiddenByHLODFade = true;
							}
							else
							{
								MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;	
							}
						}
						else
						{
							if (bIsLODDithered && LODToRender.DitheredLODIndices[0] == StaticMeshRelevance.LODIndex)
							{
								bHiddenByHLODFade = true;
							}
							else
							{
								MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
							}
						}
					}
					else if (bIsLODDithered)
					{
						if (LODToRender.DitheredLODIndices[0] == StaticMeshRelevance.LODIndex)
						{
							MarkMask |= EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask;
						}
						else
						{
							MarkMask |= EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask;
						}
					}

					// Don't cache if it requires per view per mesh state for LOD dithering or distance cull fade.
					const bool bIsMeshDitheringLOD = StaticMeshRelevance.bDitheredLODTransition && (MarkMask & (EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask | EMarkMaskBits::StaticMeshFadeInDitheredLODMapMask));
					const bool bCanCache = !bIsPrimitiveDistanceCullFading && !bIsMeshDitheringLOD;
					
					if (ViewRelevance.bDrawRelevance)
					{
						if ((StaticMeshRelevance.bUseForMaterial || StaticMeshRelevance.bUseAsOccluder)
							&& (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass)
							&& !bHiddenByHLODFade)
						{
							bool bMobileIsInDepthPassMaskedMesh = (bMobileMaskedInEarlyPass && ViewRelevance.bMasked) && ShadingPath == EShadingPath::Mobile;
							if ((StaticMeshRelevance.bUseForDepthPass && bDrawDepthOnly && ShadingPath != EShadingPath::Mobile) || bMobileIsInDepthPassMaskedMesh)
							{
								DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::DepthPass);

#if RHI_RAYTRACING
								if (IsRayTracingEnabled())
								{
									if (MarkMask & EMarkMaskBits::StaticMeshFadeOutDitheredLODMapMask)
									{
										DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::DitheredLODFadingOutMaskPass);
									}
								}
#endif
							}

							// Mark static mesh as visible for rendering
							if (StaticMeshRelevance.bUseForMaterial && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth))
							{
								DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::BasePass);
								MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask;

								if (StaticMeshRelevance.bUseAnisotropy)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::AnisotropyPass);
								}

								if (ShadingPath == EShadingPath::Mobile)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::MobileBasePassCSM);
								}
								else if(StaticMeshRelevance.bUseSkyMaterial)
								{
									// Not needed on Mobile path as in this case everything goes into the regular base pass
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SkyPass);
								}
								else if(StaticMeshRelevance.bUseSingleLayerWaterMaterial)
								{
									// Not needed on Mobile path as in this case everything goes into the regular base pass
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SingleLayerWaterPass);
								}

								if (ViewRelevance.bRenderCustomDepth)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::CustomDepth);
								}

								if (bAddLightmapDensityCommands)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::LightmapDensity);
								}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
								else if (View.Family->UseDebugViewPS())
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::DebugViewMode);
								}
#endif

#if WITH_EDITOR
								if (StaticMeshRelevance.bSelectable)
								{
									if (View.bAllowTranslucentPrimitivesInHitProxy)
									{
										DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::HitProxy);
									}
									else
									{
										DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::HitProxyOpaqueOnly);
									}
								}
#endif

								if (ViewRelevance.HasVelocity())
								{
									const FPrimitiveSceneProxy* PrimitiveSceneProxy = PrimitiveSceneInfo->Proxy;

									if (FVelocityMeshProcessor::PrimitiveHasVelocityForView(View, PrimitiveSceneProxy))
									{
										if (ViewRelevance.bVelocityRelevance &&
											FOpaqueVelocityMeshProcessor::PrimitiveCanHaveVelocity(View.GetShaderPlatform(), PrimitiveSceneProxy) &&
											FOpaqueVelocityMeshProcessor::PrimitiveHasVelocityForFrame(PrimitiveSceneProxy))
										{
											DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::Velocity);
										}

										if (ViewRelevance.bOutputsTranslucentVelocity &&
											FTranslucentVelocityMeshProcessor::PrimitiveCanHaveVelocity(View.GetShaderPlatform(), PrimitiveSceneProxy) &&
											FTranslucentVelocityMeshProcessor::PrimitiveHasVelocityForFrame(PrimitiveSceneProxy))
										{
											DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucentVelocity);
										}
									}
								}

								++NumVisibleStaticMeshElements;

								INC_DWORD_STAT_BY(STAT_StaticMeshTriangles, StaticMesh.GetNumPrimitives());
							}
						}

						if (StaticMeshRelevance.bUseForMaterial
							&& ViewRelevance.HasTranslucency()
							&& !ViewRelevance.bEditorPrimitiveRelevance
							&& ViewRelevance.bRenderInMainPass)
						{
							if (View.Family->AllowTranslucencyAfterDOF())
							{
								if (ViewRelevance.bNormalTranslucency)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyStandard);
								}

								if (ViewRelevance.bSeparateTranslucency)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAfterDOF);
								}

								if (ViewRelevance.bSeparateTranslucencyModulate)
								{
									DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAfterDOFModulate);
								}
							}
							else
							{
								// Otherwise, everything is rendered in a single bucket. This is not related to whether DOF is currently enabled or not.
								// When using all translucency, Standard and AfterDOF are sorted together instead of being rendered like 2 buckets.
								DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::TranslucencyAll);
							}

							if (ViewRelevance.bDistortion)
							{
								DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::Distortion);
							}

							if (ShadingPath == EShadingPath::Mobile && View.bIsSceneCapture)
							{
								DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::MobileInverseOpacity);
							}
						}

#if WITH_EDITOR
						if (ViewRelevance.bEditorStaticSelectionRelevance)
						{
							DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::EditorSelection);
						}
#endif

						if (ViewRelevance.bHasVolumeMaterialDomain)
						{
							VolumetricMeshBatches.AddUninitialized(1);
							FVolumetricMeshBatch& BatchAndProxy = VolumetricMeshBatches.Last();
							BatchAndProxy.Mesh = &StaticMesh;
							BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
						}

						if (ViewRelevance.bUsesSkyMaterial)
						{
							SkyMeshBatches.AddUninitialized(1);
							FSkyMeshBatch& BatchAndProxy = SkyMeshBatches.Last();
							BatchAndProxy.Mesh = &StaticMesh;
							BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
							BatchAndProxy.bVisibleInMainPass = ViewRelevance.bRenderInMainPass;
							BatchAndProxy.bVisibleInRealTimeSkyCapture = PrimitiveSceneInfo->bVisibleInRealTimeSkyCapture;
						}

						if (ViewRelevance.bRenderInMainPass && ViewRelevance.bDecal)
						{
							MeshDecalBatches.AddUninitialized(1);
							FMeshDecalBatch& BatchAndProxy = MeshDecalBatches.Last();
							BatchAndProxy.Mesh = &StaticMesh;
							BatchAndProxy.Proxy = PrimitiveSceneInfo->Proxy;
							BatchAndProxy.SortKey = PrimitiveSceneInfo->Proxy->GetTranslucencySortPriority();
						}
					}

					if (MarkMask)
					{
						MarkMasks[StaticMeshRelevance.Id] = MarkMask;
					}
				}
			}
		}
		static_assert(sizeof(WriteView.NumVisibleStaticMeshElements) == sizeof(int32), "Atomic is the wrong size");
		FPlatformAtomics::InterlockedAdd((volatile int32*)&WriteView.NumVisibleStaticMeshElements, NumVisibleStaticMeshElements);
	}

	void RenderThreadFinalize()
	{
		FViewInfo& WriteView = const_cast<FViewInfo&>(View);
		FViewCommands& WriteViewCommands = const_cast<FViewCommands&>(ViewCommands);
		
		for (int32 Index = 0; Index < NotDrawRelevant.NumPrims; Index++)
		{
			WriteView.PrimitiveVisibilityMap[NotDrawRelevant.Prims[Index]] = false;
		}

		WriteView.ShadingModelMaskInView |= CombinedShadingModelMask;
		WriteView.bUsesGlobalDistanceField |= bUsesGlobalDistanceField;
		WriteView.bUsesLightingChannels |= bUsesLightingChannels;
		WriteView.bTranslucentSurfaceLighting |= bTranslucentSurfaceLighting;
		WriteView.bUsesSceneDepth |= bUsesSceneDepth;
		WriteView.bSceneHasSkyMaterial |= bSceneHasSkyMaterial;
		WriteView.bHasSingleLayerWaterMaterial |= bHasSingleLayerWaterMaterial;
		WriteView.bHasTranslucencySeparateModulation |= bHasTranslucencySeparateModulation;
		VisibleDynamicPrimitivesWithSimpleLights.AppendTo(WriteView.VisibleDynamicPrimitivesWithSimpleLights);
		WriteView.NumVisibleDynamicPrimitives += NumVisibleDynamicPrimitives;
		WriteView.NumVisibleDynamicEditorPrimitives += NumVisibleDynamicEditorPrimitives;
		WriteView.TranslucentPrimCount.Append(TranslucentPrimCount);
		WriteView.bHasDistortionPrimitives |= bHasDistortionPrimitives;
		WriteView.bHasCustomDepthPrimitives |= bHasCustomDepthPrimitives;
		WriteView.bUsesCustomDepthStencilInTranslucentMaterials |= bUsesCustomDepthStencil;
		DirtyIndirectLightingCacheBufferPrimitives.AppendTo(WriteView.DirtyIndirectLightingCacheBufferPrimitives);

		WriteView.MeshDecalBatches.Append(MeshDecalBatches);
		WriteView.VolumetricMeshBatches.Append(VolumetricMeshBatches);
		WriteView.SkyMeshBatches.Append(SkyMeshBatches);

		for (int32 Index = 0; Index < RecachedReflectionCapturePrimitives.NumPrims; ++Index)
		{
			FPrimitiveSceneInfo* PrimitiveSceneInfo = RecachedReflectionCapturePrimitives.Prims[Index];

			PrimitiveSceneInfo->SetNeedsUniformBufferUpdate(true);
			PrimitiveSceneInfo->ConditionalUpdateUniformBuffer(RHICmdList);

			FScene& WriteScene = *const_cast<FScene*>(Scene);
			AddPrimitiveToUpdateGPU(WriteScene, PrimitiveSceneInfo->GetIndex());
		}

		for (int32 Index = 0; Index < LazyUpdatePrimitives.NumPrims; Index++)
		{
			LazyUpdatePrimitives.Prims[Index]->ConditionalUpdateUniformBuffer(RHICmdList);
		}

		for (int32 i = 0; i < PrimitivesLODMask.NumPrims; ++i)
		{
			WriteView.PrimitivesLODMask[PrimitivesLODMask.Prims[i].PrimitiveIndex] = PrimitivesLODMask.Prims[i].LODMask;
		}

		for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
		{
			FPassDrawCommandArray& SrcCommands = DrawCommandPacket.VisibleCachedDrawCommands[PassIndex];
			FMeshCommandOneFrameArray& DstCommands = WriteViewCommands.MeshCommands[PassIndex];
			if (SrcCommands.Num() > 0)
			{
				static_assert(sizeof(SrcCommands[0]) == sizeof(DstCommands[0]), "Memcpy sizes must match.");
				const int32 PrevNum = DstCommands.AddUninitialized(SrcCommands.Num());
				FMemory::Memcpy(&DstCommands[PrevNum], &SrcCommands[0], SrcCommands.Num() * sizeof(SrcCommands[0]));
			}

			FPassDrawCommandBuildRequestArray& SrcRequests = DrawCommandPacket.DynamicBuildRequests[PassIndex];
			TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& DstRequests = WriteViewCommands.DynamicMeshCommandBuildRequests[PassIndex];
			if (SrcRequests.Num() > 0)
			{
				static_assert(sizeof(SrcRequests[0]) == sizeof(DstRequests[0]), "Memcpy sizes must match.");
				const int32 PrevNum = DstRequests.AddUninitialized(SrcRequests.Num());
				FMemory::Memcpy(&DstRequests[PrevNum], &SrcRequests[0], SrcRequests.Num() * sizeof(SrcRequests[0]));
			}

			WriteViewCommands.NumDynamicMeshCommandBuildRequestElements[PassIndex] += DrawCommandPacket.NumDynamicBuildRequestElements[PassIndex];
		}

		// Prepare translucent self shadow uniform buffers.
		for (int32 Index = 0; Index < TranslucentSelfShadowPrimitives.NumPrims; ++Index)
		{
			const int32 PrimitiveIndex = TranslucentSelfShadowPrimitives.Prims[Index];

			FUniformBufferRHIRef& UniformBuffer = WriteView.TranslucentSelfShadowUniformBufferMap.FindOrAdd(PrimitiveIndex);

			if (!UniformBuffer)
			{
				FTranslucentSelfShadowUniformParameters Parameters;
				SetupTranslucentSelfShadowUniformParameters(nullptr, Parameters);
				UniformBuffer = FTranslucentSelfShadowUniformParameters::CreateUniformBuffer(Parameters, EUniformBufferUsage::UniformBuffer_SingleFrame);
			}
		}
	}
};
struct FDrawCommandRelevancePacket
{
	FDrawCommandRelevancePacket()
	{
		bUseCachedMeshDrawCommands = UseCachedMeshDrawCommands();

		for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; ++PassIndex)
		{
			NumDynamicBuildRequestElements[PassIndex] = 0;
		}
	}

	FPassDrawCommandArray VisibleCachedDrawCommands[EMeshPass::Num];
	FPassDrawCommandBuildRequestArray DynamicBuildRequests[EMeshPass::Num];
	int32 NumDynamicBuildRequestElements[EMeshPass::Num];
	bool bUseCachedMeshDrawCommands;

	void AddCommandsForMesh(
		int32 PrimitiveIndex, 
		const FPrimitiveSceneInfo* InPrimitiveSceneInfo,
		const FStaticMeshBatchRelevance& RESTRICT StaticMeshRelevance, 
		const FStaticMeshBatch& RESTRICT StaticMesh, 
		const FScene* RESTRICT Scene, 
		bool bCanCache, 
		EMeshPass::Type PassType)
	{
		const EShadingPath ShadingPath = Scene->GetShadingPath();
		const bool bUseCachedMeshCommand = bUseCachedMeshDrawCommands
			&& !!(FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::CachedMeshCommands)
			&& StaticMeshRelevance.bSupportsCachingMeshDrawCommands
			&& bCanCache;

		if (bUseCachedMeshCommand)
		{
			const int32 StaticMeshCommandInfoIndex = StaticMeshRelevance.GetStaticMeshCommandInfoIndex(PassType);
			if (StaticMeshCommandInfoIndex >= 0)
			{
				const FCachedMeshDrawCommandInfo& CachedMeshDrawCommand = InPrimitiveSceneInfo->StaticMeshCommandInfos[StaticMeshCommandInfoIndex];
				const FCachedPassMeshDrawList& SceneDrawList = Scene->CachedDrawLists[PassType];

				// AddUninitialized_GetRef()
				VisibleCachedDrawCommands[(uint32)PassType].AddUninitialized();
				FVisibleMeshDrawCommand& NewVisibleMeshDrawCommand = VisibleCachedDrawCommands[(uint32)PassType].Last();

				const FMeshDrawCommand* MeshDrawCommand = CachedMeshDrawCommand.StateBucketId >= 0
					? &Scene->CachedMeshDrawCommandStateBuckets[PassType].GetByElementId(CachedMeshDrawCommand.StateBucketId).Key
					: &SceneDrawList.MeshDrawCommands[CachedMeshDrawCommand.CommandIndex];

				NewVisibleMeshDrawCommand.Setup(
					MeshDrawCommand,
					PrimitiveIndex,
					PrimitiveIndex,
					CachedMeshDrawCommand.StateBucketId,
					CachedMeshDrawCommand.MeshFillMode,
					CachedMeshDrawCommand.MeshCullMode,
					CachedMeshDrawCommand.SortKey);
			}
		}
		else
		{
			NumDynamicBuildRequestElements[PassType] += StaticMeshRelevance.NumElements;
			DynamicBuildRequests[PassType].Add(&StaticMesh);
		}
	}
};
void FSceneRenderer::SetupMeshPass(FViewInfo& View, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FViewCommands& ViewCommands)
{
	SCOPE_CYCLE_COUNTER(STAT_SetupMeshPass);

	const EShadingPath ShadingPath = Scene->GetShadingPath();

	for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
	{
		const EMeshPass::Type PassType = (EMeshPass::Type)PassIndex;

		if ((FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::MainView) != EMeshPassFlags::None)
		{
			// Mobile: BasePass and MobileBasePassCSM lists need to be merged and sorted after shadow pass.
			if (ShadingPath == EShadingPath::Mobile && (PassType == EMeshPass::BasePass || PassType == EMeshPass::MobileBasePassCSM))
			{
				continue;
			}

			if (ViewFamily.UseDebugViewPS() && ShadingPath == EShadingPath::Deferred)
			{
				switch (PassType)
				{
					case EMeshPass::DepthPass:
					case EMeshPass::CustomDepth:
					case EMeshPass::DebugViewMode:
#if WITH_EDITOR
					case EMeshPass::HitProxy:
					case EMeshPass::HitProxyOpaqueOnly:
					case EMeshPass::EditorSelection:
#endif
						break;
					default:
						continue;
				}
			}

			PassProcessorCreateFunction CreateFunction = FPassProcessorManager::GetCreateFunction(ShadingPath, PassType);
			FMeshPassProcessor* MeshPassProcessor = CreateFunction(Scene, &View, nullptr);

			FParallelMeshDrawCommandPass& Pass = View.ParallelMeshDrawCommandPasses[PassIndex];

			if (ShouldDumpMeshDrawCommandInstancingStats())
			{
				Pass.SetDumpInstancingStats(GetMeshPassName(PassType));
			}

			Pass.DispatchPassSetup(
				Scene,
				View,
				PassType,
				BasePassDepthStencilAccess,
				MeshPassProcessor,
				View.DynamicMeshElements,
				&View.DynamicMeshElementsPassRelevance,
				View.NumVisibleDynamicMeshElements[PassType],
				ViewCommands.DynamicMeshCommandBuildRequests[PassType],
				ViewCommands.NumDynamicMeshCommandBuildRequestElements[PassType],
				ViewCommands.MeshCommands[PassIndex]);
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UE5后处理技术能够通过一系列的步骤来实现材质模型的高亮效果。 首先,我们需要在场景中添加一个Post Process Volume(后处理体积)来控制后处理效果。在该体积中,我们可以调整各种后处理参数以实现高亮效果。例如,在“Post Process Settings”中,我们可以调整亮度、对比度和饱和度等参数,以使模型的高亮更加明显。 接下来,我们需要创建一个自定义的材质,用于给模型应用高亮效果。在材质编辑器中,我们可以使用各种节点和项来调整模型的外观。为了实现高亮效果,我们可以使用“Emmisive(自发光)”节点来使模型的部分区域发出光亮。我们可以调整发光的颜色、强度和范围等属性,以获得所需的高亮效果。 然后,我们将自定义的材质应用到需要高亮的模型上。通过在模型的材质槽中选择或拖拽自定义材质,我们可以将其应用于模型的表面。一旦材质应用完成,我们可以在视图中观察到模型的高亮效果。 最后,我们可以在后处理体积的调节中进一步调整高亮效果。通过调整后处理体积的参数,如亮度、对比度和饱和度等,我们可以优化高亮效果,使其更加符合预期。 总结起来,通过使用UE5的后处理技术,我们可以通过添加Post Process Volume、创建自定义材质、应用材质和调整后处理参数等步骤,来实现材质模型的高亮效果。这样可以使模型在游戏中更加突出,增强视觉效果和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值