ComputeShader是一些管线类操作或GPU数据处理所必须的一个环节,UE操作ComputeShader不同版本各种崩溃,API也是不断修改。本文基于我测试成功的UE5.3、UE5.4进行流程说明。
本文实现参考github库:
https://github.com/Will-Z666/UE5_ComputerShaderSample
该仓库就我写这篇文章时的状态,异步节点做了一半,传入贴图没有做ComputeShader的测试,因此我在代码中注释了一部分。
没有太多基础的童鞋可以先尝试测试跑通下这篇文章:
https://blog.csdn.net/grayrail/article/details/144704622
1.创建插件
1.1 首先新建一个UE5 C++工程以测试,并创建一个蓝图库插件。
1.2 修改CsRunner.cpp,在模块启动时导入Shader目录的usf:
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CsRunner.h"
#include "Interfaces/IPluginManager.h"
#define LOCTEXT_NAMESPACE "FCsRunnerModule"
void FCsRunnerModule::StartupModule()
{
const FString ShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("CsRunner"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping("/Project/Shaders", ShaderDir);
}
void FCsRunnerModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FCsRunnerModule, CsRunner)
1.3 修改build.cs,设置插件依赖库(slate其实不需要):
cpp
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"Renderer",
"RenderCore",
"RHI",
"Projects"
}
);
1.4 修改uplugin文件,设置插件导入阶段为"PostConfigInit":
cpp
"Modules": [
{
"Name": "CsRunner",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit"
}
]
1.5 在插件内目录新建Shader文件夹,新建ComputeShader的usf文件:
1.6 编写该usf文件内容以进行测试:
cpp
#include "/Engine/Public/Platform.ush"
RWTexture2D<float3> RenderTarget;
Texture2D<float3> InputTexture;
[numthreads(THREADGROUPSIZE_X, THREADGROUPSIZE_Y, THREADGROUPSIZE_Z)]
void MainComputeShader(
uint3 DispatchThreadId : SV_DispatchThreadID,
uint GroupIndex : SV_GroupIndex)
{
float x = DispatchThreadId.x;
float y = DispatchThreadId.y;
float z = 0;
RenderTarget[DispatchThreadId.xy] = sin(x);
}
2.核心代码编写
新建ComputeShaderDeclaration.h类,编写调用ComputeShader核心代码。
这部分代码会检查游戏是否在渲染线程,并插入渲染线程执行逻辑。
核心部分DispatchRenderThread在cpp文件中。
cpp
#pragma once
#include "CoreMinimal.h"
#include "GenericPlatform/GenericPlatformMisc.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "Engine/TextureRenderTarget2D.h"
struct FCSParameters
{
int X;
int Y;
int Z;
UTexture2D* InputTexture;
FRenderTarget* RenderTarget;
FCSParameters(int x, int y, int z)
: X(x)
, Y(y)
, Z(z)
{
}
};
class FComputeShaderInterface
{
public:
static void DispatchRenderThread(
FRHICommandListImmediate& RHICmdList,
FCSParameters Params
);
static void DispatchGameThread(
FCSParameters Params
)
{
ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)(
[Params](FRHICommandListImmediate& RHICmdList)
{
DispatchRenderThread(RHICmdList, Params);
});
}
static void Dispatch(
FCSParameters Params
)
{
if (IsInRenderingThread()) {
DispatchRenderThread(GetImmediateCommandList_ForRenderCommand(), Params);
}
else {
DispatchGameThread(Params);
}
}
};
新建ComputeShaderDeclaration.cpp类:
cpp
#include "ComputeShaderDeclaration.h"
#include "GlobalShader.h"
#include "MaterialShader.h"
#include "ShaderParameterStruct.h"
#include "RenderGraphResources.h"
#include "RenderGraphUtils.h"
#include "RenderGraphBuilder.h"
#include "RenderTargetPool.h"
#define NUM_THREADS_PER_GROUP_DIMENSION_X 32
#define NUM_THREADS_PER_GROUP_DIMENSION_Y 32
#define NUM_THREADS_PER_GROUP_DIMENSION_Z 1
DECLARE_STATS_GROUP(TEXT("ComputeShader"), STATGROUP_ComputeShader, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("ComputeShader Execute"), STAT_ComputeShader_Execute, STATGROUP_ComputeShader);
class FComputeShader : public FGlobalShader
{
public:
//Declare this class as a global shader
DECLARE_GLOBAL_SHADER(FComputeShader);
//Tells the engine that this shader uses a structure for its parameters
SHADER_USE_PARAMETER_STRUCT(FComputeShader, FGlobalShader);
/// <summary>
/// DECLARATION OF THE PARAMETER STRUCTURE
/// The parameters must match the parameters in the HLSL code
/// For each parameter, provide the C++ type, and the name (Same name used in HLSL code)
/// </summary>
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RenderTarget)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture)
END_SHADER_PARAMETER_STRUCT()
public:
//Called by the engine to determine which permutations to compile for this shader
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
//Modifies the compilations environment of the shader
static inline void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
//We're using it here to add some preprocessor defines. That way we don't have to change both C++ and HLSL code
// when we change the value for NUM_THREADS_PER_GROUP_DIMENSION
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), NUM_THREADS_PER_GROUP_DIMENSION_X);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), NUM_THREADS_PER_GROUP_DIMENSION_Y);
OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Z"), NUM_THREADS_PER_GROUP_DIMENSION_Z);
}
};
IMPLEMENT_GLOBAL_SHADER(FComputeShader, "/Project/Shaders/Shader.usf", "MainComputeShader", SF_Compute);
void FComputeShaderInterface::DispatchRenderThread(FRHICommandListImmediate& RHICmdList, FCSParameters Params) {
FRDGBuilder GraphBuilder(RHICmdList);
{
SCOPE_CYCLE_COUNTER(STAT_ComputeShader_Execute);
DECLARE_GPU_STAT(ComputeShader)
RDG_EVENT_SCOPE(GraphBuilder, "ComputeShader");
RDG_GPU_STAT_SCOPE(GraphBuilder, ComputeShader);
typename FComputeShader::FPermutationDomain PermutationVector;
TShaderMapRef<FComputeShader> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector);
bool bIsShaderValid = ComputeShader.IsValid();
if (bIsShaderValid) {
FComputeShader::FParameters* PassParameters = GraphBuilder.AllocParameters<FComputeShader::FParameters>();
FRDGTextureDesc Desc(FRDGTextureDesc::Create2D(Params.RenderTarget->GetSizeXY(), PF_B8G8R8A8, FClearValueBinding::White, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV));
FRDGTextureRef TmpTexture = GraphBuilder.CreateTexture(Desc, TEXT("ComputeShader_TempTexture"));
FRDGTextureRef TargetTexture = RegisterExternalTexture(GraphBuilder, Params.RenderTarget->GetRenderTargetTexture(), TEXT("ComputeShader_RT"));
PassParameters->RenderTarget = GraphBuilder.CreateUAV(TmpTexture);
//传入贴图这里我注释了
//FRHITexture* InputTexture = Params.InputTexture->GetResource()->GetTextureRHI();
//FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InputTexture, TEXT("ComputeShader_InputTexture"));
//PassParameters->InputTexture = RDGSourceTexture;
auto GroupCount = FComputeShaderUtils::GetGroupCount(FIntVector(Params.X, Params.Y, Params.Z), FComputeShaderUtils::kGolden2DGroupSize);
GraphBuilder.AddPass(
RDG_EVENT_NAME("ExecuteExampleComputeShader"),
PassParameters,
ERDGPassFlags::AsyncCompute,
[&PassParameters, ComputeShader, GroupCount](FRHIComputeCommandList& RHICmdList)
{
FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, GroupCount);
});
if (TargetTexture->Desc.Format == PF_B8G8R8A8) {
AddCopyTexturePass(GraphBuilder, TmpTexture, TargetTexture, FRHICopyTextureInfo());
}
}
}
GraphBuilder.Execute();
}
现在C++部分基本已经完成,下面开始讲解蓝图配置部分。
3.蓝图配置
3.1 右键新建RenderTarget,配置好格式参数:
3.2 新建Material,拖入RenedrTarget并设置材质参数:
3.3 将该材质赋予场景中的测试模型。
3.4 新建Actor蓝图,增加如下节点,RenderTarget外部传入即可:
最后在场景中放入该Actor进行测试。