说明
本例为参考UE官方案例及各个博客文章后,添加GlobalShader实现屏幕后处理边缘提取的实践记录。
添加新着色器
新建Shader源文件,MyGlobalShader.usf
// Engine\Shaders\Private\MyGlobalShader.usf
#include "Common.ush"
Texture2D SceneColorTexture;
SamplerState SceneColorSampler;
void MainPS(
noperspective float4 UVAndScenePos : TEXCOORD0,
float4 SvPostion : SV_POSITION,
out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScenePos.xy;
float3 FinalColor = float3(0.0, 0.0, 0.0);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float2 SampleUV = UV + (float2(x, y) * View.BufferSizeAndInvSize.zw);
float3 SampleColor = Texture2DSample(SceneColorTexture, SceneColorSampler, SampleUV).rgb;
if(x == 0 && y == 0)
{
FinalColor += (4 * SampleColor);
}
else if(x * y == 0)
{
FinalColor += (-1 * SampleColor);
}
}
FinalColor = dot(FinalColor, float3(0.299, 0.587, 0.114));
OutColor = float4(FinalColor, 1.0);
}
}
声明Shader类
新建GlobalShader类声明,使得UE能够识别并开始编译着色器
// Engine\Source\Runtime\RenderCore\Public\MyGlobalShader.h
#pragma once
#include "GlobalShader.h"
#include "ShaderParameterStruct.h"
class FMyGlobalShaderPS: public FGlobalShader
{
DECLARE_EXPORTED_SHADER_TYPE(FMyGlobalShaderPS, Global, RENDERCORE_API);
public:
SHADER_USE_PARAMETER_STRUCT(FMyGlobalShaderPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters,)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameter, View)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorSampler)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FShaderPermutationParameters& Parameters)
{
return true;
}
};
声明GlobalShader需要遵循以下要求:
- 必须是FGlobalShader的子类。这样该着色器最终才会出现在GlobalShaderMap中,这意味着无需材质即可找到该着色器。
- 需要使用DECLARE_EXPORTED_SHADER_TYPE()宏,它会生成着色器类型在序列化时所需的导出内容。第三个参数是着色器模块所在代码模块的外部链接类型(如需要)。例如,任何不存在于渲染器模块中的C++代码。
- 有两个构造函数:默认构造函数和序列化构造函数。
- 需要使用ShouldCompilePermutation()函数,以便确定在某些情况下是否应编译此着色器。
注册ShaderType
// Engine\Source\Runtime\RenderCore\Private\MyGlobalShader.cpp
#include "MyGlobalShader.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
#include "RHIStaticStates.h"
#include "Runtime/Renderer/Private/SceneRendering.h"
#include "ScreenPass.h"
IMPLEMENT_SHADER_TYPE(, FMyGlobalShaderPS, TEXT("/Engine/Private/MyGlobalShader.usf"), TEXT("MainPS"), SF_Pixel);
效果测试
测试函数
// Engine\Source\Runtime\RenderCore\Private\MyGlobalShader.cpp
void MyGlobalShaderTest(FRDGBuilder& GraphBuilder, const FViewInfo& View, FRDGTextureRef InputColorTexture, FRDGTextureRef ViewFamilyTexture)
{
// Copy Texture
FRDGTextureRef CopiedViewFamilyTexture = GraphBuilder.CreateTexture(ViewFamilyTexture->Desc, TEXT("CopiedViewFamilyTexture"));
AddCopyTexturePass(GraphBuilder, ViewFamilyTexture, CopiedViewFamilyTexture);
// Prepare Parameters
TShaderMapRef<FMyGlobalShaderPS> MyGlobalPS(View.ShaderMap);
FMyGlobalShaderPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FMyGlobalShaderPS::FParameters>();
PassParameters->View = View.ViewUniformBuffer;
PassParameters->SceneColorSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->SceneColorTexture = CopiedViewFamilyTexture;
PassParameters->RenderTargets[0] = FRenderTargetBinding(ViewFamilyTexture, ERenderTargetLoadAction::ENoAction);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("MyGlobalShaderTestPass"), View,
FScreenPassTextureViewport(ViewFamilyTexture),
FScreenPassTextureViewport(ViewFamilyTexture),
MyGlobalPS, PassParameters);
}
// Engine\Source\Runtime\Renderer\Private\DeferredShadingRenderer.cpp
(...)
/************** My Global Shader Test **********/
static TAutoConsoleVariable<int32> CVarMyGlobalShaderTest(
TEXT("r.MyTest"),
0,
TEXT("Test My Global Shader, set it to 0 to disable, or to 1, 2 or 3 for fun!"),
ECVF_RenderThreadSafe);
/***********************************************/
(...)
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
(...)
if (RendererOutput == ERendererOutput::DepthPrepassOnly)
{
(...)
}
else
{
(...)
// Finish rendering for each view.
if (ViewFamily.bResolveScene && ViewFamilyTexture)
{
(...)
}
int32 MyTestValue = CVarMyGlobalShaderTest.GetValueOnAnyThread();
if(MyTestValue != 0 && ViewFamily.bResolveScene && ViewFamilyTexture)
{
for(int32 i = 0; i < Views.Num(); ++i)
{
const FViewInfo& View = Views[i];
MyGlobalShaderTest(GraphBuilder, View, nullptr, ViewFamilyTexture);
}
}
}
(...)
}
效果
使用控制台命令r.MyTest 1,启用屏幕后处理描边效果
问题记录
在RenderCore模块中添加MyGlobalShader,在使用LAYOUT_FIELD和IMPLEMENT_SHADER_TYPE都出现了报错
最后重新generate一下solution得以解决。
因为UE在编译之前需要UBT UHT进行反射解析。
Reference
Unreal Engine Documentation: Adding Global Shaders to Unreal Engine