UE4的Global Shader在很久之前的版本就有了,并且底层的渲染管线也是使用Global Shader渲染Light Shaft、Volumetric Fog等等。而且由于版本的更迭,很多设置参数的方式和调用的函数都渐渐改变。虽然旧的一些特性依然使用原来的方式,但新的一些特性如4.24的大气系统都开始使用新的方式。本文记录的就是4.24版本使用较新的方式设置参数,传递参数以及调用函数的方法。
创建插件
使用UE4.24版本创建空插件模板,命名CustomGlobalShader。
之后会将渲染逻辑和游戏逻辑分别写在两个模块中,CustomGlobalShader.uplugin中填写需要加载的模块。
为两个模块分别创建两个文件夹,并且创建Shaders文件夹作为shader路径
ShaderDeclaration模块
创建模块
首先修改.Build.cs文件,添加依赖的模块,如Renderer, RendererCore, RHI和Projects。1
2
3
4
5
6
7
8
9
10
11
12using UnrealBuildTool;
public class ShaderDeclaration : ModuleRules
{
public ShaderDeclaration(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PrivateDependencyModuleNames.AddRange( new string[]
{ "Core", "CoreUObject", "Engine", "Renderer", "RenderCore", "RHI", "Projects" });
}
}
ShaderDeclaration.h
因为在插件中已经有了模块接口,直接用它与Renderer交互即可。并且提供了将游戏模块与渲染hook解耦的优点,允许更安全和简单的清理。随着4.22的到来,API中可以看到Renderer被大改成更现代的使用方式,就像DX12一样。最大的区别是现在必须将渲染代码封装到一个Render Graph或者Render Pass中。两者各有优点。
1 |
|
ShaderDeclaration.cpp
游戏线程会调用以下的方法,开始渲染、停止渲染、更新参数等等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
IMPLEMENT_MODULE(FShaderDeclarationModule, ShaderDeclaration)
// 声明一些GPU统计数据,后续方便追踪
DECLARE_GPU_STAT_NAMED(GlobalShaderPlugin_Render, TEXT("GlobalShaderPlugin: Root Render"));
DECLARE_GPU_STAT_NAMED(GlobalShaderPlugin_Pixel, TEXT("GlobalShaderPlugin: Render Pixel Shader"));
void FShaderDeclarationModule::StartupModule()
{
OnPostResolvedSceneColorHanndle.Reset();
bCachedParametersValid = false;
// 映射虚拟的着色器资源路径到插件实际的着色器路径
FString GlobalShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("CustomGlobalShader"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/CustomShaders"), GlobalShaderDir);
}
void FShaderDeclarationModule::ShutdownModule()
{
EndRendering();
}
void FShaderDeclarationModule::BeginRendering()
{
if (OnPostResolvedSceneColorHanndle.IsValid())
{
return;
}
bCachedParametersValid = false;
// 获取Renderer模块
const FName RendererModuleName("Renderer");
IRendererModule* RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
if (RendererModule)
{
OnPostResolvedSceneColorHanndle = RendererModule->GetResolvedSceneColorCallbacks().AddRaw(this, &FShaderDeclarationModule::PostResolveSceneColor_RenderThread);
}
}
void FShaderDeclarationModule::EndRendering()
{
if (!OnPostResolvedSceneColorHanndle.IsValid())
{
return;
}
const FName RendererModuleName("Renderer");
IRendererModule* RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
if (RendererModule)
{
RendererModule->GetResolvedSceneColorCallbacks().Remove(OnPostResolvedSceneColorHanndle);
}
OnPostResolvedSceneColorHanndle.Reset();
}
void FShaderDeclarationModule::UpdateParameters(FShaderUsageParameters& DrawParameters)
{
RenderEveryFrameLock.Lock();
CachedShaderUsageParameters = DrawParameters;
bCachedParametersValid = true;
RenderEveryFrameLock.Unlock();
}
void FShaderDeclarationModule::PostResolveSceneColor_RenderThread(FRHICommandListImmediate& RHICmdList, class FSceneRenderTargets& SceneContext)
{
if (!bCachedParametersValid)
{
return;
}
// 根据数据可以选择是否锁,添加此代码只是为了演示如何锁
RenderEveryFrameLock.Lock();
FShaderUsageParameters Copy = CachedShaderUsageParameters;
RenderEveryFrameLock.Unlock();
Draw_RenderThread(Copy);
}
void FShaderDeclarationModule::Draw_RenderThread(const FShaderUsageParameters& DrawParameters)
{
check(IsInRenderingThread());
if (!DrawParameters.RenderTarget)
{
return;
}
FRHICommandListImmediate& RHICmdList = GRHICommandList.GetImmediateCommandList();
QUICK_SCOPE_CYCLE_COUNTER(STAT_GlobalShaderPlugin_Render); // 为UE4前端收集CPU分析数据
SCOPED_DRAW_EVENT(RHICmdList, GlobalShaderPlugin_Render); // 为分析GPU活动添加的元数据,可以用RenderDoc这种工具查看
if (!UserRenderTarget.IsValid())
{
// 创建缓存RT描述符,FPooledRenderTarget是渲染线程允许共享和纹理可视化的渲染目标。
FPooledRenderTargetDesc UserRenderTargetDesc(FPooledRenderTargetDesc::Create2DDesc(DrawParameters.GetRenderTargetSize(), PF_R32_UINT, FClearValueBinding::None, TexCreate_None, TexCreate_ShaderResource | TexCreate_UAV, false));
UserRenderTargetDesc.DebugName = TEXT("GlobalShaderPlugin_UserRenderTarget");
// 如果旧元素依然有效则返回true, 分配了新元素则返回false
GRenderTargetPool.FindFreeElement(RHICmdList, UserRenderTargetDesc, UserRenderTarget, TEXT("GlobalShaderPlugin_UserRenderTarget"));
}
FSimpleColor::DrawToRenderTarget_RenderThread(RHICmdList, DrawParameters, UserRenderTarget->GetRenderTargetItem().TargetableTexture);
}
SimpleColor.h
声明一个简单着色器的绘制函数1
2
3
4
5
6
7
8
9
10
class FSimpleColor
{
public:
static void DrawToRenderTarget_RenderThread(FRHICommandListImmediate& RHICmdList, const FShaderUsageParameters& DrawParameters, FTextureRHIRef UserRenderTarget);
};
SimpleColor.cpp
此处会创建两个Global Shader,分别是顶点着色器和像素着色器。并且通过IMPLEMENT_GLOBAL_SHADER()进行绑定。
1 |
|
PixelShader.usf
着色器的声明模块基本结束。需要创建与之对应的着色器文件.usf放在Shaders文件夹。1
2
3
4
5
6
7
8
9
10
11
12
13
14// VERTEX SHADER
void MainVertexShader(float4 InPosition : ATTRIBUTE0, float2 InUV : ATTRIBUTE1, out float2 OutUV : TEXCOORD0, out float4 OutPosition : SV_POSITION)
{
OutPosition = InPosition;
OutUV = InUV;
}
// PIXEL SHADER
float4 InputColor;
void MainPixelShader(in float2 uv : TEXCOORD0, out float4 OutColor : SV_Target0)
{
OutColor = InputColor;
}
着色器应用模块
创建模块
首先修改.Build.cs文件,添加依赖的模块,如上面创建的ShaderDeclaration1
2
3
4
5
6
7
8
9
10
11
12
13using UnrealBuildTool;
public class ShaderUsage : ModuleRules
{
public ShaderUsage(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{ "Core", "CoreUObject", "Engine", "RHI", "ShaderDeclaration" });
}
}
ShaderUsage.h
基本的模块功能。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FShaderUsageModule : public IModuleInterface
{
public:
static inline FShaderUsageModule& Get()
{
return FModuleManager::LoadModuleChecked<FShaderUsageModule>("ShaderUsage");
}
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("ShaderUsage");
}
};
ShaderUsage.cpp
1 |
|
TestActor.h
创建一个测试Actor类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
UCLASS()
class ATestActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GlobalShader)
FColor InputColor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GlobalShader)
class UTextureRenderTarget2D* UserRenderTarget;
public:
virtual void BeginPlay() override;
virtual void BeginDestroy() override;
virtual void Tick(float DeltaTime) override;
};
TestActor.cpp
1 |
|
目前应用模块也已经完成,编译完成后,在引擎中测试效果。
测试结果
首先以TestActor为父类创建蓝图类。然后创建一个RT,和一个使用该RT的材质。蓝图类中添加一个Box,并使用该测试材质。EventTick每帧传入随机的颜色,Play一下便可以看到颜色不断变化的Box了。