首页 > 其他分享 >Unity 自定义Postprocess 景深和散景模糊

Unity 自定义Postprocess 景深和散景模糊

时间:2023-11-09 21:47:27浏览次数:42  
标签:uv 散景 自定义 cmd Tooltip Unity ShaderIDs public 模糊

前言

本篇将介绍如何通过添加RenderFeature实现自定义的postprocess——散景模糊(Bokeh Blur)和景深

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

景深

  • 定义:聚焦清晰的焦点前后可接受的清晰区域,也就是画面中景象清晰的范围
    如下图相机景深原理图所示,DOF为景深范围,S为被拍摄物体的距离,\(D_n\)为近点距离,\(D_F\)为远点距离,DOF左边(\(D_F\) - S)为后景深,DOF右边(S - \(D_n\))为前景深

    undefined

  • 但实际上,在shader可以无需模拟相机的成像,只需根据深度图来确定相机的模糊范围即可

散景模糊

  • 定义:落在景深以外的画面,会有逐渐产生松散模糊的效果

    下图中带有模糊的甜甜圈形状正是散景模糊的效果
    image-20231109135138396不过这一形状不是固定的,它主要受镜头的光圈叶片数的影响,所形成的光圈孔形状不同image-20231109135401908

  • 同样,我们无需模拟相机的成像,而是通过黄金角度、均匀排布来实现

  • 黄金分割(黄金比例):对一整体切一刀,形成一个较大长度a,较小长度b,黄金比例也就是\(\frac{a}{b} = \frac{b}{a + b} ≈ 0.618\)

  • 黄金角度:黄金角度也是使用的黄金比例的公式,只不过是对2\(\pi\)进行切割,a为较小角度,b为较大角度,求得a = 137.5°

    • 黄金角度有何用处?

      其实在大自然界,许多现象都与黄金角度有关:大部分花朵的花瓣以137.5°的角度旋转;植物的果实也以137.5°的角度进行生长

  • 双螺旋均匀分布

    假设现在最中心有一个像素,逐渐向外移动并以黄金角度进行旋转,就会形成如下图所示完美的双螺旋均匀分布
    image-20231109141554512

    奇特的是,就算角度有1°之差,双螺旋现象就会被打破
    image-20231109141752824

  • 实现思路:可以看到,上图所示的均匀排布的形状很像最开始所提到的甜甜圈形状,只是这里都是点。但对这些旋转后的均匀排布图像逐渐进行叠加,即可实现散景模糊

Shader

  • 实现

    Shader "Custom/BokehBlur"
    {
        Properties
        {
            [HideinInspector] _MainTex("Main Tex", 2D) = "white" {}
        }
        
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalRenderPipeline"
                "RenderType" = "Transparent"
            }
            Cull Off 
            ZWrite OFF
            ZTest Always
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    
            #pragma vertex VS
            #pragma fragment PS
    
            CBUFFER_START(UnityPerMaterial)
            float4 _MainTex_TexelSize;
            CBUFFER_END
            
            half _FocusDistance;	//焦点
            half _FarBlurIntensity;	//远景模糊强度
            half _NearBlurIntensity;	// 近景模糊强度
            half _BlurLoop;		   // 散景模糊迭代次数,越大越模糊
            half _BlurRadius;	   // 采样半径,越大甜甜圈越大
            TEXTURE2D(_MainTex);   
            SAMPLER(sampler_MainTex);
            TEXTURE2D(_SourTex);   // 模糊前的纹理
            SAMPLER(sampler_SourTex);
            SAMPLER(_CameraDepthTexture);	// viewport的深度纹理
    
            struct VSInput
            {
                float4 positionL : POSITION;
                float2 uv : TEXCOORD0;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            
            ENDHLSL
    
            Pass
            {
                NAME "Boken Blur"
                
                HLSLPROGRAM
                
                PSInput VS(VSInput vsInput)
                {
                    PSInput vsOutput;
    
                    vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
    
                    #ifdef UNITY_UV_STARTS_AT_TOP
                    if(_MainTex_TexelSize.y < 0)
                        vsInput.uv.y = 1 - vsInput.uv.y;
                    #endif
                    vsOutput.uv = vsInput.uv;
    
                    return vsOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    float4 outputColor;
    
                    float angle = 2.3398;   // 弧度制的黄金角度
                    float2x2 rotation = float2x2(cos(angle), -sin(angle), sin(angle), cos(angle));  // 旋转矩阵
                    float2 offsetUV = float2(_BlurRadius, 0);	// 每次旋转都需要进行偏移
                    float2 targetUV;
                    float r;
                    for(int i = 1; i < _BlurLoop; ++i)
                    {
                        // 甜甜圈的面积是均匀分布,因此r是线性增加的
                        r = sqrt(i);
                        offsetUV = mul(rotation, offsetUV);
                        targetUV = psInput.uv + _MainTex_TexelSize.xy * offsetUV * r;
                        // 暗处模糊不会很明显,但明处会
                        outputColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, targetUV);
                    }
    
                    return outputColor / (_BlurLoop - 1);	// 模糊基操
                }
                
                ENDHLSL
            }
    
            Pass
            {
                NAME "depth of field"
                HLSLPROGRAM
                PSInput VS(VSInput vsInput)
                {
                    PSInput vsOutput;
    
                    vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
    
                    #ifdef UNITY_UV_STARTS_AT_TOP
                    if(_MainTex_TexelSize.y < 0)
                        vsInput.uv.y = 1 - vsInput.uv.y;
                    #endif
                    vsOutput.uv = vsInput.uv;
    
                    return vsOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    
                    float4 blurTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, psInput.uv);
                    float4 sourceTex = SAMPLE_TEXTURE2D(_SourTex, sampler_SourTex, psInput.uv);  // 未模糊的纹理
    
                    // 转换为线性深度,且由近到远,值从0到1
                    float depth = Linear01Depth(tex2D(_CameraDepthTexture, psInput.uv).r , _ZBufferParams);
                    float distance;
                    // 分为远处模糊和近处模糊
                    if(depth > _FocusDistance)
                    {
                        // 因为深度是线性的,但我们想要的是焦点处清晰,但前后逐渐模糊,所以这里采用二次函数
                        distance = saturate((depth - _FocusDistance) * (depth - _FocusDistance) * _FarBlurIntensity);
                    }
                    else
                    {
                        distance = saturate((depth - _FocusDistance) * (depth - _FocusDistance) * _NearBlurIntensity);
                    }
                    
                    // 最后模糊图和原图基于距离进行lerp
                    return lerp(sourceTex, blurTex, distance);
                }
                ENDHLSL
            }
        }
    }
    

RenderFeature

  • 都是些老朋友,这里放重要部分

  • PassSetting

    [System.Serializable]
    public class PassSetting
    {
        [Tooltip("profiler tag will show up in frame debugger")]
        public readonly string m_ProfilerTag = "Dual Blur Pass";
        
        [Tooltip("Pass insert position")]
        public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents;
        
        [Tooltip("resolution ratio of sample")]
        [Range(1, 10)] 
        public int m_Downsample = 1;
    
        [Tooltip("Loop of sample")]
        [Range(2, 7)] 
        public int m_PassLoop = 2;
    
        [Tooltip("Radius of sample")] 
        [Range(0, 10)]
        public float m_BlurRadius = 1;
    
        [Tooltip("Near Blur Intensity of DOF")] 
        [Range(0, 10)]
        public float m_NearBlurIntensity = 1;
    
        [Tooltip("Far Blur Intensity of DOF")] 
        [Range(0,10)]
        public float m_FarBlurIntensity = 1;
    
        [Tooltip("Camera Focus Point")] 
        [Range(0, 1)]
        public float m_FocusDistance = 1;
    
    }
    
  • 全部实现

    public class BokehBlurRenderFeature : ScriptableRendererFeature
    {
        // render feature 显示内容
        [System.Serializable]
        public class PassSetting
        {
            [Tooltip("profiler tag will show up in frame debugger")]
            public readonly string m_ProfilerTag = "Dual Blur Pass";
            
            [Tooltip("Pass insert position")]
            public RenderPassEvent m_passEvent = RenderPassEvent.AfterRenderingTransparents;
            
            [Tooltip("resolution ratio of sample")]
            [Range(1, 10)] 
            public int m_Downsample = 1;
    
            [Tooltip("Loop of sample")]
            [Range(2, 7)] 
            public int m_PassLoop = 2;
    
            [Tooltip("Radius of sample")] 
            [Range(0, 10)]
            public float m_BlurRadius = 1;
    
            [Tooltip("Near Blur Intensity of DOF")] 
            [Range(0, 10)]
            public float m_NearBlurIntensity = 1;
    
            [Tooltip("Far Blur Intensity of DOF")] 
            [Range(0,10)]
            public float m_FarBlurIntensity = 1;
    
            [Tooltip("Camera Focus Point")] 
            [Range(0, 1)]
            public float m_FocusDistance = 1;
    
        }
        
        public PassSetting m_Setting = new PassSetting();
        BokehBlurRenderPass m_BokehBlurPass;
        
        // 初始化
        public override void Create()
        {
            m_BokehBlurPass = new BokehBlurRenderPass(m_Setting);
        }
    
        // 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_BokehBlurPass);
        }
    }
    

RenderPass

  • ShaderIDs

    static class ShaderIDs
    {
        // int 相较于 string可以获得更好的性能,因为这是预处理的
        internal static readonly int  m_BlurRadiusProperty = Shader.PropertyToID("_BlurRadius");
        internal static readonly int  m_NearBlurIntensityProperty = Shader.PropertyToID("_NearBlurIntensity");
        internal static readonly int  m_FarBlurIntensityProperty = Shader.PropertyToID("_FarBlurIntensity");
        internal static readonly int  m_PassLoopProperty = Shader.PropertyToID("_BlurLoop");
        internal static readonly int m_FocusDistanceProperty = Shader.PropertyToID("_FocusDistance");
    
        // Blur RT and source RT
        internal static readonly int m_BlurRTProperty = Shader.PropertyToID("_BufferRT1");
        internal static readonly int m_SourRTProperty = Shader.PropertyToID("_SourTex");
    }
    
  • 构造函数

    // 用于设置material 属性
    public BokehBlurRenderPass(BokehBlurRenderFeature.PassSetting passSetting)
    {
        this.m_passSetting = passSetting;
    
        renderPassEvent = m_passSetting.m_passEvent;
    
        if (m_Material == null) m_Material = CoreUtils.CreateEngineMaterial("Custom/BokehBlur");
        
        // 基于pass setting设置material Properties
        m_Material.SetFloat(ShaderIDs.m_BlurRadiusProperty, m_passSetting.m_BlurRadius);
        m_Material.SetFloat(ShaderIDs.m_NearBlurIntensityProperty, m_passSetting.m_NearBlurIntensity);
        m_Material.SetFloat(ShaderIDs.m_FarBlurIntensityProperty, m_passSetting.m_FarBlurIntensity);
        m_Material.SetFloat(ShaderIDs.m_FocusDistanceProperty, m_passSetting.m_FocusDistance);
        m_Material.SetInt(ShaderIDs.m_PassLoopProperty, m_passSetting.m_PassLoop);
    }
    
  • OnCameraSetup

    public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    {
        // Grab the color buffer from the renderer camera color target
        m_TargetBuffer = renderingData.cameraData.renderer.cameraColorTarget;
        
        // camera target descriptor will be used when creating a temporary render texture
        RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
        // 设置 temporary render texture的depth buffer的精度
        descriptor.depthBufferBits = 0;
        // 注意!因为申请RT后,RT的分辨率不可改变,而该Source RT在后续由于camera render targt会复制一份给他,所以这里不能用降采样的分辨率
        cmd.GetTemporaryRT(ShaderIDs.m_SourRTProperty, descriptor, FilterMode.Bilinear);
        // 降采样
        descriptor.width /= m_passSetting.m_Downsample;
        descriptor.height /= m_passSetting.m_Downsample;
        cmd.GetTemporaryRT(ShaderIDs.m_BlurRTProperty, descriptor, FilterMode.Bilinear);
    }
    
  • Execute

    // 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)))
        {
            // 将camera texture复制给sour RT,同时发送给shader中的_SourTex
            cmd.CopyTexture(m_TargetBuffer, ShaderIDs.m_SourRTProperty);
            // 进行 bokenh blur并复制给Blur RT
            cmd.Blit(m_TargetBuffer, ShaderIDs.m_BlurRTProperty, m_Material, 0);
            // 将blur后且进行景深计算的RT复制给输出的RT
            cmd.Blit(ShaderIDs.m_BlurRTProperty, m_TargetBuffer, m_Material, 1);
        }
        
        // Execute the command buffer and release it
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }
    
  • OnCameraCleanUp

    // 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");
        
        cmd.ReleaseTemporaryRT(ShaderIDs.m_BlurRTProperty);
        cmd.ReleaseTemporaryRT(ShaderIDs.m_SourRTProperty);
    }
    

效果

image-20231109212709491

reference

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

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

https://zh.wikipedia.org/wiki/黄金分割率

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

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

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

标签:uv,散景,自定义,cmd,Tooltip,Unity,ShaderIDs,public,模糊
From: https://www.cnblogs.com/chenglixue/p/17822931.html

相关文章

  • JavaScript--String对象&自定义对象&Windows对象
    String对象 varstr1=newString("abc")varstr2="abc"trim():去除字符串前后两端的空白字符自定义对象  BOM对象 1、Windowconfirm方法会产生一个返回值varflag=confirm("");按确定返回true按取消返回falsesetTimeout()只执行一次setInterval()循环执行......
  • 19、Flink 的Table API 和 SQL 中的自定义函数及示例(4)
    (文章目录)本文展示了自定义函数在Flinksqlclient的应用以及自定义函数中使用pojo的示例。本文依赖flink、kafka集群能正常使用。本文分为2个部分,即自定义函数在Flinksqlclient中的应用以及自定义函数中使用pojo数据类型。本文的示例如无特殊说明则是在Flink1.17版本中运......
  • IDEA 集成 EasyCode 插件,快速生成自定义 mybatisplus 代码
    IDEA集成easyCode插件在idea插件市场中,搜索EasyCode插件,下载并进行安装EasyCode插件介绍1.修改作者名称EasyCode插件可以修改作者名称,即生成代码后,注释中自动添加相应作者的姓名。2.TypeMapperTypeMapper指的是生成mapper.xml文件中数据库中的字段和java......
  • salt自定义模块内使用日志例子
    如果你想要在你的SaltMinion中使用自定义的Salt模块并且记录日志,你可以创建一个自定义Salt模块,并在模块中使用Python的标准`logging`库来记录日志。以下是一个示例:首先,在SaltMaster上创建一个自定义模块的目录,例如`/srv/salt/_modules/`。然后在该目录中创建一个Python文件,例......
  • UNITY---huatuo,革命性的热更新解决方案
    最近huatuo(华佗)热更新解决方案火爆了unity开发圈,起初我觉得热更新嘛,不就是内置一个脚本解释器+脚本语言开发,如xLua,ILRuntime,puerts。Huatuo又能玩出什么花样,凭什么会这么NB,引起了那么多程序员的关注与称赞呢?带着这些问题我详细的看了huatuo的资料,阅读了示例项目+huatuo源码......
  • Unity 搭建ILRuntime开发环境
    Unity热更新目前主流的方案有:Lua,ILRuntime,puerts,huatuo方案。前两个大家都比较熟悉了,puerts是基于TypeScript开发的热更新,huatuo是基于C#的方案。后两个大家会比较陌生。本系列分享基于ILRuntime来做热更新。 ILRuntime热更新原理 ILRuntime热更新原理是基于Unity......
  • Django——增加自定义功能
    在Django中,增加一个自己的功能,并在Django运行中创建一个进程,单独运行。#如果需要在运行时,同时运行某个程序代码,那么在wsgi中添加即可。fromnetwork_configimporttestfrommultiprocessingimportProcessif__name__=='gb_netconf.wsgi':#windows中必须要写,如果不......
  • Unity程序员要注意的编码规范
    Unity程序员如何写好代码,写代码的过程中要注意的哪些些点,今天给大家分享一些经验规则,通过遵守这些规则作出明智的架构决策,确保更高的团队开发效率和稳定的代码。避免抽象类我们在开发中经常喜欢抽象,其实抽象得过程中往往会产生设计过度和抽象过度,而这些抽象得代码可能会令人难以......
  • 从FrameDebugger看Unity渲染
    Unity如何渲染一个3D+2D的游戏画面,今天通过FrameDebugger来看下Unity内置渲染管线的渲染策略,后续再出一些URP渲染管线相关的文章。 Unity渲染场景的几个主要部分 Unity内置渲染管线是基于摄像机来进行渲染的,每个摄像机按照摄像机的渲染顺序来依次渲染,渲染完一个摄像机,再......
  • laravel:自动加载自定义类(10.27.0)
    一,配置1,在laravel项目的根目录下添加extend目录,如图:2,编辑composer.json,在autoload增加一行:"":"extend/",如图:生成自动加载文件:liuhongdi@lhdpc:/data/laravel/dignews$composerdump-autoload-oGeneratingoptimizedautoloadfiles...命令的解释:将PSR-......