Unity 自定义Postprocess Kawase Blur

发布时间 2023-11-09 09:43:42作者: 爱莉希雅

前言

本篇将介绍如何通过添加RenderFeature实现自定义的postprocess——KawaseBlur

关于RenderFeature的基础可以看这篇https://www.cnblogs.com/chenglixue/p/17816447.html

KawaseBlur介绍

  • 因为毛神对于十大模糊算法的介绍已经整理得十分详细了,所以这里不会深入,但会大致讲讲它的思想
  • 思想:对距离目标pixel越来越远的地方的四个点进行sample,且在两个Render Target Texture间进行Blit。与高斯模糊不同的是,KawaseBlur采样随循环次数变化的Blur Kernel
    img随循环次数增加,Blur Kernel逐渐变大
    image-20231108173649047

Shader

  • Shader很简单,重点在于Render Pass中的循环迭代

  • 实现

    Shader "Custom/PP_KawaseBlur"
    {
        Properties
        {
            _MainTex("Main Tex", 2D) = "white" {}
            _BlurIntensity("Blur Intensity", Range(0, 10)) = 1
        }
        
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalPipeline"
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            half _BlurIntensity;
            float4 _MainTex_TexelSize;
            CBUFFER_END
    
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            ENDHLSL
    
            Pass
            {
                Cull Off
                ZWrite Off
                ZTest Always
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
                
                struct VSInput
                {
                    float4 postionL : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct PSInput
                {
                    float4 positionH : SV_POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput vsOutput;
    
                    vsOutput.positionH = TransformObjectToHClip(vsInput.postionL);
                    vsOutput.uv = vsInput.uv;
    
                    return vsOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    float4 outputColor = 0;
    
                    // 经典卷积
                    outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv);
                    outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(-1, -1) * _MainTex_TexelSize.xy * _BlurIntensity);
                    outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(-1, 1) * _MainTex_TexelSize.xy * _BlurIntensity);
                    outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(1, -1) * _MainTex_TexelSize.xy * _BlurIntensity);
                    outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv + float2(1, 1) * _MainTex_TexelSize.xy * _BlurIntensity);
    
                    return outputColor / 5.f;
                }
                ENDHLSL
            }
        }
    }
    

Render Feature

  • 整体实现

    public class KawaseBlurRenderFeature : ScriptableRendererFeature
    {
        // render feature Inspector 显示内容
        [System.Serializable]
        public class PassSetting
        {
            // profiler tag will show up in frame debugger
            public readonly string m_ProfilerTag = "Kawase Blur Pass";
            // 安插位置
            public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents;
    
            // 控制分辨率
            [Range(1, 5)] 
            public int m_sampleWeaken = 1;
    
            [Range(0, 5)] 
            public int m_PassLoop = 2;
    
            // 模糊强度
            [Range(0, 10)] 
            public float m_BlurIntensity = 5;
        }
        
        public PassSetting m_Setting = new PassSetting();
        KawaseBlurRenderPass m_KawaseBlurPass;
        
        // 初始化 Pass
        public override void Create()
        {
            m_KawaseBlurPass = new KawaseBlurRenderPass(m_Setting);
        }
    
        // 添加Pass
        // 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)
        {
            // can queue up multiple passes after each other
            renderer.EnqueuePass(m_KawaseBlurPass);
        }
    }
    
  • PassSetting:创建一个PassSetting class,用于存储后续计算必要的变量

  • Create():初始化Pass

  • AddRenderPasses():添加一个或多个Pass

Render Pass

  • 整体实现

    class KawaseBlurRenderPass : ScriptableRenderPass
    {
        // 用于存储pass setting
        private KawaseBlurRenderFeature.PassSetting m_passSetting;
    
        // render target texture
        private RenderTargetIdentifier m_TargetBuffer, m_TempBuffer1, m_TempBuffer2;
    
        private Material m_Material;
    
        static class ShaderIDs
        {
            // int 相较于 string可以获得更好的性能,因为这是预处理的
            internal static readonly int m_BlurIntensityProperty = Shader.PropertyToID("_BlurIntensity");
            // 关于这里的_BufferRT,应该是shader自带的内容,笔者google也未查到,还望知道的大佬指点迷津
            internal static readonly int m_TempBufferRT1Property = Shader.PropertyToID("_BufferRT1");
            internal static readonly int m_TempBufferRT2Property = Shader.PropertyToID("_BufferRT2");
        }
    
        // 用于设置material 属性
        public KawaseBlurRenderPass(KawaseBlurRenderFeature.PassSetting passSetting)
        {
            this.m_passSetting = passSetting;
    
            renderPassEvent = m_passSetting.m_passEvent;
    
            if (m_Material == null) m_Material = CoreUtils.CreateEngineMaterial("Custom/PP_KawaseBlur");
    
            // 基于pass setting设置material Properties
            m_Material.SetFloat(ShaderIDs.m_BlurIntensityProperty, m_passSetting.m_BlurIntensity);
        }
    
        // Gets called by the renderer before executing the pass.
        // Can be used to configure render targets and their clearing state.
        // Can be used to create temporary render target textures.
        // If this method is not overriden, the render pass will render to the active camera render target.
        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            // camera target descriptor will be used when creating a temporary render texture
            RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
    
            // 降低分辨率,改善模糊效果
            // Downsample original camera target descriptor
            descriptor.width /= m_passSetting.m_sampleWeaken;
            descriptor.height /= m_passSetting.m_sampleWeaken;
    
            // Set the number of depth bits we need for temporary render texture
            descriptor.depthBufferBits = 0;
    
            // Enable these if pass requires access to the CameraDepthTexture or the CameraNormalsTexture.
            // ConfigureInput(ScriptableRenderPassInput.Depth);
            // ConfigureInput(ScriptableRenderPassInput.Normal);
    
            // Grab the color buffer from the renderer camera color target
            m_TargetBuffer = renderingData.cameraData.renderer.cameraColorTarget;
    
            // Create a temporary render texture using the descriptor from above
            cmd.GetTemporaryRT(ShaderIDs.m_TempBufferRT1Property, descriptor, FilterMode.Bilinear);
            cmd.GetTemporaryRT(ShaderIDs.m_TempBufferRT2Property, descriptor, FilterMode.Bilinear);
            m_TempBuffer1 = new RenderTargetIdentifier(ShaderIDs.m_TempBufferRT1Property);
            m_TempBuffer2 = new RenderTargetIdentifier(ShaderIDs.m_TempBufferRT2Property);
        }
    
        // The actual execution of the pass. This is where custom rendering occurs
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // Grab a command buffer. We put the actual execution of the pass inside of a profiling scope
            CommandBuffer cmd = CommandBufferPool.Get();
    
            using (new ProfilingScope(cmd, new ProfilingSampler(m_passSetting.m_ProfilerTag)))
            {
                // Blit from the color buffer to a temporary buffer and back
                Blit(cmd, m_TargetBuffer, m_TempBuffer1, m_Material, 0);
    
                for (uint i = 1; i <= m_passSetting.m_PassLoop; ++i)
                {
                    // 增大Blur Kernel.从而影响shader
                    cmd.SetGlobalFloat(ShaderIDs.m_BlurIntensityProperty, i * m_passSetting.m_BlurIntensity + 1);
    
                    Blit(cmd, m_TempBuffer1, m_TempBuffer2, m_Material, 0);
    
                    // 交换 tempbuffer1 和 tempbuffer2,使得下个循环继续使用tempbuffer1进行计算
                    var tempRT = m_TempBuffer1;
                    m_TempBuffer1 = m_TempBuffer2;
                    m_TempBuffer2 = tempRT;
                }
    
                // 最后将最终的模糊图像传给要输出的Render Target
                cmd.SetGlobalFloat(ShaderIDs.m_BlurIntensityProperty, m_passSetting.m_PassLoop * m_passSetting.m_BlurIntensity + 1);
                Blit(cmd, m_TempBuffer1, m_TargetBuffer, m_Material, 0);
            }
    
            // Execute the command buffer and release it
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    
        // Called when the camera has finished rendering
        // release/cleanup any allocated resources that were created by this pass
        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            if(cmd == null) throw new ArgumentNullException("cmd");
    
            // Since created a temporary render texture in OnCameraSetup, we need to release the memory here to avoid a leak
            cmd.ReleaseTemporaryRT(ShaderIDs.m_TempBufferRT1Property);
            cmd.ReleaseTemporaryRT(ShaderIDs.m_TempBufferRT2Property);
        }
    }
    
  • ShaderIDs:创建一个ShaderIDs class, 用于存储计算shader properities后的int ID值

  • OnCameraSetup():准备render target相关信息

  • Execute():Render Pass的灵魂,主要逻辑都在这里实现

  • OnCameraCleanup():执行完pass后,释放render target占用的内存

效果

image-20231108173429271

  • 模糊前

    image-20231108173450712

  • 模糊后

    image-20231108173438887

reference

https://zhuanlan.zhihu.com/p/125744132

https://github.com/QianMo/X-PostProcessing-Library/blob/master/Assets/X-PostProcessing/Effects/KawaseBlur/KawaseBlur.cs