欢迎光临散文网 会员登陆 & 注册

URP管线,RenderFeature后处理模糊小记

2023-06-06 15:56 作者:zhou-shan  | 我要投稿


参数界面

参考文章:Carle专栏  算法取自毛老师  https://zhuanlan.zhihu.com/p/125744132

模糊算法这里用的是Kawase和DualKawase,最后整合到了一起~~

文末附代码~~~~~

(才接触RenderFeature,如有解释不准确的地方,希望斧正 感谢)

简单解释一下Kawase思路:

一、算法解析

KawaseBlur

Kawase模糊精华在于算子是随模糊迭代而变化的,图中黑点为被渲染像素点,红点为采样点,由采样点的值计算出被渲染像素点的值。注意,这里4个采样点位于黑点像素的四个角!!四个角!意味着这里UV偏移就不能是意义上的One pixe,而是偏移半个像素点的距离,知道这里后咋们上蒜子图~~

算子

二、shader部分

回顾KawaseBlur图,第一次采样UV偏移为 0.5 * KawaseBlurKernel[m] * TexturePixeSize。第二次采样UV偏移为1.5*XXX,第三次采样为2.5*XXX~~~如此类推,所以说算子是迭代变化的。上shader关键代码:

shader关键代码

三、RenderFeature部分

shader都是小菜,RenderFeature才是最复杂的,上菜!!!

CS

这里我喜欢把Setting、Feature、Pass分别定义成单独的CS文件。

1、Setting解析

这里定义的参数是显示到面板的接口,

看红框部分!!!

药点

①[System.Serializable]是“序列化参数列表”,没懂什么意思,大概看来就是配合后面在Feature中定义好Public该类,就能暴露出参数,可通过面板直接修改。

②这里shader可直接Feature中指定路径,也可以暴露出来手动指定

③downsamplescale是将处理源图像给缩小的倍数,passloop是迭代次数,BlurRadius是将要传入到Pass中的定义的Offset值

2、Feature解析

Feature

药点

①Create函数当Setting变化时执行,所以这里我们将函数外声明好的m_pass放到这里面进行实例化,他的有参构造函数在Pass的Class中,简单的赋值并指定渲染时机。

有参构造函数

②AddRenderPasses每帧执行,将Pass放入到缓冲池里。这里可以做一个判空,有需要再Debug一下也行。

③Setupsource函数是我们定义的将相机图像传入到RT的一个函数。

3、Pass解析,重中之重来咯

这里定义了Pass类中需要的变量,以及enum类型的BlurPassIndex。另外临时RT的申请流程分三步走,

先用 Shader.PropertyToID("XXXX")定义int类型的ID,

再用cmd.GetTemporaryRT(ID)申请RT,

最后实例化RT(将RT与ID绑定的操作)RenderTargetIdentifier RTNAME = new RenderTargetIdentifier(ID)。

这里因为后面涉及到RT在循环中申请和释放,所以我只是先声明了RT并未将其实例化。

Pass的大体结构可分为三部分

第一部分:

OnCameraSetup

OnCameraSetup函数是初始化函数,可以将申请RT、配置RT、申请变量等初始化操作放在这里面执行。(这里的操作也可以放到第二部分Execute中做,看个人选择和功能需求)

第二部分:

Execute

Render函数如图

Render

Execute函数就是具体Pass的执行函数了,也是最重要的部分。

药点

①因为Execute函数中没有自定义commandBuffer,所以我们会在函数体内用CommandBufferPool.Get(“PassNameXXX”)命令申请一个CommandBuffer类型的缓冲区命令,最后由context.ExecuteCommandBuffer(cmd);注入到缓冲池中

②用了个Switch控制Feature执行哪个函数,因为我们将Kawase和DualKawase写到了一起

③申请的缓冲区命令和RT一样,最终都需要手动去释放以免内存泄漏。CommandBufferPool.Release(cmd);

④看Render函数,先用cmd.Blit命令将Source图像复制给RT1,再结合for函数和一个bool值和语法糖,乒乓执行Pass


稍等一下,插句嘴

看到这里细心的朋友应该注意到了,我们用RT1 RT2都是先申请后使用,为什么这个this.Source只是声明了都没申请就能用!!!!

而且最后一个Blit我们将结果直接复制到Source中就收工大吉了,

在FreamDebug中却显示我们最后的Blit是将结果Copy给了cameraColorTarget

这是为什么呢???有的同学肯定就说了Class类直接用等号赋值相当于引用,可以理解为指针。但是我发现RT、cameraColorTarget、Source的类型RenderTargetIdentifier为结构体!!不是Class!!!结构体等号赋值实际上是新定义了一个变量,这里把我困扰到了,所以我觉得最合理的应该把这里的Source全部替换为cameraColorTarget,省去AddRenderPasses里的那步,我懒了 试过可行,这儿就把重新截图了。Unity肯定哪步将二者的地址给指定了一下,所以我最后也不用再释放Source这个变量了......

有知道的朋友能告知一下原因吗!!!!!!!感谢了

继续药点

⑤这里这个语法糖我觉得非常巧妙,避免了我们去再定义一个逻辑或者RT来充当temp打工人。

⑥最后释放两张RT,这步其实可以放到接下来要截图的第三部分中,但是我还是就写在第二步的Execute中吧。

三、第三部分

就是可以用来最终注销释放RT等操作,没什么可说的。

DualKawase就先鸽一鸽吧,码字太累了...

上源码:

BlurSetting.cs

using UnityEngine;

using UnityEngine.Rendering;

using UnityEngine.Rendering.Universal;


namespace BlurSettingnamespace

{

    [System.Serializable]

    public class BlurSetting

    {

        public Shader shader = null;


        public BlurType blurType = BlurType.Kawase;


        public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingSkybox;


        public enum BlurType

        {

            Kawase ,

            DualKawase,

            Gaussian

        }       

    }

    [System.Serializable]

    public class KawaseBlurSetting

    {

        [Range(1, 10)]

        public int downsamplescale = 1;


        [Range(0, 10)]

        public int passloop = 5;


        [Range(0.0f, 5.0f)]

        public float BlurRadius = 1.0f;


    }

    [System.Serializable]

    public class DualKawaseBlurSetting

    {

        [Range(0, 10)]

        public int iteration = 5;


        [Range(0.0f, 5.0f)]

        public float BlurRadius = 1.0f;

    }

}

KawaseBlurRenderPass.cs


using UnityEngine.Rendering;

using UnityEngine.Rendering.Universal;


using BlurSettingnamespace;


namespace KawaseBlurRenderPassnamespace

{

    class KawaseBlurRenderPass : ScriptableRenderPass

    {

        private BlurSetting m_setting;

        private KawaseBlurSetting kawaseBlurSetting;

        private DualKawaseBlurSetting dualKawaseBlurSetting;

        

        enum BlurPassIndex

        {

            KawasePass = 0,

            DualKawaseUpPass = 1,

            DualKawaseDownPass = 2

        }


        public KawaseBlurRenderPass(BlurSetting blurSetting, KawaseBlurSetting kawaseBlurSetting , DualKawaseBlurSetting dualKawaseBlurSetting)

        {

            this.m_setting = blurSetting;

            this.kawaseBlurSetting = kawaseBlurSetting;

            this.dualKawaseBlurSetting = dualKawaseBlurSetting;

            this.renderPassEvent = blurSetting.passEvent;            

        }


        //定义源RT

        private RenderTargetIdentifier Source { get; set; }


        //声明两个临时RT(未实例化)  定义其ID

        private readonly int TempRTID1 = Shader.PropertyToID("TempRTID1Name");

        private readonly int TempRTID2 = Shader.PropertyToID("TempRTID2Name");        


        RenderTargetIdentifier TempRT1;

        RenderTargetIdentifier TempRT2;


        //定义一个shader参数ID

        private readonly int _OffsetID = Shader.PropertyToID("_Offset");

        Material passmat;


        public void Setupsource(RenderTargetIdentifier source)

        {

            this.Source = source;

        }

        // This method is called before executing the render pass.

        // It can be used to configure render targets and their clear state. Also to create temporary render target textures.

        // When empty this render pass will render to the active camera render target.

        // You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.

        // The render pipeline will ensure target setup and clearing happens in a performant manner.

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)

        {

            if(m_setting.blurType == BlurSetting.BlurType.Kawase)

                Initialize(cmd, renderingData);


            if (m_setting.shader == null)

                return;

            else            

                passmat = new Material(m_setting.shader); 

            

        }


        // Here you can implement the rendering logic.

        // Use <c>ScriptableRenderContext</c> to issue drawing commands or execute command buffers

        // https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html

        // You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)

        {

            CommandBuffer cmd = CommandBufferPool.Get("KawaseBlurPass");


            //重要操作

            switch(m_setting.blurType)

            {

                case BlurSetting.BlurType.Kawase:

                    Render(cmd , renderingData);

                    break;

                case BlurSetting.BlurType.DualKawase:

                    DualRender(cmd, renderingData);

                    break;

                default:

                    break;

            }


            context.ExecuteCommandBuffer(cmd);


            CommandBufferPool.Release(cmd);            

        }


        // Cleanup any allocated resources that were created during the execution of this render pass.

        public override void OnCameraCleanup(CommandBuffer cmd)

        {

        }

        void Initialize(CommandBuffer cmd , RenderingData renderingData)

        {

            RenderTextureDescriptor renderTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor;


            int width = Mathf.Max(renderTextureDescriptor.width / kawaseBlurSetting.downsamplescale, 1);

            int height = Mathf.Max(renderTextureDescriptor.height / kawaseBlurSetting.downsamplescale, 1);


            //申请两个临时RT

            cmd.GetTemporaryRT(TempRTID1, width, height, 0, FilterMode.Trilinear, renderTextureDescriptor.colorFormat);

            cmd.GetTemporaryRT(TempRTID2, width, height, 0, FilterMode.Trilinear, renderTextureDescriptor.colorFormat);


            //指定RT  实例化的过程

            TempRT1 = new RenderTargetIdentifier(TempRTID1);

            TempRT2 = new RenderTargetIdentifier(TempRTID2);

        }

        void DualInitialize(CommandBuffer cmd , RenderingData renderingData)

        {

            //RenderTextureDescriptor renderTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor;

            

        }

        void Render(CommandBuffer cmd ,RenderingData renderingData)

        {

            cmd.Blit(this.Source, TempRTID1);

            bool NeedSwith = true;

            for(int i = 1;i < (kawaseBlurSetting.passloop + 1); i++)

            {

                //cmd.SetGlobalFloat(_OffsetID , i / kawaseBlurSetting.downsamplescale + kawaseBlurSetting.BlurRadius);

                cmd.SetGlobalFloat(_OffsetID , i + kawaseBlurSetting.BlurRadius);

                cmd.Blit(NeedSwith ? TempRT1 : TempRT2, NeedSwith ? TempRT2 : TempRT1, passmat, (int)BlurPassIndex.KawasePass);

                NeedSwith = !NeedSwith;

            }

            cmd.Blit(NeedSwith ? TempRT1 : TempRT2, this.Source);


            //释放RT

            cmd.ReleaseTemporaryRT(TempRTID1);

            cmd.ReleaseTemporaryRT(TempRTID2);

        }


        void DualRender(CommandBuffer cmd , RenderingData renderingData)

        {

            RenderTextureDescriptor renderTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor;

            int Width = renderTextureDescriptor.width;

            int Height = renderTextureDescriptor.height;

            bool IsChange = true;


            //初始化RT1 RT2

            SetupRT(cmd, TempRT1, TempRTID1, Width, Height);


            Width /= 2;

            Height /= 2;

            SetupRT(cmd, TempRT2, TempRTID2, Width , Height);


            //将Source传入RT1中

            cmd.Blit(this.Source, TempRTID1);


            //传递参数给shader

            cmd.SetGlobalFloat(_OffsetID, dualKawaseBlurSetting.BlurRadius);


            //DownSample

            for (int i = 1;i < dualKawaseBlurSetting.iteration + 1; i++)

            {

                Width /= 2;

                Height /= 2;


                cmd.Blit(IsChange ? TempRTID1 : TempRTID2,

                        IsChange ? TempRTID2 : TempRTID1, passmat, (int)BlurPassIndex.DualKawaseDownPass);


                IsChange = !IsChange;


                //释放RT 准备参加下次循环计算

                cmd.ReleaseTemporaryRT(IsChange ? TempRTID2 : TempRTID1);


                //重新申请RT 准备参加下次循环计算

                SetupRT(cmd, IsChange ? TempRT2 : TempRT1, IsChange ? TempRTID2 : TempRTID1, Width, Height);    

            }

            //找到最后参与Pass计算的RT 此时两张RT都已申请,未释放


            //释放最后申请的RT

            cmd.ReleaseTemporaryRT(IsChange ? TempRTID2 : TempRTID1);

            //重新申请正常尺寸的RT

            Width *= 4;

            Height *= 4;


            SetupRT(cmd, IsChange ? TempRT2 : TempRT1, IsChange ? TempRTID2 : TempRTID1, Width, Height);

            //升采样循环

            for (int i = 1; i < dualKawaseBlurSetting.iteration + 1; i++)

            {

                Width *= 2;

                Height *= 2;


                cmd.Blit(IsChange ? TempRTID1 : TempRTID2,

                        IsChange ? TempRTID2 : TempRTID1, passmat, (int)BlurPassIndex.DualKawaseUpPass);


                IsChange = !IsChange;


                //释放RT

                cmd.ReleaseTemporaryRT(IsChange ? TempRTID2 : TempRTID1);


                //重新申请RT

                SetupRT(cmd, IsChange ? TempRT2 : TempRT1, IsChange ? TempRTID2 : TempRTID1, Width, Height);

            }

            //最后将最后参与计算的RT传递回源RT中,再将两个RT都释放

            cmd.Blit(IsChange ? TempRTID1 : TempRTID2, this.Source);


            cmd.ReleaseTemporaryRT(TempRTID1);

            cmd.ReleaseTemporaryRT(TempRTID2);

        }


        void SetupRT(CommandBuffer cmd, RenderTargetIdentifier renderTargetIdentifier, int ID , int width , int heigh)

        {

            //申请临时RT

            cmd.GetTemporaryRT(ID, width, heigh, 0, FilterMode.Trilinear, RenderTextureFormat.Default);

            //实例化RT

            renderTargetIdentifier = new RenderTargetIdentifier(ID);

        }

    }

}

KawaseBlurRenderfeature.cs

using UnityEngine;

using UnityEngine.Rendering;

using UnityEngine.Rendering.Universal;


using KawaseBlurRenderPassnamespace;

using BlurSettingnamespace;


public class KawaseBlurRenderfeature : ScriptableRendererFeature

{    

    public BlurSetting blurSetting = new();

    public KawaseBlurSetting kawaseBlurSetting = new();

    public DualKawaseBlurSetting dualKawaseBlurSetting = new();


    //public DualKawaseBlurSetting dualKawaseBlurSetting;


    KawaseBlurRenderPass m_pass;

    

    public override void Create()

    {

        m_pass = new(blurSetting, kawaseBlurSetting, dualKawaseBlurSetting);   

    }


    // Here you can inject one or multiple render passes in the renderer.

    // This method is called when setting up the renderer once per-camera.

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)

    {

        m_pass.Setupsource(renderer.cameraColorTarget);

        if(blurSetting.shader != null && kawaseBlurSetting.passloop != 0 && dualKawaseBlurSetting.iteration != 0)

            renderer.EnqueuePass(m_pass);

    }

}

shader源码(本来我写成了三个文件,但是路径难免会不同,我就随便整合成一个了,凑合用吧)

Shader "Post/KawaseBlur"

{

    Properties

    {

        [HideInInspector] _MainTex ("Texture", 2D) = "white" { }

    }


    HLSLINCLUDE

    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"


    //#include "../../RenderFeature/PostShaderSource/postprocessing.hlsl"

    //#include "../../RenderFeature/PostShaderSource/KawaseBlurKernel.hlsl"


    /////////////////////////////////////////////////////////////////////////////////////////

    //kawaseBlur算子和双重向下采样算子

    static float2 KawaseBlurKernel[4] = {

        float2(-1, -1), //左下

        float2(-1, 1), //左上

        float2(1, 1), //右上

        float2(1, -1)      //右下


    };


    //双重向上采样算子

    static float2 KawaseBlurKernel_Up[8] = {

        float2(0, 1),

        float2(0.5, 0.5),

        float2(1, 0),

        float2(0.5, -0.5),

        float2(0, -1),

        float2(-0.5, -0.5),

        float2(-1, 0),

        float2(-0.5, 0.5),

    };


    float4 KawaseBlur(TEXTURE2D(Tex), SAMPLER(samplertex), float2 uv, float2 TexSize, float PixeOffset)

    {

        float4 col = 0;

        int loopnum = 4;

        for (int i = 0; i < loopnum; i++)

        col += SAMPLE_TEXTURE2D(Tex, samplertex, uv + (float2) (PixeOffset +0.5f) * KawaseBlurKernel[i] * TexSize);

        return col / loopnum;

    }


    float4 DualKawaseBlur_Up(TEXTURE2D(Tex), SAMPLER(samplertex), float2 uv, float2 TexSize, float PixeOffset)

    {

        float4 col = 0;

        for (int i = 4; i < 0; i++)

        {

            col += SAMPLE_TEXTURE2D(Tex, samplertex, uv + KawaseBlurKernel_Up[i] * (1.0 + PixeOffset) * TexSize);

            col += SAMPLE_TEXTURE2D(Tex, samplertex, uv + KawaseBlurKernel_Up[i + 1] * (1.0 + PixeOffset) * TexSize) * 2;

        }

        return col * 0.0833;

    }


    float4 DualKawaseBlur_Down(TEXTURE2D(Tex), SAMPLER(samplertex), float2 uv, float2 TexSize, float PixeOffset)

    {

        float4 col = SAMPLE_TEXTURE2D(Tex, samplertex, uv) * 0.5;

        for (int i = 0; i < 4; i++)

        col += SAMPLE_TEXTURE2D(Tex, samplertex, uv + KawaseBlurKernel[i] * (float2)PixeOffset * TexSize) * 0.125;

        return col;

    }


    struct appdata

    {

        float4 vertex : POSITION;

        float2 uv : TEXCOORD0;

    };


    struct v2f

    {

        float2 uv : TEXCOORD0;

        float4 vertex : SV_POSITION;

    };

   


    TEXTURE2D(_MainTex);

    SAMPLER(sampler_MainTex);


    CBUFFER_START(UnityPerMaterial)


        float4 _MainTex_TexelSize;


    CBUFFER_END

   

    v2f VertDefault(appdata v)

    {

        v2f o;

        o.vertex = TransformObjectToHClip(v.vertex.xyz);

        o.uv = v.uv;

        return o;

    }


    //////////////////////////////////////////////////////////////////////////////////////////////


    float _Offset;


    float4 frag(v2f i) : SV_Target

    {

        // sample the texture

        float4 col = KawaseBlur(_MainTex, sampler_MainTex, i.uv, _MainTex_TexelSize.xy, _Offset);

        return col;

    }


    float4 frag_Up(v2f i) : SV_Target

    {

        // sample the texture

        float4 col = KawaseBlur(_MainTex, sampler_MainTex, i.uv, _MainTex_TexelSize.xy, _Offset);

        return col;

    }


    float4 frag_Down(v2f i) : SV_Target

    {

        // sample the texture

        float4 col = KawaseBlur(_MainTex, sampler_MainTex, i.uv, _MainTex_TexelSize.xy, _Offset);

        return col;

    }

    ENDHLSL


    SubShader

    {

        Tags { "RenderType" = "Opaque" }

        Cull Off

        ZWrite Off

        ZTest Always

       

        Pass

        {

            Name "KawaseBlurPass"

            HLSLPROGRAM


            #pragma vertex VertDefault

            #pragma fragment frag


            ENDHLSL

        }


        Pass

        {

            Name "DualKawaseBlurPass_UpSampling"

            HLSLPROGRAM


            #pragma vertex VertDefault

            #pragma fragment frag_Up


            ENDHLSL

        }


        Pass

        {

            Name "DualKawaseBlurPass_DownSampling"

            HLSLPROGRAM


            #pragma vertex VertDefault

            #pragma fragment frag_Down


            ENDHLSL

        }

    }

}


URP管线,RenderFeature后处理模糊小记的评论 (共 条)

分享到微博请遵守国家法律