## 前言 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](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, MyUAV) ``` ### 使用对应的函数创建并且绑定到对应Shader变量上 SHADER_PARAMETER_UAV对应CreateTexture(),SHADER_PARAMETER_RDG_BUFFER_UAV对应CreateUAV()。(前者没试过) 可以使用FRDGTextureUAVDesc与Buff两种方式进行创建。 ## SRV创建与使用 UAV是不能直接在Shader里读取,所以需要通过创建SRV的方式来读取。因为我并没有测试SRV,所以这里贴一下FGenerateMips中的部分代码: ```c++ TSharedPtr FGenerateMips::SetupTexture(FRHITexture* InTexture, const FGenerateMipsParams& InParams) { check(InTexture->GetTexture2D()); TSharedPtr 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; } ``` ```c++ void FGenerateMips::Compute(FRHICommandListImmediate& RHIImmCmdList, FRHITexture* InTexture, TSharedPtr 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(); 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(); 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 MipOutUAV; #else RWTexture2D 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完整代码 以下就是我写的例子,因为比较简单而且有注释,所以就不详细解释了。 ```c++ 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 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(); FRDGTextureUAVDesc UAVDesc(RDGRenderTarget); Parameters->SimpleUniformStruct = TUniformBufferRef::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 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); }); } ``` ## 如何使用 直接在蓝图中调用即可 ![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/UseRDGCompute.png) 注意RenderTarget的格式需要与UAV的格式一一对应。 ![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RTFormat.png) 结果: ![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RDGComputeShaderResult.png)