找回密码
 立即注册
查看: 522|回复: 0

修改虚幻引擎后处理管线,自定义Bloom

[复制链接]
发表于 2023-3-10 17:53 | 显示全部楼层 |阅读模式
泛光是常见后处理效果,这次的修改主要是两个目标
1.UE原版后处理Bloom效果的输出集中在阈值颜色区域,无法在不改变物体本身光照颜色的情况下去随意泛出更强的光晕
2.UE原版的Bloom无论在PC还是手机(只看了UE5.1),都有一个Setup阈值pass,和若干不同分辨率的高斯blur(每个blur,xy各一个pass),新的Bloom替换效率更高,不含阈值和高斯blur的pass,且可以通过自定义控制台修改downsample等pass的数量来适配不同的平台。
由于项目本身是二次元游戏,相比能量集中在阈值以上,更希望到处都泛一点。所以新的方法直接干掉bloomsetup也是合理的。就算不干掉我也觉得可以把他合并到其他pass里。
<hr/>前置知识:
UE和Unity两个引擎对Bloom的实现方法:从RenderDoc看UE 5.1和Unity URP后处理区别 - 知乎 (zhihu.com)
用代理委托把渲染主题和shader代码分离到插件:虚幻UE通过插件修改引擎自定义PostProcess - 知乎 (zhihu.com)
这次的Bloom方法来源:Custom Bloom Post-Process in Unreal Engine | Froyok - Léna Piquet
<hr/>首先看UE的PPT




和我之前的文章一样,是downsample然后分别guassianblur然后权重加起来
我们看看其他的做法:
使命召唤高级战争里提供了一个自定义13sample的降采样的filter




同时还用了一个3x3的filter去升采样。同时每次升采样会对结果进行高斯模糊。
但是实际上,升采样的同时就会把高亮的颜色浸染到其他像素上,其实本质上升采样和高斯模糊做了同一件事情。
这次这位歪果仁提供的方法相当于各种方法的结合(本来bloom就这几种操作)用COD的降采样filter,然后再用他的升采样filter,同时每次升采样的结果是上一次升采样结果和这次结果的lerp(这个lerp的方法和unity的方法类似,提供了一个radius值去控制泛光的光圈大小)


注意:这个图里还提到了Glare,Ghosts+halo。皆为Lensflare镜头光晕的部分,由于镜头光晕共享了downsample的结果,所以如果我们依旧需要镜头光晕的效果,我们也需要对其进行适配。(但这个留在以后再说,事实上如果要求不高可以砍一些ue的lensflare功能,退化成一张片来优化效率)

我们先来看效果,新Bloom下:
Radius = 0.2的结果


Radius = 0.6的结果


Radius = 0.9的结果



UE5在新自定义Bloom不同Radius
https://www.zhihu.com/video/1617175263149531136
原本的Bloom之中,还有对不同阶段的自定义Hint。我们也可以对应加上偏色的功能。
同时Renderdoc里


需要注意一点,降采样应该是8次,但是由于Scenecolor提供的已经是1/2分辨率的结果,所以这里只有7次
这里已经完全没有原来的bloom和lensflare了。只有简简单单的downsample和upsample。
<hr/>实现要点:
由于我们用了代理委托的方法,我们需要在原本的Postprocess里找出逻辑替换


我们找到这行,把里面逻辑做ifelse,完全替换成我们的委托函数

除此之外由于我们完全抛弃了Setuppass,所以我们的结果是通过原本的linearcolor和linearcolorwithbloom来进行lerp
LinearColor += CombinedBloom.rgb * (1.0 + BloomDirtMaskColor);原本的加法逻辑替换成lerp
float3 BloomMask = saturate( CustomBloomScale + BloomDirtMaskColor );
LinearColor = lerp( LinearColor, CombinedBloom, BloomMask );这里我们需要cpp里传入CustomBloomScale的shader变量。这里我用了bloomIntensity,因为这样可以让其适配不同PostProcessVolume的切换逻辑
由于用控制台来调试新bloom的效果和质量(radius和passCount)需要新建控制台变量
TAutoConsoleVariable<int32> CVarBloomPassAmount(
    TEXT("r.CustomPP.BloomPassAmount"),
    8,
    TEXT(" Number of passes to render bloom"),
    ECVF_RenderThreadSafe);

TAutoConsoleVariable<float> CVarBloomRadius(
    TEXT("r.CustomPP.BloomRadius"),
    0.85,
    TEXT(" Size/Scale of the Bloom"),
    ECVF_RenderThreadSafe);并在Render函数中控制
int32 PassAmount = CVarBloomPassAmount.GetValueOnRenderThread();
需要注意,由于我们看的时候肯定希望editor也显示正常,所以需要处理editor的逻辑
需要一个rescale的shader,很简单。
void RescalePS(
    in noperspective float4 UVAndScreenPos : TEXCOORD0,
    out float4 OutColor : SV_Target0 )
{
    float2 UV = UVAndScreenPos.xy * InputViewportSize;
    OutColor.rgb = Texture2DSample( InputTexture, InputSampler, UV ).rgb;
    OutColor.a = 0;
}
除此之外,这里用了VS和PS的方式去Downsample,每个Pass都是画正方形,所以可以共享一个单独的VS
#include "Shared.ush"

void CustomScreenPassVS(
    in float4 InPosition : ATTRIBUTE0,
    in float2 InTexCoord : ATTRIBUTE1,
    out noperspective float4 OutUVAndScreenPos : TEXCOORD0,
    out float4 OutPosition : SV_POSITION )
{
    DrawRectangle(InPosition, InTexCoord, OutPosition, OutUVAndScreenPos);
}
Downsample
float2 InputSize;

float3 Downsample( Texture2D Texture, SamplerState Sampler, float2 UV, float2 PixelSize )
{
    const float2 Coords[13] = {
        float2( -1.0f,  1.0f ), float2(  1.0f,  1.0f ),
        float2( -1.0f, -1.0f ), float2(  1.0f, -1.0f ),

        float2(-2.0f, 2.0f), float2( 0.0f, 2.0f), float2( 2.0f, 2.0f),
        float2(-2.0f, 0.0f), float2( 0.0f, 0.0f), float2( 2.0f, 0.0f),
        float2(-2.0f,-2.0f), float2( 0.0f,-2.0f), float2( 2.0f,-2.0f)
    };


    const float Weights[13] = {
        // 4 samples
        // (1 / 4) * 0.5f = 0.125f
        0.125f, 0.125f,
        0.125f, 0.125f,

        // 9 samples
        // (1 / 9) * 0.5f
        0.0555555f, 0.0555555f, 0.0555555f,
        0.0555555f, 0.0555555f, 0.0555555f,
        0.0555555f, 0.0555555f, 0.0555555f
    };

    float3 OutColor = float3( 0.0f, 0.0f ,0.0f );

    UNROLL
    for( int i = 0; i < 13; i++ )
    {
        float2 CurrentUV = UV + Coords * PixelSize;
        OutColor += Weights * Texture2DSample(Texture, Sampler, CurrentUV ).rgb;
    }

    return OutColor;
}

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 );
}Upsample
Texture2D PreviousTexture;
float Radius;

float3 Upsample( Texture2D Texture, SamplerState Sampler, float2 UV, float2 PixelSize )
{
    const float2 Coords[9] = {
        float2( -1.0f,  1.0f ), float2(  0.0f,  1.0f ), float2(  1.0f,  1.0f ),
        float2( -1.0f,  0.0f ), float2(  0.0f,  0.0f ), float2(  1.0f,  0.0f ),
        float2( -1.0f, -1.0f ), float2(  0.0f, -1.0f ), float2(  1.0f, -1.0f )
    };

    const float Weights[9] = {
        0.0625f, 0.125f, 0.0625f,
        0.125f,  0.25f,  0.125f,
        0.0625f, 0.125f, 0.0625f
    };

    float3 Color = float3( 0.0f, 0.0f, 0.0f );

    UNROLL
    for( int i = 0; i < 9; i++ )
    {
        float2 CurrentUV = UV + Coords * PixelSize;
        Color += Weights * Texture2DSampleLevel(Texture, Sampler, CurrentUV, 0).rgb;
    }

    return Color;
}

void UpsampleCombinePS(
    in noperspective float4 UVAndScreenPos : TEXCOORD0,
    out float3 OutColor : SV_Target0 )
{
    float2 InPixelSize = 1.0f / InputSize;
    float2 UV = UVAndScreenPos.xy;

    float3 CurrentColor = Texture2DSampleLevel( InputTexture, InputSampler, UV, 0).rgb;
    float3 PreviousColor = Upsample( PreviousTexture, InputSampler, UV, InPixelSize );

    OutColor.rgb = lerp(CurrentColor, PreviousColor, Radius);
}<hr/>到这里就结束了,总之这次的实现其实对于算法意义不是很大。因为有大佬支持ue4 mobile以前就用了这个方法。我做这件事的意义可能最大的是锻炼自己TA在UE4里的工程实现能力吧。
这次由于用了代理和控制台变量的方式,把对UE引擎本身的修改减少到了最低。大大减少debug渲染代码的时间,每次修改之后只需要10s左右编译时间(5950x,64G,Incredibuild加持),不过还是有很多蛋疼的。(比如传参法线传到mobile了,又编引擎又编工程)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 16:20 , Processed in 0.123863 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表