虚幻UE通过插件修改引擎自定义PostProcess
原文来自这个大佬的博客Custom Lens-Flare Post-Process in Unreal Engine | Froyok - Léna Piquet
我所做的主要是把方法提炼出来
之前做过修改虚幻Tonemap之类的后处理操作,每增加参数都要跟着改一遍scene.h等会涉及到大量依赖的类,导致编译时间足够的让我摸完一部柯南剧场版的鱼。并且我只是个TA,还是希望做的东西能以效果为主导,而非引擎上花费大量时间(实现原理我很感兴趣,但是代码不是很想研究)
这个人的分享是一个引擎添加钩子,主题代码在plugins插件的方式完成的,数据的调用采用了控制台命令行和DataAsset蓝图控制的方式,这样可以减少对Scene类的修改,以后更新引擎也大概率可能不用改我们自己的东西。
而且据其他大佬的发现,虚幻5已经给了一些类似unity的before transparency之类的pass钩子。估计虚幻也希望我们以这种方式修改,因为这能最小化对引擎源码的修改。(主要我cpp不行,目前那么多关于Game线程到渲染宣称的数据传输代码实在看不进去)
注意:本文并非一一对应翻译,而是一篇对链接文章的总结笔记,希望能把他修改的内容提炼出来作为参照,有不足建议指出欢迎指出。当然有英文阅读能力还是阅读原文比较好,毕竟我写的就算二道贩子了hh。不过需要注意的是,原文作者这个使用方式经过了一次迭代(在他后面bloom的文章里),所以最好两篇都看。
<hr/>一,修改思路
整体的修改思路是,在引擎的postprocess的过程中增加钩子,通过if else控制是否启用替换我们的自定义渲染。由于所有这些global shader和调用代码在引擎加载的时候就编译完成,所以通过插件的方式让启动的时候加载好(shader并非材质编辑器)
后处理参数设置,采用了控制台变量+DataAsset的方式。这么做的好处是,原本对scene.h的修改的方式不是很好,因为这个类的依赖和引用非常巨大,导致编译时间很长,二则是修改的时候会对应多个初始化,数据传输等地方,当引擎升级时,这些函数很容易受到影响而变化且不容易被发现,例如导致一些初始化变量的bug难以察觉。不过这么做也有相应的坏处,就是控制台变量和dataasset需要我们通过蓝图每个tick去维护,但是如果我们新加入的后处理不一定具有这种根据区域多变的特性,那么我们也不必在意了。主要是这种方式参数不跟scene走,也就无法用到postprocess的体积的参数融合。
我个人支持这种方式的修改,因为我觉得这种方式的维护代价最小,且我希望能把更多的时间投入到效果的研究上,而不是研究代码本身。
二,开工
本人使用UE5.1版本,UE4.26,4.27开始版本大同小异
步骤一: 创建插件plugins
先创建插件,名字随意。这里和原文保持一致叫CustomPostProcess
这个时候你工程里的\Plugins会多出一个名字叫CustomPostProcess(或者其他名字,后面我就先用这个名字了)的文件夹,里面会有一个和你自定义名字相同的uplugin后缀的文件。这就是创建成功了
\Plugins\CustomPostProcess里创建一个Shaders文件夹像这样
这里就是以后我们存放自定义shader文件,即usf和ush的地方。
\Plugins\CustomPostProcess\Source\CustomPostProcess里有个CustomPostProcess.build.cs文件
这是一个C#文件,貌似是插件的编译通过这个C#文件执行
打开这个文件进行修改
PrivateIncludePaths.AddRange(
new string[]
{
// 这里是为了让我们引用到UE引擎的后处理头文件
EngineDirectory + &#34;/Source/Runtime/Renderer/Private&#34;
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
// 增加RenderGraph, PostProcess, Shaders等相关的依赖模块
&#34;Core&#34;,
&#34;RHI&#34;,
&#34;Renderer&#34;,
&#34;RenderCore&#34;,
&#34;Projects&#34;
}
);\Plugins\CustomPostProcess\Source\CustomPostProcess里应该有private,public两个文件夹分别对应CustomPostProcess的头文件和源文件。
CustomPostProcess.h
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;Modules/ModuleManager.h&#34;
class FCustomPostProcessModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};CustomPostProcess.cpp
#include &#34;CustomPostProcess.h&#34;
#include &#34;Interfaces/IPluginManager.h&#34;
#define LOCTEXT_NAMESPACE &#34;FCustomPostProcessModule&#34;
void FCustomPostProcessModule::StartupModule()
{
FString BaseDir = IPluginManager::Get().FindPlugin(TEXT(&#34;CustomPostProcess&#34;))->GetBaseDir();
FString PluginShaderDir = FPaths::Combine( BaseDir, TEXT(&#34;Shaders&#34;) );
AddShaderSourceDirectoryMapping(TEXT(&#34;/CustomShaders&#34;), PluginShaderDir);
}
void FCustomPostProcessModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FCustomPostProcessModule, CustomPostProcess)我们要实现startup函数,在函数里我们要通过FindPlugin找到我们插件的BaseDir,以此来找到我们存放Shader文件的地方,再通过AddShaderSourceDirectoryMapping来让引擎知道我们如何去索引我们的自定义shader。
最后我们要确认我们的.uplugin文件配置正确。打开它确认Modules这个属性是这样配置的
&#34;Modules&#34;: [
{
&#34;Name&#34;: &#34;CustomPostProcess&#34;,
&#34;Type&#34;: &#34;Runtime&#34;,
&#34;LoadingPhase&#34;: &#34;PostConfigInit&#34;
}
]主要应该是修改&#34;LoadingPhase&#34;来让插件在引擎初始化中正确的位置加载。
步骤二: 准备一些Shaders文件
之前我们创建的Shaders文件夹就是为了存我们的自定义Shader,我们把自己的自定义Pass按名字命名好并且创建它(可以先新建txt,然后修改后缀名usf)
例如:
usf文件: -CustomBloomSetup.usf -CustomDownsample.usf [...]
然后我们可以新建一个&#34;Share.ush&#34;文件来方便我们管理对引擎一些基础shader工具和postprocess的依赖。同时在里面命名一些通用的变量。
//这一步,原文是做镜头光晕,其实不知道这个东西是否必要,我们可以根据我们做的需求看是否必要加上
#define SCENE_TEXTURES_DISABLED 1
#include &#34;/Engine/Public/Platform.ush&#34;
#include &#34;/Engine/Private/Common.ush&#34;
#include &#34;/Engine/Private/ScreenPass.ush&#34;
#include &#34;/Engine/Private/PostProcessCommon.ush&#34;
Texture2D InputTexture;
SamplerState InputSampler;
float2 InputViewportSize;第三步: 管理CustomPostProcess的参数数据
通过两种方式,第一种是控制台调试,第二种Data Asset,我们定义新的DataAsset的类然后保存。
我们创建一个类继承DataAsset
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;Engine/DataAsset.h&#34;
#include &#34;CustomPostProcessAsset.generated.h&#34;
UCLASS()
class CUSTOMPOSTPROCESS_API UCustomPostProcessAsset : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category=&#34;General&#34;, meta=(UIMin = &#34;0.0&#34;, UIMax = &#34;10.0&#34;))
float TestPropertyFloat = 1.0f;
UPROPERTY(EditAnywhere, Category=&#34;General&#34;)
FLinearColor Tint = FLinearColor(1.0f, 0.85f, 0.7f, 1.0f);
UPROPERTY(EditAnywhere, Category=&#34;General&#34;)
UTexture2D* CustomTexture = nullptr;
};我在这里每种类型的参数都举了一个例子,省的大家再去找类似的地方copy了。
当然也可以增加Struct来满足自己自定义结构需求, 我这里拿bloom举例
USTRUCT(BlueprintType)
struct FCustomBloomSettings
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=&#34;Exedre&#34;)
FLinearColor Hint = FLinearColor::White;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=&#34;Exedre&#34;)
float BloomIntensity = 1.0f;
};
我们编译项目之后就可以在项目里新建DataAsset,搜索输入我们的class name来建立我们的参数数据。
对这个玩意右键来复制引用路径
原作者强烈建议把DataAsset保存到Plugins文件夹你对应的CustomPostProcess文件夹里,这样你的数据在迁移项目的同时也方便保留。
需要注意的是,我们用了DataAsset和控制台的方式来控制后处理参数变量(控制台后面会说)意味着不会走PostProcess体积的插值流程。需要自己用蓝图管理逻辑,具体关于pp体积插值逻辑的,YivanLee大佬有文章。
虚幻五渲染编程(Graphic篇)【第十一卷:How Postprocessing Volumes Work In UnrealEngine5】 - 知乎 (zhihu.com)
第四步: 修改引擎渲染流程
横竖还是要改引擎的,没办法。感觉或许虚幻根据情况给出更多钩子会让ue的自定义变的和srp一样轻松。
目前虚幻引擎只给我们留了寥寥几个钩子,其中并没有在PostProcess里面创建的钩子。猜测是因为PP的修改在大多数项目情况下还是不需要的。其他勾子的使用YivanLee大佬也有文章
虚幻五渲染编程(Graphic篇)【第十卷:Expand UE5 Render Module In Plugin】 - 知乎 (zhihu.com)
开始之前有一个概念Render Dependency Graph (RDG)也叫RenderGraph,是虚幻一个对渲染管线的controller。我们调用加pass都需要通过这个东西。顺便一提上面这个RDG虚幻的官方文档链接给出了常见参数格式等,可以收藏了看。
首先,我们在我们引擎里找到我们渲染逻辑修改的地方。
例如我这边需要修改整个Bloom的逻辑,那么就找到Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
在一堆include后面添加:
DECLARE_MULTICAST_DELEGATE_FourParams( FPP_MyCustomBloom, FRDGBuilder&, const FViewInfo&, const FScreenPassTexture&, FScreenPassTexture& );
RENDERER_API FPP_MyCustomBloom PP_MyCustomBloom;在我这个例子里有四个参数类型:
FPP_MyCustomBloom相当于我们起的委托名字
FRDGBuilder就是RDG的引用让我来传递渲染逻辑。
FViewInfo是我们当前的窗口设置(相机相关,分辨率,后处理参数等等)
后面两个FScreenPassTexture分别对应我们的input和output,这里用于bloom所以对应这scenecolor和bloom结果
这时候如果你希望添加控制台逻辑来控制你的自定PP开关,找到下面几行namespace
namespace
{
//-----------------------------------
TAutoConsoleVariable<int32> CVarPostProcessingMyCustomBloom(
TEXT(&#34;r.PostProcessing.MyCustomBloom&#34;),
1,
TEXT(&#34;If enabled, use My Custom Bloom rendering&#34;),
ECVF_RenderThreadSafe);
//-----------------------------------这里第一个和第三个参数很好理解,第二个1是默认值,代表默认开启。
同时我们需要修改引擎渲染逻辑,我这里是bloom,所以需要找到这部分代码
if (bBloomEnabled)
{
FSceneDownsampleChain BloomDownsampleChain;
FBloomInputs PassInputs;
PassInputs.SceneColor = SceneColor;
const bool bBloomThresholdEnabled = View.FinalPostProcessSettings.BloomThreshold > -1.0f;
// Reuse the main scene downsample chain if a threshold isn&#39;t required for bloom.
if (SceneDownsampleChain.IsInitialized() && !bBloomThresholdEnabled)
{
[...]
}
else
{
[...]
}
FBloomOutputs PassOutputs = AddBloomPass(GraphBuilder, View, PassInputs);
SceneColor = PassOutputs.SceneColor;
Bloom = PassOutputs.Bloom;
FScreenPassTexture LensFlares = AddLensFlaresPass(GraphBuilder, View, Bloom, *PassInputs.SceneDownsampleChain);
if (LensFlares.IsValid())
{
// Lens flares are composited with bloom.
Bloom = LensFlares;
}
}我们在这里要添加 if else 通过控制台变量来控制是否用我们的bloom渲染逻辑
if (bBloomEnabled)
{
// 我们的ifelse修改
if( !CVarPostProcessingMyCustomBloom.GetValueOnRenderThread() )
{
FSceneDownsampleChain BloomDownsampleChain;
FBloomInputs PassInputs;
PassInputs.SceneColor = SceneColor;
const bool bBloomThresholdEnabled = View.FinalPostProcessSettings.BloomThreshold > -1.0f;
// Reuse the main scene downsample chain if a threshold isn&#39;t required for bloom.
if (SceneDownsampleChain.IsInitialized() && !bBloomThresholdEnabled)
{
[...]
}
else
{
[...]
}
FBloomOutputs PassOutputs = AddBloomPass(GraphBuilder, View, PassInputs);
SceneColor = PassOutputs.SceneColor;
Bloom = PassOutputs.Bloom;
FScreenPassTexture LensFlares = AddLensFlaresPass(
GraphBuilder,
View,
Bloom,
*PassInputs.SceneDownsampleChain
);
if (LensFlares.IsValid())
{
// Lens flares are composited with bloom.
Bloom = LensFlares;
}
}
else
{
// 这里运行我们自己的逻辑
PP_CustomBloomFlare.Broadcast( GraphBuilder, View, HalfResSceneColor, Bloom );
}
}这里注意一点,HalfResSceneColor在UE4里叫HalfResolutionSceneColor,没事喜欢改改名是吧。
如果同样是改bloom的可以注意一点,我这里替换Bloom的逻辑是连镜头光晕LensFlares一起端了,由于我们暂时没有开镜头光晕的地方,准备之后准备再找时间修改。又由于LensFlares的ghost部分依赖bloom阈值结果加降采样操作,所以没办法简单拆开。(Bloom这里就有点麻烦,无论怎么样,你的逻辑一定要给镜头光晕提供阈值后的降采样结果,否则你自己再处理一个给他,当然又或者,你可以直接干掉,改用自定义后处理材质等自己的方式做LensFlares)
同时不要忘记针对引擎内部ush代码的修改,例如bloom的mix部分在Engine/Shaders/Private/PostProcessTonemap.usf
这里就不给出具体操作,因为这次重点还是集中于这种写法本身。
第五步:自定义Subsystem
我们有了钩子,还需要有地方来写我自己的渲染代码。
这里的解决方案是创建一个类继承UEngineSubsystem,因为它随着引擎启动,结束,可以在编辑器任何上下文调用。
我们在\Plugins\H5CustomPostProcess\Source\H5CustomPostProcess\Public
创建PostProcessSubsystem.h
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;PostProcess/PostProcessing.h&#34; // For PostProcess delegate
#include &#34;PostProcessSubsystem.generated.h&#34;
DECLARE_MULTICAST_DELEGATE_FourParams( FPP_MyCustomBloom, FRDGBuilder&, const FViewInfo&, const FScreenPassTexture&, FScreenPassTexture& );
extern RENDERER_API FPP_MyCustomBloom PP_MyCustomBloom
UCLASS()
class CUSTOMPOSTPROCESS_API UPostProcessSubsystem : public UEngineSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
private:
//------------------------------------
// Helpers
//------------------------------------
// Internal blending and sampling states;
FRHIBlendState* ClearBlendState = nullptr;
FRHIBlendState* AdditiveBlendState = nullptr;
FRHISamplerState* BilinearClampSampler = nullptr;
FRHISamplerState* BilinearBorderSampler = nullptr;
FRHISamplerState* BilinearRepeatSampler = nullptr;
FRHISamplerState* NearestRepeatSampler = nullptr;
void InitStates();
//------------------------------------
// Main function
//------------------------------------
void Render(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FScreenPassTexture& SceneColor,
FScreenPassTexture& Output
);
TArray< FScreenPassTexture > MipMapsDownsample;
TArray< FScreenPassTexture > MipMapsUpsample;
FScreenPassTexture RenderBloom(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FScreenPassTexture& SceneColor,
int32 PassAmount
);
FRDGTextureRef RenderDownsample(
FRDGBuilder& GraphBuilder,
const FString& PassName,
const FViewInfo& View,
FRDGTextureRef InputTexture,
const FIntRect& Viewport
);
FRDGTextureRef RenderUpsampleCombine(
FRDGBuilder& GraphBuilder,
const FString& PassName,
const FViewInfo& View,
const FScreenPassTexture& InputTexture,
const FScreenPassTexture& PreviousTexture,
float Radius
);
};这里以Bloom为例,我们在这个类需要override两个函数(加载和卸载),除此之外还要声明所用变量和每个Pass的render函数
PostProcessSubsystem.cpp
#include &#34;PostProcessSubsystem.h&#34;
#include &#34;CustomPostProcessAsset.h&#34;
#include &#34;RenderGraph.h&#34;
#include &#34;ScreenPass.h&#34;
#include &#34;PostProcess/PostProcessing.h&#34;
namespace
{
// TODO_SHADER_PASS1
// TODO_SHADER_PASS2
// TODO_SHADER_PASS3
}
void UPostProcessSubsystem::Initialize( FSubsystemCollectionBase& Collection )
{
Super::Initialize( Collection );
//--------------------------------
// Setup delegate
//--------------------------------
FPP_MyCustomBloom::FDelegate Delegate = FPP_MyCustomBloom::FDelegate::CreateLambda(
[=]( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FScreenPassTexture& SceneColor, FScreenPassTexture& Output )
{
Render( GraphBuilder, View, SceneColor, Output );
});
ENQUEUE_RENDER_COMMAND(BindRenderThreadDelegates)( (FRHICommandListImmediate& RHICmdList )
{
PP_MyCustomBloom.Add(Delegate);
});
//--------------------------------
// Data asset loading
//--------------------------------
FString Path = &#34;CustomPostProcessAsset&#39;/CustomPostProcess/DefaultCustomPP.DefaultCustomPP&#39;&#34;;
CustomPostProcessAsset = LoadObject<UCustomPostProcessAsset>( nullptr, *Path );
check(CustomPostProcessAsset);
}
void UPostProcessSubsystem::Deinitialize()
{
ClearBlendState = nullptr;
AdditiveBlendState = nullptr;
BilinearClampSampler = nullptr;
[...]
}
TAutoConsoleVariable<int32> CVarXXXParam1(
TEXT(&#34;r.CustomPP.Param1&#34;),
1,//default value
TEXT(&#34; Explain for Param1&#34;),
ECVF_RenderThreadSafe);
void UPostProcessSubsystem::Render(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FScreenPassTexture& SceneColor,
FScreenPassTexture& Output
)
{
}这里是我们的渲染代码,我们可以分成几块
namespace里是我们后面需要填充的每个Pass的链接逻辑。
Initialize里我们把委托函数实现(就是调用Render)并且和引擎的钩子连上(这里原文提到,这种方式可能不是线程安全的,但应该怎么做我也不懂,反正本着不出问题不关我事的摆烂想法,先不管), 在最后把我们的DataAsset加载好。
DeIntialize去清一些变量。
然后是CVarXXXParam,自己定义的控制台变量,可以用来控制Render代码的渲染流程。
Render代码里我们根据自己的需求去写,具体怎么样看后面
第六步:填充我们的pass和shader
这里以DownsampleShader举例,在之前的// TODO_SHADER_PASSxxx替换自己的代码
// Bloom downsample
class FDownsamplePS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FDownsamplePS);
SHADER_USE_PARAMETER_STRUCT(FDownsamplePS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FCustomBloomPassParameters, Pass)
SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler)
SHADER_PARAMETER(FVector2D, InputSize)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FDownsamplePS, &#34;/CustomShaders/DownsampleThreshold.usf&#34;, &#34;DownsamplePS&#34;, SF_Pixel);定义传入参数结构
重写一个bool函数,用来检测是否平台支持ShadingModel5
绑定我们的usf文件和里面对应的着色器函数,着色器类型(xxxPS对应SF_Pixel,当然还有VS和CS,GS)
在我们实际的usf里当然就是
#include &#34;Shared.ush&#34;
float2 InputSize;
void DownsamplePS(
in noperspective float4 UVAndScreenPos : TEXCOORD0,
out float3 OutColor : SV_Target0 )
{
float2 InPixelSize = (1.0f / InputSize) * 0.5;
float2 UV = UVAndScreenPos.xy;
OutColor.rgb = Downsample( InputTexture, InputSampler, UV, InPixelSize );
}第七步:写Render函数
回到我们的PostProcessSubsystem.cpp的Render函数,开始填充里面内容
check( SceneColor.IsValid() );
if( PostProcessAsset == nullptr )
{
return;
}我们先检查我们数据是否齐全
//Init States
RDG_GPU_STAT_SCOPE(GraphBuilder, CustomPostProcess)
RDG_EVENT_SCOPE(GraphBuilder, &#34;CustomPostProcess&#34;);
int32 Param1 = CVarXXXParam1.GetValueOnRenderThread();Init States可以自己往里面写一些对blend和samplestate逻辑的设置,可以参考这两个文件
/Engine/Source/Runtime/RenderCore/Private/ClearQuad.cpp
/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.cpp
然后这里定义了一些统计信息方便我们从UE的GPU调试器看到我们的Pass
之后获取了我们设置的控制台变量方便之后控制我们的渲染流程(这里主要是为了可以用控制台看自己的渲染结果,而非打开rdc,毕竟rdc慢呀)
// Buffers setup
const FScreenPassTexture BlackDummy{
GraphBuilder.RegisterExternalTexture(
GSystemTextures.BlackDummy,
TEXT(&#34;BlackDummy&#34;)
)
};
FScreenPassTexture OutputTexture;
FScreenPassTexture InputTexture( SceneColor.Texture );
//Render
OutputTexture = RenderXXX(
GraphBuilder,
View,
InputTexture,
);
FIntRect OutputViewport{
0,
0,
View.ViewRect.Width(),
View.ViewRect.Height()
};
Output.Texture = OutputTexture;
Output.ViewRect = OutputViewport;blackdummy是一张默认黑底图适合初始化。
我们声明输入和输出
RenderXXX为我们渲染的一部下面提供了具体例子
FIntRect类型提供了ViewRect的大小,具体RT的大小我们在RenderXXX的代码里进行了设置。
这里是每个RenderXXX的具体代码例子:
FRDGTextureRef UPostProcessSubsystem::RenderDownsample(
FRDGBuilder& GraphBuilder,
const FString& PassName,
const FViewInfo& View,
FRDGTextureRef InputTexture,
const FIntRect& Viewport
)
{
// Build texture
FRDGTextureDesc Description = InputTexture->Desc;
Description.Reset();
Description.Extent= Viewport.Size();
Description.Format= PF_FloatRGB;
Description.ClearValue = FClearValueBinding(FLinearColor::Black);
FRDGTextureRef TargetTexture = GraphBuilder.CreateTexture(Description, *PassName);
// Render shader
TShaderMapRef<FCustomScreenPassVS> VertexShader(View.ShaderMap);
TShaderMapRef<FDownsamplePS> PixelShader(View.ShaderMap);
FDownsamplePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FDownsamplePS::FParameters>();
PassParameters->Pass.InputTexture = InputTexture;
PassParameters->Pass.RenderTargets = FRenderTargetBinding(TargetTexture, ERenderTargetLoadAction::ENoAction);
PassParameters->InputSampler = BilinearBorderSampler;
PassParameters->InputSize = FVector2D( Viewport.Size() );
DrawShaderPass(
GraphBuilder,
PassName,
PassParameters,
VertexShader,
PixelShader,
ClearBlendState,
Viewport
);
return TargetTexture;
}我们从InputTexture里copy一份description,设置好属性和passname(也就是我们在rdc等调试窗口看到的)
我们拿到vs和ps的shader代码引用。
我们通过graphbuilder申请传输的数据带宽(PS)
后面就是设置我们PS的输入输出
DrawShaderPass就是画了,没什么好说的。
<hr/>到这里应该完整的流程就走完了,如果没有意外,编译引擎和工程,看rdc应该就出现我们的custompass了
那么这篇文章先到这里为止,后面遇到什么需要补充的我会更新在这篇文章里。
这位大佬的文章还详细介绍了镜头光晕和bloom的实现流程,其中bloom的流程我之前也发过一篇和unity的对比,发现果然大家对于bloom这种效果还是偏向非写实的感觉,都喜欢采用通过一个值lerp升采样结果来控制光晕大小的方式(这个大佬的方法和unity的几乎一样,差别只在于升降采样时的filter和对阈值的运用)。这位大佬的研究非常细致,结合现实结合其他游戏的逆向分析非常细致,非常值得一看了。
页:
[1]