Unity Shader PostProcessing - 7 - DepthOfField(DOF)景深

141 篇文章 31 订阅

基本概念

在百度百科里有解析:景深
引用其简短描述的一段话:

景深(DOF),是指在摄影机镜头或其他成像器前沿能够取得清晰图像的成像所测定的被摄物体前后距离范围。光圈、镜头、及焦平面到拍摄物的距离是影响景深的重要因素。
在聚焦完成后,焦点前后的范围内所呈现的清晰图像的距离,这一前一后的范围,便叫做景深。
在镜头前方(焦点的前、后)有一段一定长度的空间,当被摄物体位于这段空间内时,其在底片上的成像恰位于同一个弥散圆之间。被摄体所在的这段空间的长度,就叫景深。换言之,在这段空间内的被摄体,其呈现在底片面的影象模糊度,都在容许弥散圆的限定范围内,这段空间的长度就是景深。

可以看看里面介绍的视频,说得挺简洁的,我总结为下图:
在这里插入图片描述
影响景深的参数,我来说明一下:(上图DOV是我在作画时写错的,应该是DOF)

  • F:是对焦点。
  • C1C2:是对焦点与瞳孔(或镜头)连接直线上,在焦点前后两个深蓝色的圆圈,叫:弥散圆。如果弥散圆大小超出了瞳孔(或镜头)能识别的范围,图像就会模糊,就像对焦的内容与相机底片不能一一对准。
  • 瞳孔(光圈):从上面的C1C2的后半部分的描述,我们可以知道光圈的大小也会影响景深。大光圈(大瞳孔)可以营造出突出前景以及模糊背景的效果(生活中就是拍那些室内光线弱一些的场景),小光圈(小瞳孔)一般需要对图像焦距内容与背景能清晰融合,一般现实生活中拍摄户外用。瞳孔(镜头)也可以说是摄影中的:光圈,光圈就如同瞳孔,遇到弱光瞳孔会放大,接受更多的光子信息,遇到强光就瞳孔会缩小,减少接受光子,起到保护眼睛的作用,而保护眼睛的同时,这样就会对人眼影像内容的质量会有一个自动控制调节的功能:将过低,或是过高的光亮(颜色)信息自动提升或是降低亮度,看到这里,我终于知道HDR的由来了,光圈的自动放大与缩小,理解上就是HDR的处理,就是模拟类似人眼的瞳孔自动放大或是缩小的功能来提高在弱光与强光下的图像质量(提高弱光与强光的细节度,这样就相对不会太暗,或是太亮导致图像都看不清);
  • DOF:就是景深(Depth Of Field)的意思,景深的意思就是,对焦点前后两个弥散圆之间的距离,叫景深。(你可以理解就是瞳孔(或镜头)能清晰呈像的距离)

OK,了解了基本概念后,我们就在Unity中,使用后处理来模拟。

实现

但是我们的镜头,并没有说明光圈(瞳孔)此类东西,所以我也不打算真的就实打实的去按上面的方式来实现。我也不需要什么弥散圆,我就用几个参数来模拟就好了,达到差不多的效果就好了。

列举需要的参数

我总结了一下,需要下面几个参数会比较好调整景深的效果:

  • 景深对焦距离:类似概念途中的F
  • 景深值:类似概念图中的DOF

代码中就是:

[Header("Pickup DOV")]
[Range(0.01f, 100f)]
public float focusDistance = 10;                // 景深对焦距离
[Range(0.1f, 50f)]
public float depthOfField = 10f;                // 景深值

在Scene视图下显示Gizmos

OK,就这么简单,两个参数。
然后在OnDrawGizmos()函数中,将景深参数绘制出来,方便调试用,不然调起来,会相当蛋疼。
代码如下:

    private void OnDrawGizmos()
    {
        if (cam == null) cam = GetComponent<Camera>();

        // 绘制景深视锥范围
        Gizmos.color = Color.green;
        Matrix4x4 temp = Gizmos.matrix;
        Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
        var n = focusDistance - depthOfField * 0.5f;
        var f = focusDistance + depthOfField * 0.5f;
        Gizmos.DrawFrustum(Vector3.zero, cam.fieldOfView, f, n, cam.aspect);
        Gizmos.matrix = temp;
        // 绘制对焦点的圆
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position + transform.forward * focusDistance, focuseSphereSize);
        // 绘制对焦点与镜头距离:焦距
        Gizmos.color = Color.cyan;
        Gizmos.DrawLine(transform.position, transform.position + transform.forward * focusDistance);
    }

另附送绘制:相机的 正交 cube

    private void DrawCameraWireframe()
    {
        Gizmos.color = cam_frustum_color;

        Matrix4x4 temp = Gizmos.matrix;
        Gizmos.matrix = Matrix4x4.TRS(cam.transform.position, cam.transform.rotation, Vector3.one);
        if (!cam.orthographic)
        {
            // 透视视锥
            Gizmos.DrawFrustum(Vector3.zero, cam.fieldOfView, cam.farClipPlane, cam.nearClipPlane, cam.aspect);
        }
        else
        {
            // 正交 cube
            var far = cam.farClipPlane;
            var near = cam.nearClipPlane;
            var delta_fn = far - near;

            var half_height = cam.orthographicSize;
            var half_with = cam.aspect * half_height;
            var pos = Vector3.forward * (delta_fn * 0.5f + near);
            var size = new Vector3(half_with * 2, half_height * 2, delta_fn);

            Gizmos.DrawWireCube(pos, size);
        }
        Gizmos.matrix = temp;
    }

看看Scene视图下的Gizmos绘制:
在这里插入图片描述

在Shader中标出有效景深深度

如何获取深度,可以参考我之前写的一篇:Unity Shader - 获取BuiltIn深度纹理和自定义深度纹理的数据

OK,下一步,我们在Shader中,将深度值,在景深范围内的都标红色输出,否则输出绿色,如下Shader:

fixed4frag_pickupDOV (v2f_pickupDOV i) : SV_Target {
    float depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
    float halfOfDOV = _DepthOfField * 0.5;
    // 在景深内
    if ( (depth > (_FocusDistance - halfOfDOV)) && (depth < (_FocusDistance + halfOfDOV)) ) return fixed4(1, 0, 0, 1);
    // 不在景深距离
    else return fixed4(0, 1, 0, 1);
}

OK,再下一步就是,运行看看效果:
在这里插入图片描述

OK,提取景深深度值完成后,我们将将代码改回:

  • 景深内输出0,就是不模糊的地方
  • 景深外输出1,就是需要模糊的地方
fixed4 frag_pickupDOV (v2f_pickupDOV i) : SV_Target {
    float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv)) * _ProjectionParams.z;
    float halfOfDOV = _DepthOfField * 0.5;
    float focusNear = _FocusDistance - halfOfDOV;
    float focusFar = _FocusDistance + halfOfDOV;

    if ( (depth >= focusNear) && (depth <= focusFar) ) return 0; // 在景深内输出0,就是不模糊的地方
    else {
        return 1; // 在景深外输出1,就是需要模糊的地方
    }
}

运行看看效果
在这里插入图片描述

添加高斯模糊处理

有了景深深度提取后,我们添加高斯模糊来模拟景深的模糊效果。
相关文章:Unity Shader PostProcessing - 6 - GaussianBlur 高斯模糊+CommandBuffer使用做一些其他的特效

在这里插入图片描述

将提取的景深与高斯模糊图混合

景深可以提取了,高斯模糊也有了,那么就可以开始混合处理了。
如下图:
在这里插入图片描述

但是混合效果不是很好,就是因为提取的景深太多硬的边界了。
我们再添加一个平滑范围值:

    [Range(0f, 10f)]
    public float dofSmoothRange = 0.5f;                 // 景深平滑过渡范围

然后传入到Shader来平滑

        fixed4 frag_pickupDOV (v2f_pickupDOV i) : SV_Target {
            float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv)) * _ProjectionParams.z;
            float halfOfDOV = _DepthOfField * 0.5;
            float focusNear = _FocusDistance - halfOfDOV;
            float focusFar = _FocusDistance + halfOfDOV;

            if ( (depth >= focusNear) && (depth <= focusFar) ) return 0; // 在景深内输出0,就是不模糊的地方
            else {
                if (depth < focusNear) // 在景深前
                    return _DofSmoothRange == 0 ? 1 : saturate(abs(focusNear - depth) * _DofSmoothRange);
                if (depth > focusFar) // 在景深后
                    return _DofSmoothRange == 0 ? 1 : saturate(abs(depth - focusFar) * _DofSmoothRange);
                return 1; // 在景深外输出1,就是需要模糊的地方
            }
        }

在输出一下提取的景深数值:
在这里插入图片描述

再次将平滑过的景深与模糊的图片进行融合

在这里插入图片描述

这次看起来好了。

再添加一个Timeline来控制景深参数:

  • 对焦点
  • 景深范围
  • 平滑范围值

先看看提取的景深值的情况:
在这里插入图片描述

再看看正确效果:
在这里插入图片描述

最后给人物加上动画:
在这里插入图片描述

总结

总体来说,这个我自己总结的景深效果不太好,因为参数太难控制,以后等积累到一定程度,再回头看看unity内置的景深,然后再次重写。

还有,景深的效果真的非常耗性能,光模糊的采样就够多了,还有之前的提取景深呢,后面还有混合呢。。。手机端怕是要死翘翘。手机端如果要做景深,只能在特定视角下处理吧(就是DC,或是性能较高的情况下处理,或是用更加次的效果,只突出某部分就好,就是不模糊指定的部分,其他部分均值模糊一下就好了,我之前实现的一个,就可以Mask掉不需要模糊的:Unity Shader PostProcessing - 6 - GaussianBlur 高斯模糊+CommandBuffer使用做一些其他的特效,起始还有更简单的制作方式,那就是镜头剔除掉需要突出的内容,Camera.CullMask,然后渲染,再模糊,再另一个相机渲染原来不需要模糊的部分就可以了。)

Project

backup : UnityShader_DepthOfFieldTesting_2018.3.0f2

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Unity Shader是一种用于在Unity引擎中创建和控制图形渲染效果的编程语言。通过使用Unity Shader,开发人员可以自定义游戏中各种物体的外观和行为,从而实现更加逼真和出色的视觉效果。 而热图(Heatmap)是一种用于显示某个区域内物体热度分布的视觉化工具。在游戏开发中,热图通常用于统计和分析玩家在游戏中的行为和偏好,以便开发人员可以根据这些数据进行游戏优化和改进。 为了创建一个热图效果,我们可以使用Unity Shader来实现。首先,我们需要将游戏中各个物体按照玩家与其的互动情况和频率进行区分,不同的行为和频率可以对应不同的颜色或者纹理。接着,我们可以在Shader中根据这些信息来着色和渲染物体,以展示物体的热度分布。 在Shader中,我们可以通过为物体添加一张热图纹理,并使用该纹理来表示物体的热度值。热图纹理可以是一张灰度图,不同的灰度值对应不同的热度。然后,我们可以使用纹理坐标和采样操作来获取每个像素对应的热度值,并根据这些值来着色和渲染物体。 除了使用纹理来表示热度分布,我们还可以使用其他的技术和效果来增强热图的可视化效果。例如,我们可以使用颜色渐变和透明度来形成平滑的过渡效果,以更好地显示物体的热度变化。我们还可以添加动画效果,使热图效果更加生动和有趣。 总结而言,Unity Shader可以用于创建热图效果,通过着色和渲染来展示物体的热度分布。这样的热图可以帮助开发人员分析游戏中玩家的行为和偏好,从而优化和改进游戏的设计和内容。这些热图效果能够增强游戏的可视化效果,并提供有价值的数据供开发人员参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值