首页 > 其他分享 >Unity 自定义Postprocess Kawase Blur

Unity 自定义Postprocess Kawase Blur

时间:2023-11-08 17:45:20浏览次数:40  
标签:自定义 render descriptor Unity passSetting Pass public Kawase MainTex

前言

本篇将介绍如何通过添加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

标签:自定义,render,descriptor,Unity,passSetting,Pass,public,Kawase,MainTex
From: https://www.cnblogs.com/chenglixue/p/17817958.html

相关文章

  • pyspark UDF调用自定义python函数
    从PysparkUDF调用另一个自定义Python函数Python编码的PySparkUDF提供了调用其他Python函数的能力,无论它们是内置函数还是来自外部库的用户定义函数。通过使用户能够利用现有的Python代码,此功能提高了UDF的模块化和可重用性。在分布式PySpark环境中,用户可以轻松实......
  • Android 实现加减自定义控件
    ✍️作者简介:沫小北/码农小北(专注于Android、Web、TCP/IP等技术方向)</br>......
  • Unity 格子工具
     usingUnityEditor;usingUnityEngine;publicclassCustomGridWindow3:EditorWindow{privateTexture2DcustomBackgroundTexture;privateVector2gridSize=newVector2(10,10);privateColor[]cellColors;privateboolisSelecting=fal......
  • 解锁表单新操作!JVS低代码表单自定义按钮功能全解析
    在普通的表单设计中,虽然自带的【提交】、【重置】、【取消】按钮可以满足基本操作需求,但在面对更多复杂的业务场景时,这些按钮的显示控制就显得有些力不从心。为了更好地满足用户在表单操作过程中的个性化需求,JVS低代码推出了表单自定义按钮功能。这项功能不仅可以更灵活地操作表单......
  • 一些有用的自定义函数(抄录)
    提取字符串中的数字'提取字符串中的数字FunctionGetDigits(strTextAsString)AsStringDimstrCharAsString,strMsgAsStringDimiAsLongstrMsg=""Fori=1ToLen(strText)strChar=Mid(strText,i,1)IfstrCharLike"#"T......
  • 自定义钩子
    classBaseSerializer(Field): @propertydefdata(self):ifnothasattr(self,'_data'):ifself.instanceisnotNoneandnotgetattr(self,'_errors',None):self._data=self.to_representation(sel......
  • Unity 自定义Postprocess BSC明度饱和度对比度
    前言本篇将介绍如何通过添加RenderFeature实现自定义的postprocess——调整屏幕的明度、饱和度、对比度(以下统称BSC)关于RenderFeature的基础可以看这篇https://www.cnblogs.com/chenglixue/p/17816447.htmlShaderBrightness:很简单乘以rendertargettexture即可Saturatio......
  • Unity项目开发中如何做资源加密
    Unity的游戏很容易被人反编译出来,然后再重新打包发布,把自己辛辛苦苦开发的游戏,抄写的一丝不挂。很多项目要求要做好资源加密,Unity中如何做好资源加密呢?本文給大家分享加密算法+资源打包整合思路:(1) 游戏资源加密如何选择加密算法;(2) Assetsbundle资源包的加密与解密;  ......
  • Unity架构师必备的开源库,让你3天搭建商用游戏框架
    现在Unity的相关技术已经都非常常熟了,如果你的技术能力与阅历够,搭建一个商用的游戏框架,你只需要3天的时间。今天给大家分享一个Unity老鸟3天能搭建一个自己的商用框架的几个必备的开源库,方便大家学习与使用,同时学习这些有前途的开源库也能让你在公司里面游刃有余。 1:搭建商用......
  • Unity HybridCLR(wolong)/huatuo系列
    Lua,ILRuntime,HybridCLR(wolong),huatuo热更对比分析 这两年,各种Unity热更新方案如雨后春笋般出来了,今天来写篇文章来对比一下Unity各大热更新方案的优缺点。目前主流的Unity热更新的方案有:  Lua系解决方案:内置一个Lua虚拟机,做好UnityEngine与C#框架的Lua导出。典型的......