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

12 KiB
Raw Blame History

前言

在插件中使用RDG调用ComputeShader的方法我花了没几天就搞定了。但PixelShader相对来说就麻烦了怎么搞都没有绘制到RT上。最后还是通过改写DrawFullscreenPixelShader的代码搞定了。

另外说一下PixelShader的调用和传统的GlobalShader调用很相似。

设置Shader虚拟目录

这个之前忘记说了,所以这里补充一下,具体操作为在插件的模块启动函数中添加以下代码::

void FBRPluginsModule::StartupModule()
{
	FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("BRPlugins"))->GetBaseDir(), TEXT("Shaders"));
	AddShaderSourceDirectoryMapping(TEXT("/BRPlugins"), PluginShaderDir);
}

之后就可以使用这个虚拟目录来定义Shader

IMPLEMENT_GLOBAL_SHADER(FSimpleRDGComputeShader, "/BRPlugins/Private/SimpleComputeShader.usf", "MainCS", SF_Compute);

参考案例

这里我没什么管理可以推荐的感觉都不是很好。但你可以搜索ERDGPassFlags::Raster就可以找到RDG调用PixelShader的代码。

使用DrawFullscreenPixelShader绘制

在RDG中已经封装了一个绘制函数DrawFullscreenPixelShader()可以拿来做测试。使用的方法也比较简单直接在GraphBuilder.AddPass的Lambda中调用DrawFullscreenPixelShader即可。但

其使用的顶点格式是公共资源(CommonRenderResources.h)中的GFilterVertexDeclaration.VertexDeclarationRHI、GScreenRectangleVertexBuffer.VertexBufferRHI、GScreenRectangleIndexBuffer.IndexBufferRHI。

void FScreenRectangleVertexBuffer::InitRHI()
{
	TResourceArray<FFilterVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
	Vertices.SetNumUninitialized(6);

	Vertices[0].Position = FVector4(1, 1, 0, 1);
	Vertices[0].UV = FVector2D(1, 1);

	Vertices[1].Position = FVector4(0, 1, 0, 1);
	Vertices[1].UV = FVector2D(0, 1);

	Vertices[2].Position = FVector4(1, 0, 0, 1);
	Vertices[2].UV = FVector2D(1, 0);

	Vertices[3].Position = FVector4(0, 0, 0, 1);
	Vertices[3].UV = FVector2D(0, 0);

	//The final two vertices are used for the triangle optimization (a single triangle spans the entire viewport )
	Vertices[4].Position = FVector4(-1, 1, 0, 1);
	Vertices[4].UV = FVector2D(-1, 1);

	Vertices[5].Position = FVector4(1, -1, 0, 1);
	Vertices[5].UV = FVector2D(1, -1);

	// Create vertex buffer. Fill buffer with initial data upon creation
	FRHIResourceCreateInfo CreateInfo(&Vertices);
	VertexBufferRHI = RHICreateVertexBuffer(Vertices.GetResourceDataSize(), BUF_Static, CreateInfo);
}

DrawFullscreenPixelShader()使用的VertexShader是FScreenVertexShaderVSusf为FullscreenVertexShader.usf。代码如下

#include "../Common.ush"

void MainVS(
	float2 InPosition : ATTRIBUTE0,
	float2 InUV       : ATTRIBUTE1, // TODO: kill
	out float4 Position : SV_POSITION)
{
	Position = float4(InPosition.x * 2.0 - 1.0, 1.0 - 2.0 * InPosition.y, 0, 1);
}

这里可以看得出一个问题那就是PixelShader无法获得UV坐标。所以DrawFullscreenPixelShader()能做事情很有限。因此本人写的例子是自定义了顶点格式。

CommonRenderResources

Ue4的已经帮我们设置好了几个基础的VertexDeclaration位于RenderCore\Public\CommonRenderResources.h。

GEmptyVertexDeclaration.VertexDeclarationRHI在Shader中的Input为

in uint InstanceId : SV_InstanceID,
in uint VertexId : SV_VertexID,

GFilterVertexDeclaration.VertexDeclarationRHI在Shader中的Input为

in float4 InPosition : ATTRIBUTE0,
in float2 InUV : ATTRIBUTE1,  

对应的顶点缓存与索引缓存为TGlobalResource GScreenRectangleVertexBuffer与TGlobalResource GScreenRectangleIndexBuffer;

如果你想在调用PixelShader时使用FScreenRectangleVertexBuffer就需要转换UV坐标了(-1,1)=>(0,1)因为FScreenRectangleVertexBuffer的UV定义范围为(-1,1)。

RenderTarget的传入与绑定

传入的RenderTarget皆可以用Texture2D类型声明。例如

BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
	SHADER_PARAMETER_STRUCT_REF(FSimpleUniformStructParameters, SimpleUniformStruct)
	SHADER_PARAMETER_TEXTURE(Texture2D, TextureVal)
	SHADER_PARAMETER_SAMPLER(SamplerState, TextureSampler)
	SHADER_PARAMETER(FVector4, SimpleColor)
	RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()

绑定需要在上面的声明宏中加入RENDER_TARGET_BINDING_SLOTS(),之后设置变量时绑定:

FSimpleRDGPixelShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGPixelShader::FParameters>();
Parameters->RenderTargets[0] = FRenderTargetBinding(RDGRenderTarget, ERenderTargetLoadAction::ENoAction);

还可以绑定DepthStencil

Parameters->RenderTargets.DepthStencil = FDepthStencilBinding(OutDepthTexture,ERenderTargetLoadAction::ELoad,ERenderTargetLoadAction::ELoad,FExclusiveDepthStencil::DepthNop_StencilWrite);

在USF中对应Out为

out float4 OutColor : SV_Target0, 
out float  OutDepth : SV_Depth

如果有多个RenderTarget绑定会如SV_Target0、SV_Target1、SV_Target2一般递增。

资源清理

本人案例中因为只有一个Pass所以就没有用这两个函数。

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

RDGPixelDraw

直接上代码了。

void RDGDraw(FRHICommandListImmediate &RHIImmCmdList, FTexture2DRHIRef RenderTargetRHI, FSimpleShaderParameter InParameter, const FLinearColor InColor, FTexture2DRHIRef InTexture)
{
	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;

	FSimpleRDGPixelShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGPixelShader::FParameters>();
	Parameters->TextureVal = InTexture;
	Parameters->TextureSampler = TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
	Parameters->SimpleColor = InColor;
	Parameters->SimpleUniformStruct = TUniformBufferRef<FSimpleUniformStructParameters>::CreateUniformBufferImmediate(StructParameters, UniformBuffer_SingleFrame);
	Parameters->RenderTargets[0] = FRenderTargetBinding(RDGRenderTarget, ERenderTargetLoadAction::ENoAction);

	const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel; //ERHIFeatureLevel::SM5
	FGlobalShaderMap *GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
	TShaderMapRef<FSimpleRDGVertexShader> VertexShader(GlobalShaderMap);
	TShaderMapRef<FSimpleRDGPixelShader> PixelShader(GlobalShaderMap);

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

	GraphBuilder.AddPass(
		RDG_EVENT_NAME("RDGDraw"),
		Parameters,
		ERDGPassFlags::Raster,
		[Parameters, VertexShader, PixelShader, GlobalShaderMap](FRHICommandList &RHICmdList) {
			FRHITexture2D *RT = Parameters->RenderTargets[0].GetTexture()->GetRHI()->GetTexture2D();
			RHICmdList.SetViewport(0, 0, 0.0f, RT->GetSizeX(), RT->GetSizeY(), 1.0f);

			FGraphicsPipelineStateInitializer GraphicsPSOInit;
			RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
			GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
			GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
			GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
			GraphicsPSOInit.PrimitiveType = PT_TriangleList;
			GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GTextureVertexDeclaration.VertexDeclarationRHI;

			GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
			GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
			SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
			RHICmdList.SetStencilRef(0);
			SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *Parameters);

			RHICmdList.SetStreamSource(0, GRectangleVertexBuffer.VertexBufferRHI, 0);
			RHICmdList.DrawIndexedPrimitive(
				GRectangleIndexBuffer.IndexBufferRHI,
				/*BaseVertexIndex=*/0,
				/*MinIndex=*/0,
				/*NumVertices=*/4,
				/*StartIndex=*/0,
				/*NumPrimitives=*/2,
				/*NumInstances=*/1);
		});

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

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

调用方法和之前的ComputeShader部分相同这里就不赘述了。具体的可以参考我的插件。

自定义顶点格式部分

struct FTextureVertex
	{
		FVector4 Position;
		FVector2D UV;
	};

	class FRectangleVertexBuffer : public FVertexBuffer
	{
	public:
		/** Initialize the RHI for this rendering resource */
		void InitRHI() override
		{
			TResourceArray<FTextureVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
			Vertices.SetNumUninitialized(6);

			Vertices[0].Position = FVector4(1, 1, 0, 1);
			Vertices[0].UV = FVector2D(1, 1);

			Vertices[1].Position = FVector4(-1, 1, 0, 1);
			Vertices[1].UV = FVector2D(0, 1);

			Vertices[2].Position = FVector4(1, -1, 0, 1);
			Vertices[2].UV = FVector2D(1, 0);

			Vertices[3].Position = FVector4(-1, -1, 0, 1);
			Vertices[3].UV = FVector2D(0, 0);

			//The final two vertices are used for the triangle optimization (a single triangle spans the entire viewport )
			Vertices[4].Position = FVector4(-1, 1, 0, 1);
			Vertices[4].UV = FVector2D(-1, 1);

			Vertices[5].Position = FVector4(1, -1, 0, 1);
			Vertices[5].UV = FVector2D(1, -1);

			// Create vertex buffer. Fill buffer with initial data upon creation
			FRHIResourceCreateInfo CreateInfo(&Vertices);
			VertexBufferRHI = RHICreateVertexBuffer(Vertices.GetResourceDataSize(), BUF_Static, CreateInfo);
		}
	};

	class FRectangleIndexBuffer : public FIndexBuffer
	{
	public:
		/** Initialize the RHI for this rendering resource */
		void InitRHI() override
		{
			// Indices 0 - 5 are used for rendering a quad. Indices 6 - 8 are used for triangle optimization.
			const uint16 Indices[] = {0, 1, 2, 2, 1, 3, 0, 4, 5};

			TResourceArray<uint16, INDEXBUFFER_ALIGNMENT> IndexBuffer;
			uint32 NumIndices = UE_ARRAY_COUNT(Indices);
			IndexBuffer.AddUninitialized(NumIndices);
			FMemory::Memcpy(IndexBuffer.GetData(), Indices, NumIndices * sizeof(uint16));

			// Create index buffer. Fill buffer with initial data upon creation
			FRHIResourceCreateInfo CreateInfo(&IndexBuffer);
			IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16), IndexBuffer.GetResourceDataSize(), BUF_Static, CreateInfo);
		}
	};

	class FTextureVertexDeclaration : public FRenderResource
	{
	public:
		FVertexDeclarationRHIRef VertexDeclarationRHI;
		virtual void InitRHI() override
		{
			FVertexDeclarationElementList Elements;
			uint32 Stride = sizeof(FTextureVertex);
			Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, Position), VET_Float2, 0, Stride));
			Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, UV), VET_Float2, 1, Stride));
			VertexDeclarationRHI = RHICreateVertexDeclaration(Elements);
		}
		virtual void ReleaseRHI() override
		{
			VertexDeclarationRHI.SafeRelease();
		}
	};

	/*
	 *  Vertex Resource Declaration
	 */
	extern TGlobalResource<FTextureVertexDeclaration> GTextureVertexDeclaration;
	extern TGlobalResource<FRectangleVertexBuffer> GRectangleVertexBuffer;
	extern TGlobalResource<FRectangleIndexBuffer> GRectangleIndexBuffer;
TGlobalResource<FTextureVertexDeclaration> GTextureVertexDeclaration;
TGlobalResource<FRectangleVertexBuffer> GRectangleVertexBuffer;
TGlobalResource<FRectangleIndexBuffer> GRectangleIndexBuffer;