BlueRoseNote/03-UnrealEngine/Rendering/RenderingPipeline/RenderDependencyGraph学习笔记(二)——在插件中使用ComputeShader.md
2023-06-29 11:55:02 +08:00

11 KiB
Raw Permalink Blame History

前言

UE4 RDG(RenderDependencyGraph)渲染框架本质上是在原有渲染框架上基础上进行再次封装它主要的设计目的就为了更好的管理每个资源的生命周期。同时Ue4的渲染管线已经都替换成了RDG框架了但依然有很多非重要模块以及第三方插件没有替换所以掌握以下RDG框架还是十分有必要的。

上一篇文章已经大致介绍了RDG框架的使用方法。看了前文的资料与官方的ppt再看一下渲染管线的的代码基本就可以上手了写Shader。但作为一个工作与Ue4一点关系的业余爱好者用的2014年的电脑通过修改渲染管线的方式来写Shader不太现实编译一次3小时真心伤不起。同时google与Epic论坛也没有在插件中使用RDG的资料所以我就花了些时间探索了一下用法最后写了本文。因为非全职开发UE4时间精力有限不可避免得会有些错误还请见谅。 代码写在我的插件里如果感觉有用麻烦Star一下。位于在Rendering下的SimpleRDG.cpp与SimpleRenderingExample.h中。

https://github.com/blueroseslol/BRPlugins

首先还是从ComputeShader开始因为比较简单。

参考文件

下面推荐几个参考文件强烈推荐看GenerateMips包含RDG Compute与GlobalShader两个案例。

  • GenerateMips.cpp
  • ShaderPrint.cpp
  • PostProcessCombineLUTs.cpp

搜索ERDGPassFlags::Compute就可以找到RDG调用ComputeShader的代码。

RDG的数据导入与导出

RDG需要使用RegisterExternalBuffer/Texture导入数据GraphBuilder.QueueExternalBuffer/Texture取出渲染结果这需要使用一个TRefCountPtr对象。直接使用RHIImmCmdList.CopyTexture尝试将FRDGTextureRef的数据拷贝出来时会触发禁止访问的断言。

UAV

UAV用于保存ComputeShader的计算结果它的创建步骤如下

实现使用宏声明Shader变量

SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)

使用对应的函数创建并且绑定到对应Shader变量上

SHADER_PARAMETER_UAV对应CreateTexture()SHADER_PARAMETER_RDG_BUFFER_UAV对应CreateUAV()。(前者没试过)

可以使用FRDGTextureUAVDesc与Buff两种方式进行创建。

SRV创建与使用

UAV是不能直接在Shader里读取所以需要通过创建SRV的方式来读取。因为我并没有测试SRV所以这里贴一下FGenerateMips中的部分代码

TSharedPtr<FGenerateMipsStruct> FGenerateMips::SetupTexture(FRHITexture* InTexture, const FGenerateMipsParams& InParams)
{
check(InTexture->GetTexture2D());

	TSharedPtr<FGenerateMipsStruct> GenMipsStruct = MakeShareable(new FGenerateMipsStruct());

		FPooledRenderTargetDesc Desc;
		Desc.Extent.X = InTexture->GetSizeXYZ().X;
		Desc.Extent.Y = InTexture->GetSizeXYZ().Y;
		Desc.TargetableFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV;
		Desc.Format = InTexture->GetFormat();
		Desc.NumMips = InTexture->GetNumMips();;
		Desc.DebugName = TEXT("GenerateMipPooledRTTexture");

		//Create the Pooled Render Target Resource from the input texture
		FRHIResourceCreateInfo CreateInfo(Desc.DebugName);

		//Initialise a new render target texture for creating an RDG Texture
		FSceneRenderTargetItem RenderTexture;

		//Update all the RenderTexture info
		RenderTexture.TargetableTexture = InTexture;
		RenderTexture.ShaderResourceTexture = InTexture;

		RenderTexture.SRVs.Empty(Desc.NumMips);
		RenderTexture.MipUAVs.Empty(Desc.NumMips);
		for (uint8 MipLevel = 0; MipLevel < Desc.NumMips; MipLevel++)
		{
			FRHITextureSRVCreateInfo SRVDesc;
			SRVDesc.MipLevel = MipLevel;
			RenderTexture.SRVs.Emplace(SRVDesc, RHICreateShaderResourceView((FTexture2DRHIRef&)InTexture, SRVDesc));

			RenderTexture.MipUAVs.Add(RHICreateUnorderedAccessView(InTexture, MipLevel));
		}
		RHIBindDebugLabelName(RenderTexture.TargetableTexture, Desc.DebugName);
		RenderTexture.UAV = RenderTexture.MipUAVs[0];

		//Create the RenderTarget from the PooledRenderTarget Desc and the new RenderTexture object.
	GRenderTargetPool.CreateUntrackedElement(Desc, GenMipsStruct->RenderTarget, RenderTexture);

		//Specify the Sampler details based on the input.
	GenMipsStruct->Sampler.Filter = InParams.Filter;
	GenMipsStruct->Sampler.AddressU = InParams.AddressU;
	GenMipsStruct->Sampler.AddressV = InParams.AddressV;
	GenMipsStruct->Sampler.AddressW = InParams.AddressW;

	return GenMipsStruct;
}

void FGenerateMips::Compute(FRHICommandListImmediate& RHIImmCmdList, FRHITexture* InTexture, TSharedPtr<FGenerateMipsStruct> GenMipsStruct)
{
    check(IsInRenderingThread());	
	//Currently only 2D textures supported
	check(InTexture->GetTexture2D());

	//Ensure the generate mips structure has been initialised correctly.
	check(GenMipsStruct);

	//Begin rendergraph for executing the compute shader
	FRDGBuilder GraphBuilder(RHIImmCmdList);
	FRDGTextureRef GraphTexture = GraphBuilder.RegisterExternalTexture(GenMipsStruct->RenderTarget, TEXT("GenerateMipsGraphTexture"));
	
	···
	
	FRDGTextureSRVDesc SRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(GraphTexture, MipLevel - 1);

	FGenerateMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateMipsCS::FParameters>();
	PassParameters->MipInSRV = GraphBuilder.CreateSRV(SRVDesc);
}

可以看出是先通过CreateUntrackedElement()创建IPooledRenderTarget之后再调用RegisterExternalTexture进行注册最后再调用CreateSRV创建SRV。

另外IPooledRenderTarget除了有CreateUntrackedElement()还有FindFreeElement()。这个函数就适合在多Pass RDG中使用了。

FRDGTextureRef GraphTexture = GraphBuilder.RegisterExternalTexture(GenMipsStruct->RenderTarget, TEXT("GenerateMipsGraphTexture"));

FRDGTextureSRVDesc SRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(GraphTexture, MipLevel - 1);

FGenerateMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateMipsCS::FParameters>();
PassParameters->MipInSRV = GraphBuilder.CreateSRV(SRVDesc);

在Shader中读取SRV

读取SRV与读取Texture相同首先需要创建采样器

SHADER_PARAMETER_SAMPLER(SamplerState, MipSampler)

PassParameters->MipSampler = RHIImmCmdList.CreateSamplerState(GenMipsStruct->Sampler);

之后就可以想Texture2D那样进行取值了

#pragma once
#include "Common.ush"
#include "GammaCorrectionCommon.ush"

float2 TexelSize;
Texture2D MipInSRV;
#if GENMIPS_SRGB
RWTexture2D<half4> MipOutUAV;
#else
RWTexture2D<float4> MipOutUAV;
#endif
SamplerState MipSampler;

[numthreads(8, 8, 1)]
void MainCS(uint3 DT_ID : SV_DispatchThreadID)
{
    float2 UV = TexelSize * (DT_ID.xy + 0.5f);

#if GENMIPS_SRGB
    half4 outColor = MipInSRV.SampleLevel(MipSampler, UV, 0);
    outColor = half4(LinearToSrgb(outColor.xyz), outColor.w);
#else
    float4 outColor = MipInSRV.SampleLevel(MipSampler, UV, 0);
#endif

#if GENMIPS_SWIZZLE
    MipOutUAV[DT_ID.xy] = outColor.zyxw;
#else
    MipOutUAV[DT_ID.xy] = outColor;
#endif
}

RDGCompute完整代码

以下就是我写的例子,因为比较简单而且有注释,所以就不详细解释了。

void RDGCompute(FRHICommandListImmediate &RHIImmCmdList, FTexture2DRHIRef RenderTargetRHI, FSimpleShaderParameter InParameter)
{
	check(IsInRenderingThread());

	//Create PooledRenderTarget
	FPooledRenderTargetDesc RenderTargetDesc = FPooledRenderTargetDesc::Create2DDesc(RenderTargetRHI->GetSizeXY(),RenderTargetRHI->GetFormat(), FClearValueBinding::Black, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV, false);
	TRefCountPtr<IPooledRenderTarget> PooledRenderTarget;

	//RDG Begin
	FRDGBuilder GraphBuilder(RHIImmCmdList);
	FRDGTextureRef RDGRenderTarget = GraphBuilder.CreateTexture(RenderTargetDesc, TEXT("RDGRenderTarget"));

	//Setup Parameters
	FSimpleUniformStructParameters StructParameters;
	StructParameters.Color1 = InParameter.Color1;
	StructParameters.Color2 = InParameter.Color2;
	StructParameters.Color3 = InParameter.Color3;
	StructParameters.Color4 = InParameter.Color4;
	StructParameters.ColorIndex = InParameter.ColorIndex;

	FSimpleRDGComputeShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGComputeShader::FParameters>();
	FRDGTextureUAVDesc UAVDesc(RDGRenderTarget);
	Parameters->SimpleUniformStruct = TUniformBufferRef<FSimpleUniformStructParameters>::CreateUniformBufferImmediate(StructParameters, UniformBuffer_SingleFrame);
	Parameters->OutTexture = GraphBuilder.CreateUAV(UAVDesc);

	//Get ComputeShader From GlobalShaderMap
	const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel; //ERHIFeatureLevel::SM5
	FGlobalShaderMap *GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
	TShaderMapRef<FSimpleRDGComputeShader> ComputeShader(GlobalShaderMap);

	//Compute Thread Group Count
	FIntVector ThreadGroupCount(
		RenderTargetRHI->GetSizeX() / 32,
		RenderTargetRHI->GetSizeY() / 32,
		1);

	//ValidateShaderParameters(PixelShader, Parameters);
	//ClearUnusedGraphResources(PixelShader, Parameters);

	GraphBuilder.AddPass(
		RDG_EVENT_NAME("RDGCompute"),
		Parameters,
		ERDGPassFlags::Compute,
		[Parameters, ComputeShader, ThreadGroupCount](FRHICommandList &RHICmdList) {
			FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *Parameters, ThreadGroupCount);
		});

	GraphBuilder.QueueTextureExtraction(RDGRenderTarget, &PooledRenderTarget);
	GraphBuilder.Execute();

	//Copy Result To RenderTarget Asset
	RHIImmCmdList.CopyTexture(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FRHICopyTextureInfo());
	//RHIImmCmdList.CopyToResolveTarget(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FResolveParams());
}

调用绘制函数

与传统方法类似调用上述渲染函数时需要使用ENQUEUE_RENDER_COMMAND(CaptureCommand)[]。下面是我写在蓝图函数库的代码。

void USimpleRenderingExampleBlueprintLibrary::UseRDGComput(const UObject *WorldContextObject, UTextureRenderTarget2D *OutputRenderTarget, FSimpleShaderParameter Parameter)
{
	check(IsInGameThread());

	FTexture2DRHIRef RenderTargetRHI = OutputRenderTarget->GameThread_GetRenderTargetResource()->GetRenderTargetTexture();

	ENQUEUE_RENDER_COMMAND(CaptureCommand)
	(
		[RenderTargetRHI, Parameter](FRHICommandListImmediate &RHICmdList) {
			SimpleRenderingExample::RDGCompute(RHICmdList, RenderTargetRHI, Parameter);
		});
}

如何使用

直接在蓝图中调用即可

注意RenderTarget的格式需要与UAV的格式一一对应。

结果: