# Scene View Extension

在 UE4.17 的时候官方开放了可以在后期插入自己 pass 的接口,那么我们就可以写自己的 shader,甚至可以写 compute shader 去做后期处理。还可以做多 pass。而且使用方式也很简单

我们只需要继承 FSceneViewExtensionBase

1
2
3
4
5
6
7
8
9
10
11
12
13
class GAMES202_API FDualKawaseBlur : public FSceneViewExtensionBase
{
public:
FDualKawaseBlur(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister)
{
}

virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override {};
virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override {};
virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override {};
//输出贴图
virtual void SubscribeToPostProcessingPass(EPostProcessingPass Pass, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled) override;
};

并且重载 3 个函数,这个类里面还有很多虚函数可以重写,可以在各个地方执行逻辑
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

virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) = 0;//

//在游戏线程创建view时候调用
virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) = 0;

//在创建View时调用,在剔除之前
virtual void SetupViewPoint(APlayerController* Player, FMinimalViewInfo& InViewInfo) {}

//设置Mvp矩阵时候调用
virtual void SetupViewProjectionMatrix(FSceneViewProjectionData& InOutProjectionData) {}

//当view family即将渲染在游戏线程调用
virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) = 0;

//开始渲染view family前调用
virtual void PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily) {}

//在渲染线程开始渲染时候调用,对每个视图,在PreRenderViewFamily_RenderThread后调用
virtual void PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView) {}

//视图初始化时候调用
virtual void PreInitViews_RenderThread(FRDGBuilder& GraphBuilder) {}

//使用延迟渲染在basePass完成的时候调用
virtual void PostRenderBasePassDeferred_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView, const FRenderTargetBindingSlots& RenderTargets, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTextures) {}

//使用移动端渲染的basepass完成时候调用
virtual void PostRenderBasePassMobile_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) {}

//后期盒子之前调用
virtual void PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessingInputs& Inputs) {};

具体在 SceneViewExtension.h

对于在后期插入 pass 输出贴图我们可以在 SubscribeToPostProcessingPass 里面注册 Delegate,这是个回调函数,ue5 会在 SSRInput、MotionBlur、Tonemap、FXAA、VisualizeDepthOfField 这 5 个 post process pass 中有个 lambda 函数去调用这 Delegate 并输出我们的 pass,而 ue4 则没有 SSRInput 和不支持 mobile 插入 pass

在创建好自己的 SceneViewExtension 类后我可以在新建个蓝图 actor 类,在 actor 中新建个函数,直接调用

FSceneViewExtensions::NewExtension,他会自动帮你在引擎注册视图,这样我们就可以启动我们的 pass 了

1
2
3
4
5
6
7
8
9
10
11
12
void ASetupDualKawaseBlur::DrawPass()
{
if(DualKawaseBlur)
{
DualKawaseBlur.Reset();
DualKawaseBlur = FSceneViewExtensions::NewExtension<FDualKawaseBlur>(InOffset,InPassNum,UseGaussianPass);
}
else
{
DualKawaseBlur = FSceneViewExtensions::NewExtension<FDualKawaseBlur>(InOffset,InPassNum,UseGaussianPass);
}
}

# DualKawaseBlur

DualKawaseBlur 的原理也很简单,他是衍生自 Kawase
Blur 的模糊算法,而 Kawase Blur 就是用来做 bloom,那 DualKawaseBlur 的原理就是在降分辨率的同时采样一个像素的四个角做卷积(通俗点就是做平均),做完几张降分辨率的 pass 后,再做同样 pass
的升分辨率的操作并上下左右像素和四个角做卷积


这就是 Dual Kawase Blur 的过程

方式还是很简单的

在性能上表现最佳,效果也是比较接近高斯模糊,相比之下,高斯模糊模糊的值越大,消耗也是成倍增加


在用 compute shader 的情况下,3070ti,差不多同样的效果,高斯模糊竟然达到了 20ms,而 dual blur 只有 3 个 pass 才 0.2ms

# 源码

最后是代码

DualKawaseBlur.h
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
#include "CoreMinimal.h"
#include "SceneViewExtension.h"
#include "ScreenPass.h"
#include "ShaderParameterStruct.h"
//#include "DualKawaseBlur.generated.h"

/**
*
*/

class GAMES202_API FDualKawaseBlur : public FSceneViewExtensionBase
{
public:
FDualKawaseBlur(const FAutoRegister& AutoRegister,float InOffset,int InPassNum,bool InUseGaussian)
: FSceneViewExtensionBase(AutoRegister),
Offset(InOffset),
PassNum(InPassNum)
{
}

virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override {};
virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override {};
virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override {};//
//注册函数输出贴图
virtual void SubscribeToPostProcessingPass(EPostProcessingPass Pass, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled) override;//
//逻辑编写
FScreenPassTexture PostProcessPassAfterTonemap_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& InOutInputs);

float Offset;

int PassNum = 1;

};

DualKawaseBlur.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Fill out your copyright notice in the Description page of Project Settings.


#include "DualKawaseBlur.h"
#include "PixelShaderUtils.h"
#include "RenderGraphEvent.h"
#include "ScreenPass.h"
#include "PostProcess/PostProcessing.h"
#include "PostProcess/PostProcessMaterial.h"

//用于提交的降Pass的Compute shader类
class FDualKawaseBlurDCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FDualKawaseBlurDCS);
SHADER_USE_PARAMETER_STRUCT(FDualKawaseBlurDCS, FGlobalShader);

static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}

//定义环境,可以定义#if和#Define数值
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters,
FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("NUMTHREADS_X"), 32);
OutEnvironment.SetDefine(TEXT("NUMTHREADS_Y"), 32);
}

BEGIN_SHADER_PARAMETER_STRUCT(FParameters,)
SHADER_PARAMETER(FVector4f, ViewSize)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_SAMPLER(SamplerState, Sampler)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, Intexture)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, OutTexture)

END_SHADER_PARAMETER_STRUCT()
};
//用于提交的升Pass的Compute shader类
class FDualKawaseBlurUCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FDualKawaseBlurUCS);
//声明传参用的宏
SHADER_USE_PARAMETER_STRUCT(FDualKawaseBlurUCS, FGlobalShader);
//应该编译的环境
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
//环境定义,可以定义#if 和#define
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters,
FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("NUMTHREADS_X"), 32);
OutEnvironment.SetDefine(TEXT("NUMTHREADS_Y"), 32);
}
//定义传参用的宏
BEGIN_SHADER_PARAMETER_STRUCT(FParameters,)
SHADER_PARAMETER(FVector4f, ViewSize)
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, Intexture)
SHADER_PARAMETER_SAMPLER(SamplerState, Sampler)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, OutTexture)

END_SHADER_PARAMETER_STRUCT()
};

//compute shader类所对应usf那些函数
IMPLEMENT_GLOBAL_SHADER(FDualKawaseBlurDCS, "/ProjectS/DualKawaseBlur.usf", "DownSampleCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FDualKawaseBlurUCS, "/ProjectS/DualKawaseBlur.usf", "UpSampleCS", SF_Compute);


void FDualKawaseBlur::SubscribeToPostProcessingPass(EPostProcessingPass Pass, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled)
{
//对应pass枚举
if (Pass == EPostProcessingPass::MotionBlur)
{
InOutPassCallbacks.Add(FAfterPassCallbackDelegate::CreateRaw(this, &FDualKawaseBlur::PostProcessPassAfterTonemap_RenderThread));
}
}

FScreenPassTexture FDualKawaseBlur::PostProcessPassAfterTonemap_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& InOutInputs)
{

const FScreenPassTexture SceneColor = InOutInputs.Textures[(uint32)EPostProcessMaterialInput::SceneColor];
const FViewInfo& ViewInfo = static_cast<const FViewInfo&>(View);
FRDGTextureDesc TextureDesc = SceneColor.Texture->Desc;
FRDGTextureRef TempTexture = SceneColor.Texture;
TextureDesc.Flags = ETextureCreateFlags::UAV;
FScreenPassTexture OutPass;

RDG_EVENT_SCOPE(GraphBuilder, "DualKawaseBlurCompute");
for (int i = 0; i < PassNum; ++i)
{
FScreenPassRenderTarget OutSceneRenderTarget(GraphBuilder.CreateTexture(TextureDesc, TEXT("DluaKawaseDownPass")), ERenderTargetLoadAction::ELoad);

//创建UAV
FRDGTextureUAVRef Outexture = GraphBuilder.CreateUAV(OutSceneRenderTarget.Texture);
FDualKawaseBlurDCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FDualKawaseBlurDCS::FParameters>();
PassParameters->Intexture = TempTexture;
PassParameters->ViewSize = FVector4f(TextureDesc.Extent.X, TextureDesc.Extent.Y, Offset, 0);
PassParameters->View = View.ViewUniformBuffer;
PassParameters->Sampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->OutTexture = Outexture;
//初始化着色器
TShaderMapRef<FDualKawaseBlurDCS> ComputeShader(ViewInfo.ShaderMap);
//提交pass
FComputeShaderUtils::AddPass(GraphBuilder,
RDG_EVENT_NAME("DualKawaseBlurDownSample %dx%d (CS)", Outexture->Desc.Texture->Desc.Extent.X, Outexture->Desc.Texture->Desc.Extent.Y),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(TextureDesc.Extent, FIntPoint(32, 32)));
//降分辨率
TextureDesc.Extent.X = TextureDesc.Extent.X / 2;
TextureDesc.Extent.Y = TextureDesc.Extent.Y / 2;
TempTexture = Outexture->Desc.Texture;
}
for (int i = 0; i < PassNum; ++i)
{
TextureDesc.Extent.X = TextureDesc.Extent.X * 2;
TextureDesc.Extent.Y = TextureDesc.Extent.Y * 2;
FScreenPassRenderTarget OutSceneRenderTarget(GraphBuilder.CreateTexture(TextureDesc, TEXT("DluaKawaseUpPass")), ERenderTargetLoadAction::ELoad);

FRDGTextureUAVRef Outexture = GraphBuilder.CreateUAV(OutSceneRenderTarget.Texture);
FDualKawaseBlurUCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FDualKawaseBlurUCS::FParameters>();
PassParameters->Intexture = TempTexture;
PassParameters->ViewSize = FVector4f(TextureDesc.Extent.X, TextureDesc.Extent.Y, Offset, 0);
PassParameters->View = View.ViewUniformBuffer;
PassParameters->Sampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->OutTexture = Outexture;

TShaderMapRef<FDualKawaseBlurUCS> ComputeShader(ViewInfo.ShaderMap);
FComputeShaderUtils::AddPass(GraphBuilder,
RDG_EVENT_NAME("DualKawaseBlurUpSample %dx%d (CS)", Outexture->Desc.Texture->Desc.Extent.X, Outexture->Desc.Texture->Desc.Extent.Y),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(TextureDesc.Extent, FIntPoint(32, 32)));
TempTexture = Outexture->Desc.Texture
OutPass = OutSceneRenderTarget;
}

if (OutPass.IsValid())
{
return MoveTemp(OutPass);
}
else
{
return SceneColor;
}

}

最后新建个 actor 类编写一个函数,然后在蓝图的构建函数里面调用就可以了

actor
1
2
3
4
5
6
7
8
9
10
11
12
void ASetupDualKawaseBlur::DrawPass()
{
if(DualKawaseBlur)
{
DualKawaseBlur.Reset();
DualKawaseBlur = FSceneViewExtensions::NewExtension<FDualKawaseBlur>(InOffset,InPassNum,UseGaussianPass);
}
else
{
DualKawaseBlur = FSceneViewExtensions::NewExtension<FDualKawaseBlur>(InOffset,InPassNum,UseGaussianPass);
}
}

更新于

请我喝[茶]~( ̄▽ ̄)~*

Natsuneko 微信支付

微信支付

Natsuneko 支付宝

支付宝

Natsuneko 贝宝

贝宝