学习教程来自:【技术美术百人计划】图形 4.5 Dof景深基础

笔记

1. 什么是景深

相机对焦点前后相对清晰的成像范围

2. 景深的作用

突出表达

3. 移动端景深效果实现

3.1 制作思路

在后处理阶段,制作mask,分别渲染模糊场景和正常场景,再合并效果

3.2 原图模糊处理

在OnrenderImage对MainTex中的纹理模糊,传值给BlurTex,再进行混合

Pass{
    CGPROGRAM

    #pragma vertex vert
    #pragma fragment frag                                                       // 顶点着色器和片源着色器声明

    #include "UnityCG.cginc"

    struct appdata{                                                             // 顶点着色器输入结构体,位置和UV
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
    };

    struct v2f{                                                                 // 顶点着色器输出结构体,位置和UV
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
    };

    sampler2D _MainTex;
    float4 _BlurOffset;                                                         // 模糊偏移

    v2f vert (appdata v){
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);                              // 对象空间转换到裁剪空间
        o.uv = v.uv;
        return o;
    }       

    half4 frag (v2f i) : SV_Target{

//----------------------------------- 高斯模糊处理 -------------------------------------//
        half2 uv1 = i.uv + _BlurOffset.xy * half2(1,0) * -2;
        half2 uv2 = i.uv + _BlurOffset.xy * half2(1,0) * -1;
        half2 uv3 = i.uv;
        half2 uv4 = i.uv + _BlurOffset.xy * half2(1,0) * 1;
        half2 uv5 = i.uv + _BlurOffset.xy * half2(1,0) * 2;

        half2 uv6 = i.uv + _BlurOffset.xy * half2(0,1) * -2;
        half2 uv7 = i.uv + _BlurOffset.xy * half2(0,1) * -1;
        half2 uv8 = i.uv;
        half2 uv9 = i.uv + _BlurOffset.xy * half2(0,1) * 1;
        half2 uv10 = i.uv + _BlurOffset.xy * half2(0,1) * 2;

        half4 s = 0;
        s += tex2D(_MainTex, uv1) * 0.05;
        s += tex2D(_MainTex, uv2) * 0.25;
        s += tex2D(_MainTex, uv3) * 0.40;
        s += tex2D(_MainTex, uv4) * 0.25;
        s += tex2D(_MainTex, uv5) * 0.05;

        s += tex2D(_MainTex, uv6) * 0.05;
        s += tex2D(_MainTex, uv7) * 0.25;
        s += tex2D(_MainTex, uv8) * 0.40;
        s += tex2D(_MainTex, uv9) * 0.25;
        s += tex2D(_MainTex, uv10) * 0.05;

        s /= 2;


        //return half4(final_depth.xxx, 1);
        return half4(s.rgb, 1);                                                 // 高斯模糊的效果对比
        //return half4(1,1,1, 1);                                                 // 高斯模糊的效果对比
    }                      

    ENDCG
}

3.3 获得景深Mask

获取深度Texture,对比设置的焦点并计算景深范围,范围内的不做模糊处理,值为0,范围外的渐变的规整到0-1并乘以smooth使其更平滑

Pass{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag                                                       // 顶点着色器和片源着色器声明

    #include "UnityCG.cginc"

    struct appdata{                                                             // 顶点着色器输入结构体,位置和UV
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
    };

    struct v2f{                                                                 // 顶点着色器输出结构体,位置和UV
        float2 uv : TEXCOORD0;
        float4 vertex : SV_POSITION;
    };

    sampler2D _MainTex;
    sampler2D _BlurTex;                                                         // 模糊后的纹理
    sampler2D _CameraDepthTexture;                                              // 相机深度纹理
    float4 _BlurOffset;                                                         // 模糊范围偏移
    float _FocusDistance, _DepthOfField, _DofSmoothRange;                       // 焦点距离,景深,光滑过度  
    float _Step;               

    v2f vert (appdata v){
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);                              // 对象空间转换到裁剪空间
        o.uv = v.uv;
        return o;
    }       

    half4 frag (v2f i) : SV_Target{
        half4 col = tex2D(_MainTex, i.uv);                                      // 原图颜色
        half4 blur_col = tex2D(_BlurTex, i.uv);                                 // 模糊后的颜色


        // half depth = Linear01Depth(tex2D(_CameraDepthTexture, i.uv));        // 避免远裁面的值对景深效果的影响
        half depth = Linear01Depth(tex2D(_CameraDepthTexture, i.uv)).r * _ProjectionParams.z * _Step;//避免远裁面的值对景深效果的影响
        float focusNear = _FocusDistance - _DepthOfField;                       // 近焦距点
        float focusFar = _FocusDistance + _DepthOfField;                        // 远焦距点

        half final_depth = 0;
        if((depth>=focusNear)&&(depth<=focusFar));                              // 在景深范围内的点不做模糊
        else {
            if(depth<focusNear){                                                // 在景深范围之外的点,全部归到0-1
                final_depth = saturate(abs(focusNear-depth) * _DofSmoothRange); // 加入smooth使过度更平滑
            }else{
                final_depth = saturate(abs(focusFar-depth) * _DofSmoothRange);

            }
        }

        half4 final_col = lerp(col, blur_col, final_depth*1.2);                 // 使用Mask进行混合
        //return half4(final_depth,final_depth,final_depth, 1);
        return half4(final_col.rgb, 1);
        //return half4(depth.xxx, 1);
        // return half4(col.rgb, 1);

    }                      

    ENDCG
}

白色区域为被模糊的区域(焦外区域)

3.4 脚本部分

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DOF : MonoBehaviour
{
    public Material mat;
    // Start is called before the first frame update
    [Range(1, 4)]
    public int _Iteration = 2;                                              // 迭代次数
    [Range(0, 15)]
    public float _BlurRadius = 5;                                           // 模糊半径
    [Range(0, 10)]
    public float _DownSample = 2;                                           // 下采样次数
    [Range(0, 10)]
    public float _DepthOfField = 1.0f;                                      // 景深
    public float _FocusDistance = 1;                                        // 焦距

    void Start()
    {
        if (mat == null || SystemInfo.supportsImageEffects == false || mat.shader == null || mat.shader.isSupported == false){
            enabled = false;                                                // 判断材质和shader是否为空,是否被支持,来决定是否启用
            return ;
        }
    }


    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        {
            mat.SetFloat("_DepthOfField", _DepthOfField);
            mat.SetFloat("_FocusDistance", _FocusDistance);
            int width = (int)(src.width / _DownSample);                     // 下采样减少计算量
            int height = (int)(src.height / _DownSample);                   
//----------------------------- 高斯模糊 Pass 0 ----------------------------//                                                                        
            mat.SetVector("_BlurOffset", new Vector4(_BlurRadius / width, _BlurRadius / height, 0, 0));

            RenderTexture RT1 = RenderTexture.GetTemporary(width, height);
            RenderTexture RT2 = RenderTexture.GetTemporary(width, height);  // 创建2张RT交替处理
            Graphics.Blit(src, RT1, mat, 0);
            //Graphics.Blit(src, dest, mat, 1);
            for (int i = 0; i < _Iteration; i++)                            // 每次迭代减少尺寸,降采样
            {
                RenderTexture.ReleaseTemporary(RT2);
                width = width / 2;
                height = height / 2;
                RT2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(RT1, RT2, mat, 0);
                width = width / 2;
                height = height / 2;
                RenderTexture.ReleaseTemporary(RT1);
                RT1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(RT2, RT1, mat, 0);

            }
            for (int i = 0; i < _Iteration; i++)                            // 每次迭代放大尺寸,升采样
            {
                RenderTexture.ReleaseTemporary(RT2);
                width = width * 2;
                height = height * 2;
                RT2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(RT1, RT2, mat, 0);
                width = width * 2;
                height = height * 2;
                RenderTexture.ReleaseTemporary(RT1);
                RT1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(RT2, RT1, mat, 0);
            }

//------------------------------- 混合 Pass 1 ------------------------------// 
            mat.SetTexture("_BlurTex", RT1);

            Graphics.Blit(src, dest, mat, 1);                               // 使用shader的第二个Pass混合原图和模糊

            RenderTexture.ReleaseTemporary(RT1);
            RenderTexture.ReleaseTemporary(RT2);                            // 释放
        }
    }
}

4. 高级景深效果思路扩展

4.1 颜色泄露

对焦区域的颜色被模糊到了背景中
解决:扩散滤波,规定模糊的范围

4.2 模糊不连续

前景区域的模糊不连续
解决:前景单独计算,制作一个Mask去融合背景

4.3 散景模拟(Bokeh)

为了模拟不同光源在景深下的效果
解决:修改滤波的公式

作业

1. 实现景深效果

高斯模糊下

上边代码实现的效果

换成基于法线的双边滤波后,颜色泄露的情况有所改善,模糊的颜色也感觉正常了



一个潜在的bug,教程视频中的代码没有释放RT1,导致显存跑满,改为申请前释放即可

2. 分析官方后处理插件PPS中景深效果的实现

简单看了下代码,看的比较浅,不一定理解的对,大概有以下过程

enum Pass
{
    CoCCalculation,             // 由深度、焦点距离、_LensCoeff计算CoC的值,感觉和上边插值用的final_depth类似
    CoCTemporalFilter,          // 开启TAA的话将Texture中CoC的值进行滤波
    DownsampleAndPrefilter,     // 下采样
    BokehSmallKernel,           // 5种模糊方式计算dof纹理(焦外模糊的颜色)
    BokehMediumKernel,
    BokehLargeKernel,
    BokehVeryLargeKernel,
    PostFilter,
    Combine,                    // 将原图的颜色和模糊后的颜色结合
    DebugOverlay 
}