ainatipen 发表于 2023-3-8 13:39

虚幻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 + "/Source/Runtime/Renderer/Private"
      }
      );

    PublicDependencyModuleNames.AddRange(
      new string[]
      {
            // 增加RenderGraph, PostProcess, Shaders等相关的依赖模块
            "Core",
            "RHI",
            "Renderer",
            "RenderCore",
            "Projects"
      }
      );\Plugins\CustomPostProcess\Source\CustomPostProcess里应该有private,public两个文件夹分别对应CustomPostProcess的头文件和源文件。
CustomPostProcess.h
#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FCustomPostProcessModule : public IModuleInterface
{
    public:
      virtual void StartupModule() override;
      virtual void ShutdownModule() override;
};CustomPostProcess.cpp
#include "CustomPostProcess.h"
#include "Interfaces/IPluginManager.h"

#define LOCTEXT_NAMESPACE "FCustomPostProcessModule"

void FCustomPostProcessModule::StartupModule()
{
    FString BaseDir = IPluginManager::Get().FindPlugin(TEXT("CustomPostProcess"))->GetBaseDir();
    FString PluginShaderDir = FPaths::Combine( BaseDir, TEXT("Shaders") );
    AddShaderSourceDirectoryMapping(TEXT("/CustomShaders"), PluginShaderDir);
}

void FCustomPostProcessModule::ShutdownModule()
{
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FCustomPostProcessModule, CustomPostProcess)我们要实现startup函数,在函数里我们要通过FindPlugin找到我们插件的BaseDir,以此来找到我们存放Shader文件的地方,再通过AddShaderSourceDirectoryMapping来让引擎知道我们如何去索引我们的自定义shader。
最后我们要确认我们的.uplugin文件配置正确。打开它确认Modules这个属性是这样配置的
    "Modules": [
      {
            "Name": "CustomPostProcess",
            "Type": "Runtime",
            "LoadingPhase": "PostConfigInit"
      }
    ]主要应该是修改"LoadingPhase"来让插件在引擎初始化中正确的位置加载。
步骤二: 准备一些Shaders文件

之前我们创建的Shaders文件夹就是为了存我们的自定义Shader,我们把自己的自定义Pass按名字命名好并且创建它(可以先新建txt,然后修改后缀名usf)
例如:
usf文件: -CustomBloomSetup.usf   -CustomDownsample.usf   [...]
然后我们可以新建一个"Share.ush"文件来方便我们管理对引擎一些基础shader工具和postprocess的依赖。同时在里面命名一些通用的变量。
//这一步,原文是做镜头光晕,其实不知道这个东西是否必要,我们可以根据我们做的需求看是否必要加上
#define SCENE_TEXTURES_DISABLED 1

#include "/Engine/Public/Platform.ush"
#include "/Engine/Private/Common.ush"
#include "/Engine/Private/ScreenPass.ush"
#include "/Engine/Private/PostProcessCommon.ush"

Texture2D InputTexture;
SamplerState InputSampler;
float2 InputViewportSize;第三步: 管理CustomPostProcess的参数数据

通过两种方式,第一种是控制台调试,第二种Data Asset,我们定义新的DataAsset的类然后保存。
我们创建一个类继承DataAsset
#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "CustomPostProcessAsset.generated.h"

UCLASS()
class CUSTOMPOSTPROCESS_API UCustomPostProcessAsset : public UDataAsset
{
    GENERATED_BODY()

    public:
      UPROPERTY(EditAnywhere, Category="General", meta=(UIMin = "0.0", UIMax = "10.0"))
      float TestPropertyFloat = 1.0f;

      UPROPERTY(EditAnywhere, Category="General")
      FLinearColor Tint = FLinearColor(1.0f, 0.85f, 0.7f, 1.0f);

      UPROPERTY(EditAnywhere, Category="General")
      UTexture2D* CustomTexture = nullptr;
};我在这里每种类型的参数都举了一个例子,省的大家再去找类似的地方copy了。
当然也可以增加Struct来满足自己自定义结构需求, 我这里拿bloom举例
USTRUCT(BlueprintType)
struct FCustomBloomSettings
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Exedre")
    FLinearColor Hint = FLinearColor::White;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Exedre")
    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("r.PostProcessing.MyCustomBloom"),
    1,
    TEXT("If enabled, use My Custom Bloom rendering"),
    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'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'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 "CoreMinimal.h"
#include "PostProcess/PostProcessing.h" // For PostProcess delegate
#include "PostProcessSubsystem.generated.h"

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 "PostProcessSubsystem.h"
#include "CustomPostProcessAsset.h"

#include "RenderGraph.h"
#include "ScreenPass.h"
#include "PostProcess/PostProcessing.h"

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 = "CustomPostProcessAsset'/CustomPostProcess/DefaultCustomPP.DefaultCustomPP'";

    CustomPostProcessAsset = LoadObject<UCustomPostProcessAsset>( nullptr, *Path );
    check(CustomPostProcessAsset);
}

void UPostProcessSubsystem::Deinitialize()
{
    ClearBlendState = nullptr;
    AdditiveBlendState = nullptr;
    BilinearClampSampler = nullptr;
    [...]
}

TAutoConsoleVariable<int32> CVarXXXParam1(
    TEXT("r.CustomPP.Param1"),
    1,//default value
    TEXT(" Explain for Param1"),
    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, "/CustomShaders/DownsampleThreshold.usf", "DownsamplePS", SF_Pixel);定义传入参数结构
重写一个bool函数,用来检测是否平台支持ShadingModel5
绑定我们的usf文件和里面对应的着色器函数,着色器类型(xxxPS对应SF_Pixel,当然还有VS和CS,GS)
在我们实际的usf里当然就是
#include "Shared.ush"

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, "CustomPostProcess");

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("BlackDummy")
      )
    };

    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]
查看完整版本: 虚幻UE通过插件修改引擎自定义PostProcess