RDG应用

RDG的使用,用于备忘,快速复制
本文包括使用CS、增加ScreenPass,增加Mesh Pass
包含大量代码的Low B文章
所用的引擎版本是4.27

一、CS 工具

1.1 测试

一个在插件中辅助计算的CS案例,功能是图像反色
在这里插入图片描述

1.1.0 模块初始化时的路径注册

FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("XXComputeTools"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(FString(TEXT("/XX")), PluginShaderDir);

1.1.1 CS_Zero.h

#pragma once

#include "ShaderParameterUtils.h"
#include "RHIStaticStates.h"
#include "Shader.h"
#include "GlobalShader.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
#include "ShaderParameterStruct.h"
#include "UniformBuffer.h"
#include "RHICommandList.h"

#define NUM_THREADS_PER_GROUP_DIMENSION 32

BEGIN_SHADER_PARAMETER_STRUCT(FCS_ZeroParameters, )
	SHADER_PARAMETER_TEXTURE(Texture2D<float4>, In_TestTexture)
	SHADER_PARAMETER_UAV(RWTexture2D<float4>, Out_TestTexture)
END_SHADER_PARAMETER_STRUCT()

struct NORACOMPUTETOOLS_API FCS_ZeroParameters2
{
	UTextureRenderTarget2D* In_TestTexture;
	FRHITexture* Out_TestTexture;
};

class NORACOMPUTETOOLS_API FCS_ZeroShader : public FGlobalShader
{
public:

	DECLARE_SHADER_TYPE(FCS_ZeroShader, Global);
	SHADER_USE_PARAMETER_STRUCT(FCS_ZeroShader, FGlobalShader);

	/*
		检查是否支持
	*/
	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		return true;
	}

	/*
		编译时调用,填充FShaderCompileJob
	*/
	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
	{
		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
		OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), NUM_THREADS_PER_GROUP_DIMENSION);
		OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), NUM_THREADS_PER_GROUP_DIMENSION);
		OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Z"), 1);
	}

	using FParameters = FCS_ZeroParameters;
};

//注册
IMPLEMENT_GLOBAL_SHADER(FCS_ZeroShader, "/Nora/CS_Zero.usf", "CS_Zero", SF_Compute);

class NORACOMPUTETOOLS_API FCS_ZeroClass
{
public:
	static void RunComputeShader_RenderThread(FRHICommandListImmediate& RHICmdList, const FCS_ZeroParameters2& DrawParameters);
};

1.1.2 CS_Zero.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "ComputeShaders/CS_Zero.h"
#include "Engine/TextureRenderTarget2D.h"

void FCS_ZeroClass::RunComputeShader_RenderThread(FRHICommandListImmediate& RHICmdList, const FCS_ZeroParameters2& DrawParameters)
{
	TShaderMapRef<fcs_zeroshader> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
	FIntVector GroupCounts = FIntVector(FMath::DivideAndRoundUp(DrawParameters.In_TestTexture->SizeX, NUM_THREADS_PER_GROUP_DIMENSION), FMath::DivideAndRoundUp(DrawParameters.In_TestTexture->SizeY, NUM_THREADS_PER_GROUP_DIMENSION), 1);

	FUnorderedAccessViewRHIRef TextureUAV = RHICreateUnorderedAccessView(DrawParameters.Out_TestTexture);

	FCS_ZeroShader::FParameters Parameters;
	Parameters.In_TestTexture = DrawParameters.In_TestTexture->TextureReference.TextureReferenceRHI;
	Parameters.Out_TestTexture = TextureUAV;

	FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, Parameters, GroupCounts);
}

1.1.3 蓝图节点

TArray<fcolor> UxxBPLibrary::xx_CS_Zero(UTextureRenderTarget2D* In_Texture, UTextureRenderTarget2D* Out_Texture)
{
	// 等串流完成
	In_Texture->WaitForStreaming();
	Out_Texture->WaitForStreaming();

	// 获取RT的源
	FTextureRenderTargetResource* RenderTargetResource = Out_Texture->GameThread_GetRenderTargetResource();

	// 返回的颜色数组
	TArray<fcolor> ReturnArray;
	ReturnArray.Init(FColor::White, In_Texture->SizeX * In_Texture->SizeX);

	// 创建中转写入目标
	FRHIResourceCreateInfo CreateInfo;
	FTexture2DRHIRef TempTexture = RHICreateTexture2D(In_Texture->SizeX, In_Texture->SizeY, PF_A32B32G32R32F, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);

	// 设置参数
	FCS_ZeroParameters2 Parameters;
	Parameters.In_TestTexture = In_Texture;
	Parameters.Out_TestTexture = TempTexture;

	// 设置栅栏
	FRenderCommandFence Fence = FRenderCommandFence();
	
	ENQUEUE_RENDER_COMMAND(CaptureCommand)
	(
		[&](FRHICommandListImmediate& RHICmdList)
		{
			// 执行RT
			FCS_ZeroClass::RunComputeShader_RenderThread(RHICmdList, Parameters);

			// 回读到数组
			RHICmdList.ReadSurfaceData(TempTexture, FIntRect(0,0, In_Texture->SizeX, In_Texture->SizeY), ReturnArray, FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX));
			
			// 拷贝到RT
			FRHICopyTextureInfo Info;
			Info.Size = FIntVector(In_Texture->SizeX, In_Texture->SizeY, 1);
			RHICmdList.CopyTexture(TempTexture, RenderTargetResource->TextureRHI, Info);
		}
	);

	Fence.BeginFence();
	Fence.Wait();

	// 释放
	TempTexture.SafeRelease();

	// 立即更新资源
	Out_Texture->UpdateResourceImmediate(false);

	return ReturnArray;
}

1.1.4 CS

#include "/Engine/Public/Platform.ush"

Texture2D<float4> In_TestTexture;
RWTexture2D<float4> Out_TestTexture;

[numthreads(THREADGROUPSIZE_X, THREADGROUPSIZE_Y, THREADGROUPSIZE_Z)]
void CS_Zero(
    uint3 GroupID : SV_GroupID,
    uint3 DispatchThreadId : SV_DispatchThreadID,
    uint3 GroupThreadId : SV_GroupThreadID
)
{
    Out_TestTexture[DispatchThreadId.xy] = 1.0f - float4(In_TestTexture[DispatchThreadId.xy].xyz, 0.0f);
}

1.2 补充内容

cs并行计算优势很大,一些图像处理相关的引擎工具,可以用CS做提高运行效率。

1.2.1 Barrier

可以等这个Group都执行完Part A之后再执行Part B

//PartA
GroupMemoryBarrierWithGroupSync();
//PartB

1.2.2 Atomic

原子操作

InterlockedAdd(OutHistogramBuffer[histogram_index0], hog_index0_value);

1.2.3 展开循环、分支

有时候固定次数循环可以展开来优化,或者分支展开等操作

UNROLL
for( uint i = 0; i < 9; i++ )
{
    sum += InHistogramPow2Buffer[index0 + i];
}

二、Screen Pass

请添加图片描述
增加一个反色后处理

2.1 创建Shader

Engine/Shaders/Private/PostProcessTestZero.usf

#include "Common.ush"
#include "PostProcessCommon.ush"
#include "DeferredShadingCommon.ush"
#include "ScreenPass.ush"
 
SCREEN_PASS_TEXTURE_VIEWPORT(Input)
Texture2D InputTexture;
SamplerState InputSampler;

void MainVS(
    float4 InPosition : ATTRIBUTE0,
    float2 InTexCoord : ATTRIBUTE1,
    out noperspective float4 OutUVAndScreenPos : TEXCOORD0,
    out float4 OutPosition : SV_POSITION)
{
    DrawRectangle(InPosition, InTexCoord, OutPosition, OutUVAndScreenPos);
}
 
void MainPS(noperspective float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{  
    float2 UV = UVAndScreenPos.xy;
    OutColor = float4(float3(1.0f,1.0f,1.0f) - Texture2DSample(InputTexture, InputSampler, UV).rgb, 1.0f);
}

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessTestZero.h

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

#pragma once

#include "ScreenPass.h"

struct FTestZeroInputs
{
	FScreenPassRenderTarget OverrideOutput;

	FScreenPassTexture SceneColor;
};

FScreenPassTexture AddTestZeroPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FTestZeroInputs TestZeroInputs);

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessTestZero.cpp

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

#include "PostProcess/PostProcessTestZero.h"
#include "PostProcess/PostProcessCombineLUTs.h"
#include "CanvasTypes.h"
#include "RenderTargetTemp.h"
#include "UnrealEngine.h"

BEGIN_SHADER_PARAMETER_STRUCT(FZeroParameters, )
	SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
	SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Input)
	SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture)
	SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler)
END_SHADER_PARAMETER_STRUCT()

class FTestZeroVS : public FGlobalShader
{
	DECLARE_GLOBAL_SHADER(FTestZeroVS);
	SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FTestZeroVS, FGlobalShader);

	using FParameters = FZeroParameters;

	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1);
	}
};

IMPLEMENT_GLOBAL_SHADER(FTestZeroVS, "/Engine/Private/PostProcessTestZero.usf", "MainVS", SF_Vertex);

class FTestZeroPS : public FGlobalShader
{
public:
	DECLARE_GLOBAL_SHADER(FTestZeroPS);
	SHADER_USE_PARAMETER_STRUCT(FTestZeroPS, FGlobalShader);

	BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
		SHADER_PARAMETER_STRUCT_INCLUDE(FZeroParameters, TestZero)
		RENDER_TARGET_BINDING_SLOTS()
	END_SHADER_PARAMETER_STRUCT()

	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1);
	}
};

IMPLEMENT_GLOBAL_SHADER(FTestZeroPS, "/Engine/Private/PostProcessTestZero.usf", "MainPS", SF_Pixel);

FScreenPassTexture AddTestZeroPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FTestZeroInputs TestZeroInputs)
{
	check(TestZeroInputs.SceneColor.IsValid());
	RDG_EVENT_SCOPE(GraphBuilder, "TestZero");

	const FSceneViewFamily& ViewFamily = *(View.Family);
	const FScreenPassTextureViewport Viewport(TestZeroInputs.SceneColor);
	FScreenPassRenderTarget Output = TestZeroInputs.OverrideOutput;
	FRDGTextureDesc OutputDesc = TestZeroInputs.SceneColor.Texture->Desc;
	OutputDesc.Reset();
	OutputDesc.Flags |= TexCreate_RenderTargetable;

	if (!Output.IsValid())
	{
		Output = FScreenPassRenderTarget(GraphBuilder.CreateTexture(OutputDesc, TEXT("TestZero")), Viewport.Rect, View.GetOverwriteLoadAction());
	}

	// 创建参数
	FTestZeroPS::FParameters* PassParameters = GraphBuilder.AllocParameters<ftestzerops::fparameters>();
	PassParameters->TestZero.View = View.ViewUniformBuffer;
	PassParameters->TestZero.Input = GetScreenPassTextureViewportParameters(Viewport);
	PassParameters->TestZero.InputTexture = TestZeroInputs.SceneColor.Texture;
	PassParameters->TestZero.InputSampler = TStaticSamplerState<sf_bilinear, am_clamp,="" am_clamp="">::GetRHI();
	PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();

	TShaderMapRef<ftestzerovs> VertexShader(View.ShaderMap);
	TShaderMapRef<ftestzerops> PixelShader(View.ShaderMap);

	AddDrawScreenPass(
		GraphBuilder,
		RDG_EVENT_NAME("FTestZero %dx%d (PS)", Viewport.Rect.Width(), Viewport.Rect.Height()),
		View,
		Viewport,
		Viewport,
		FScreenPassPipelineState(VertexShader, PixelShader),
		PassParameters,
		[VertexShader, PixelShader, PassParameters](FRHICommandListImmediate& RHICmdList)
		{
			SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->TestZero);
			SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
		}
	);

	return MoveTemp(Output);
}

2.2 增加一个Show flag作为开关

Engine/Source/Runtime/Engine/Public/ShowFlagsValues.inl中增加一个定义

...
/** Screen Pass Zero*/
SHOWFLAG_FIXED_IN_SHIPPING(0, TestZero, SFG_Developer, NSLOCTEXT("UnrealEd", "TestZeroSF", "Test Zero"))
...

Engine/Source/Runtime/Engine/Public/ShowFlags.hInit函数中,设置默认值

...
SetTestZero(false);
...

2.3 在RDG中增加Pass

Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessing.cpp 里有AddPostProcessingPassesAddMobilePostProcessingPasses分别对应桌面和移动管线。需要分别添加。

2.3.1 将Pass添加到EPass和PassNames[]中

顺序要对应

...
enum class EPass : uint32
{
	MotionBlur,
	Tonemap,
	TestZero,// ←
	FXAA,
	...
};
...
const TCHAR* PassNames[] =
{
	TEXT("MotionBlur"),
	TEXT("Tonemap"),
	TEXT("TestZero"),//←
	TEXT("FXAA"),
	...
};

2.3.2 判断是否启用

在合适的位置增加是否启用的判断

if (IsPostProcessingEnabled(View))
{
	...
	PassSequence.SetEnabled(EPass::TestZero, EngineShowFlags.TestZero);
	...
}
else
{
	...
	PassSequence.SetEnabled(EPass::TestZero, false);
	...
}

2.3.3 根据EPass的顺序在合适的位置增加Pass

if (PassSequence.IsEnabled(EPass::TestZero))
{
	FTestZeroInputs TestZeroInputs;
	PassSequence.AcceptOverrideIfLastPass(EPass::TestZero, TestZeroInputs.OverrideOutput);
	TestZeroInputs.SceneColor = SceneColor;
	SceneColor = AddTestZeroPass(GraphBuilder, View, TestZeroInputs);
}

三、增加 Mesh Pass

在这里插入图片描述

3.1 增加Pass的材质参数

增加新Mesh Pass开关和所使用的材质,以及相关渲染状态变更时的处理
Engine/Source/Runtime/Engine/Classes/Components/PrimitiveComponent.h

/**
* Pass 2
*/
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadOnly, Category = CustomProperties)
uint8 bSecondPass : 1;

/**
* Pass 2 Material
*/
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadOnly, Category = CustomProperties)
TArray<umaterialinterface*> SecondPassMaterials;
...
UFUNCTION(BlueprintCallable, Category = "Azure")
void SetSecondPass(bool bInSecondPass);

UFUNCTION(BlueprintCallable, Category = "Azure")
void SetSecondPassMaterial(int32 Index, UMaterialInterface* InSecondPassMaterial);

Engine/Source/Runtime/Engine/Private/Components/PrimitiveComponent.cpp

void UPrimitiveComponent::SetSecondPass(bool bInSecondPass)
{
	if (bInSecondPass != bSecondPass)
	{
		bSecondPass = bInSecondPass;
		MarkRenderStateDirty();
	}
}

void UPrimitiveComponent::SetSecondPassMaterial(int32 Index, UMaterialInterface* InSecondPassMaterial)
{
	if (Index > SecondPassMaterials.Num())
	{
		SecondPassMaterials.AddZeroed(Index - SecondPassMaterials.Num());
	}
	if (SecondPassMaterials[Index] != InSecondPassMaterial)
	{
		SecondPassMaterials[Index] = InSecondPassMaterial;
		MarkRenderStateDirty();
	}
}

3.2 收集新Mesh Pass的材质

Engine/Source/Runtime/Engine/Private/Components/MeshComponent.cpp中增加新Mesh Pass材质的关联

FMaterialRelevance UMeshComponent::GetMaterialRelevance(ERHIFeatureLevel::Type InFeatureLevel) const
{
	// Combine the material relevance for all materials.
	FMaterialRelevance Result;
	for(int32 ElementIndex = 0;ElementIndex < GetNumMaterials();ElementIndex++)
	{
		UMaterialInterface const* MaterialInterface = GetMaterial(ElementIndex);
		if(!MaterialInterface)
		{
			MaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface);
		}
		Result |= MaterialInterface->GetRelevance_Concurrent(InFeatureLevel);
	}
	
	if (bSecondPass && SecondPassMaterials.Num() > 0)
	{
		for (int MaterialIndex = 0; MaterialIndex < SecondPassMaterials.Num(); MaterialIndex++)
		{
			if (IsValid(SecondPassMaterials[MaterialIndex]))
			{
				Result |= SecondPassMaterials[MaterialIndex]->GetRelevance_Concurrent(InFeatureLevel);
			}
		}
	}
	return Result;
}

Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp
Engine/Source/Runtime/Engine/Private/Components/StaticMeshComponent.cpp
中的GetUsedMaterials函数中添加新Mesh Pass所使用的的材质

...
if (bSecondPass && SecondPassMaterials.Num())
{
	for (int Index = 0; Index < SecondPassMaterials.Num(); Index++)
	{
		if (IsValid(SecondPassMaterials[Index]))
		{
			OutMaterials.Add(SecondPassMaterials[Index]);
		}
	}
}
...

3.3 PrimitiveComponent新增内容同步到PrimitiveSceneProxy中

Engine/Source/Runtime/Engine/Public/PrimitiveSceneProxy.h中增加

...
inline const bool SecondPass() const { return bSecondPass; }
inline int32 GetSecondPassMaterialNum() { return SecondPassMaterials.Num(); }
inline UMaterialInterface* GetSecondPassMaterial(int32 Index) const { return SecondPassMaterials.Num() > Index ? SecondPassMaterials[Index] : NULL; }
...
uint8 bSecondPass : 1;
TArray< UMaterialInterface*> SecondPassMaterials;
...


Engine/Source/Runtime/Engine/Private/PrimitiveSceneProxy.cpp 构造函数FPrimitiveSceneProxy中从PrimitiveComponent中拷贝参数

...
,bSecondPass(InComponent->bSecondPass)
,SecondPassMaterials(InComponent->SecondPassMaterials)
...

3.4 为新增Mesh Pass再收集一次网格

Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp中在迭代当前LOD的Sections时,收集两次

void FSkeletalMeshSceneProxy::GetMeshElementsConditionallySelectable(const TArray<const fsceneview*="">& Views, const FSceneViewFamily& ViewFamily, bool bInSelectable, uint32 VisibilityMap, FMeshElementCollector& Collector) const
{
	...
	{
		...
		{
			...
			for (FSkeletalMeshSectionIter Iter(LODIndex, *MeshObject, LODData, LODSection); Iter; ++Iter)
			{
				...
				// +Second
				if (SecondPass() && GetSecondPassMaterial(SectionElementInfo.UseMaterialIndex))
				{
					FSectionElementInfo SecondElementInfo = FSectionElementInfo(SectionElementInfo);
					SecondElementInfo.Material = GetSecondPassMaterial(SecondElementInfo.UseMaterialIndex);
					GetDynamicElementsSection(Views, ViewFamily, VisibilityMap, LODData, LODIndex, SectionIndex, bSectionSelected, SecondElementInfo, bInSelectable, Collector);
				}
			}
			...
		}
		...
	}
	...
}

Engine/Source/Runtime/Engine/Private/StaticMeshRender.cpp中增加

void FStaticMeshSceneProxy::DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)
{
	...
	{
		...
		{
			...
			{
				...
				{
					...
					for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
					{
						FMeshBatch BaseMeshBatch;
						if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, PrimitiveDPG, bIsMeshElementSelected, true, BaseMeshBatch))
						{
							...
							// +Second
							if (SecondPass() && GetSecondPassMaterialNum() > 0)
							{
								const FStaticMeshLODResources& LOD = RenderData->LODResources[LODIndex];
								const FStaticMeshVertexFactories& VFs = RenderData->LODVertexFactories[LODIndex];
								const FStaticMeshSection& Section = LOD.Sections[SectionIndex];

								int32 UseMaterialIndex = Section.MaterialIndex;
								if (IsValid(GetSecondPassMaterial((UseMaterialIndex))))
								{
									FMeshBatch SecondMeshBatch;
									if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, PrimitiveDPG, bIsMeshElementSelected, true, SecondMeshBatch))
									{
										SecondMeshBatch.CastShadow = false;
										SecondMeshBatch.bUseAsOccluder = false;
										SecondMeshBatch.MaterialRenderProxy = GetSecondPassMaterial(UseMaterialIndex)->GetRenderProxy();
										PDI->DrawMesh(SecondMeshBatch, ScreenSize);
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

</umaterialinterface*></sf_bilinear,></ftestzerops::fparameters></fcs_zeroshader>

posted @ 2021-08-29 15:11  Miria  阅读(606)  评论(0)    收藏  举报