说明

本例为参考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,启用屏幕后处理描边效果
result

问题记录

在RenderCore模块中添加MyGlobalShader,在使用LAYOUT_FIELD和IMPLEMENT_SHADER_TYPE都出现了报错

error1
error2

最后重新generate一下solution得以解决。
因为UE在编译之前需要UBT UHT进行反射解析。

Reference

Unreal Engine Documentation: Adding Global Shaders to Unreal Engine

【UE4源代码观察】观察官方的GlobalShader范例

(UE4 4.27)自定义GlobalShader

剖析虚幻渲染体系(08)- Shader体系