URP后处理的Kawase模糊实现

一般说起模糊,大体都会想到卷积以及高斯模糊进行处理。

模糊的核心思想在于对图像高频信号的滤波(反向滤波的话可以用作边缘检测),由于涉及到傅里叶快速展开,如有兴趣可以去参考一些关于图像频域转换的知识,这里就不做过多展开。

但是由于高斯模糊需要在二维图像上对两个一维进行分别计算,消耗两个PASS,开销还是有一定大。一般对模糊品质没有特别需求的话,常规会使用Kawase(川瀬)模糊进行替换。

川瀬 Blur于Masaki Kawase 在GDC2003的分享《Frame Buffer Postprocessing Effects in DOUBLE-S.T.E.A.L (Wreckless)》中提出。

Kawase模糊是一种后处理效果,最初用于Bloom特效,但后来被推广为一种专门的模糊算法。它在模糊外观上与高斯模糊非常接近。由于采用了随迭代次数移动的blur kernel,而不是像高斯模糊或box blur一样从头到尾固定的blur kernel。所以相对来说实际的开销会更小一些。

Kawase模糊的思路如下:

  • 对距离当前像素越来越远的地方的四个角进行采样。
  • 在两个大小相等的纹理之间进行乒乓式的blit(即交替复制)。

川瀬のブルームフィルター - Wikipedia

备注:SIGGRAPH 2015上ARM团队提出的一种衍生自Kawase Blur的模糊算法Dual Kawase Blur,优化了性能表现。

具体思路是在C#处,基于当前迭代次数,对每次模糊的半径进行设置,并在Shader内实现一个Filter进行处理,如下

fixed4 frag (v2f input) : SV_Target
{
    float2 res = _MainTex_TexelSize.xy;
    float i = _offset;

    fixed4 col;                
    col.rgb = tex2D( _MainTex, input.uv ).rgb;
    col.rgb += tex2D( _MainTex, input.uv + float2( i, i ) * res ).rgb;
    col.rgb += tex2D( _MainTex, input.uv + float2( i, -i ) * res ).rgb;
    col.rgb += tex2D( _MainTex, input.uv + float2( -i, i ) * res ).rgb;
    col.rgb += tex2D( _MainTex, input.uv + float2( -i, -i ) * res ).rgb;
    col.rgb /= 5.0f;

    return col;
}

由于URP修改了渲染管线,不能使用OnRenderImage()进行后道处理,所以需要继承ScriptableRendererFeature来实现RenderFeature

public partial class URPKawaseBlur : ScriptableRendererFeature
{
    public URPKawaseBlurSettings settings = new URPKawaseBlurSettings();
    private URPKawaseBlurPass _blurPass;
    public override void Create()
    {
        _blurPass = new URPKawaseBlurPass("KawaseBlur");
        _blurPass.blurMaterial = settings.blurMaterial;
        _blurPass.passCount = settings.blurPasses;
        _blurPass.Downsampling = settings.downsample;
        _blurPass.copyToFramebuffer = settings.copyToFramebuffer;
        _blurPass.rtID = Shader.PropertyToID(settings.targetName);
        _blurPass.renderPassEvent = settings.renderPassEvent;
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(_blurPass);
    }

    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    {
#if UNITY_2022_1_OR_NEWER
        _blurPass.Setup(renderer.cameraColorTargetHandle);
#else
        _blurPass.Setup(renderer.cameraColorTarget);
#endif
    }
}

这里需要注意,由于Unity在URP  2022.1修改了原先的设计,用RTHandles替换了RenderTargetHandle,需要补充一个条件编译指令,不然会报错。

详情:Upgrading to version 2022.1 of the Universal Render Pipeline | Universal RP | 13.1.9 (unity3d.com)

C#处的pingpong迭代:

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    CommandBuffer cmd = CommandBufferPool.Get(profilerTag);
    RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
    opaqueDesc.depthBufferBits = 0;

    // first pass
    cmd.SetGlobalFloat("_offset", 1.5f);
    cmd.Blit(source, tmpRT1, blurMaterial);

    for (var i = 1; i < passCount - 1; i++)
    {
        cmd.SetGlobalFloat("_offset", 0.5f + i);
        cmd.Blit(tmpRT1, tmpRT2, blurMaterial);

        // pingpong
        var rttmp = tmpRT1;
        tmpRT1 = tmpRT2;
        tmpRT2 = rttmp;
    }

    // final pass
    cmd.SetGlobalFloat("_offset", 0.5f + passCount - 1f);
    if (copyToFramebuffer)
    {
        cmd.Blit(tmpRT1, source, blurMaterial);
    }
    else
    {
        cmd.Blit(tmpRT1, tmpRT2, blurMaterial);
        cmd.SetGlobalTexture(rtID, tmpRT2);
    }
    context.ExecuteCommandBuffer(cmd);
    cmd.Clear();

    CommandBufferPool.Release(cmd);
}

创建好了这个后处理feature之后,将其加入到现有的管线中。

如果不知道自己用了哪种管线,可以通过Project Setting查看(这里由于后期还需要查看一些Deferred管线的效果,选了High Fidelity管线,个人可依照自己需求进行调整)

当然这个后道的shader只是负责生成一张全局的Blur Render Texture,实际在游戏中还需要制作一个将这全局模糊RT映射给场景物体的材质。

所以这里需要将屏幕空间位置计算到材质上

v2f vert (appdata v)
{
    v2f o;
    o.color = v.color;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.texcoord = TRANSFORM_TEX(v.uv, _MainTex);
    float4 screenPos  = ComputeScreenPos(o.vertex);
    o.uv = screenPos.xy / screenPos.w;
    return o;
}

这里需要注意一下函数得出的齐次坐标需要除一下w分量做变换。(齐次坐标系真好用…(x

float4 screenPos  = ComputeScreenPos(o.vertex);
o.uv = screenPos.xy / screenPos.w;

像素着色器这里直接对着这个UV采样映射就可以了

fixed4 frag (v2f input) : SV_Target
{
    fixed4 color = tex2D(_BlurTexture, input.uv);
    color.a = 1;
    return color;
}

渲染一下看看​​​​​​​效果

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值