概述
文章目录
- 基本概念
- 实现
- 列举需要的参数
- 在Scene视图下显示Gizmos
- 在Shader中标出有效景深深度
- 添加高斯模糊处理
- 将提取的景深与高斯模糊图混合
- 再次将平滑过的景深与模糊的图片进行融合
- 总结
- Project
基本概念
在百度百科里有解析:景深。
引用其简短描述的一段话:
景深(DOF),是指在摄影机镜头或其他成像器前沿能够取得清晰图像的成像所测定的被摄物体前后距离范围。光圈、镜头、及焦平面到拍摄物的距离是影响景深的重要因素。
在聚焦完成后,焦点前后的范围内所呈现的清晰图像的距离,这一前一后的范围,便叫做景深。
在镜头前方(焦点的前、后)有一段一定长度的空间,当被摄物体位于这段空间内时,其在底片上的成像恰位于同一个弥散圆之间。被摄体所在的这段空间的长度,就叫景深。换言之,在这段空间内的被摄体,其呈现在底片面的影象模糊度,都在容许弥散圆的限定范围内,这段空间的长度就是景深。
可以看看里面介绍的视频,说得挺简洁的,我总结为下图:
影响景深的参数,我来说明一下:(上图DOV是我在作画时写错的,应该是DOF)
- F:是对焦点。
- C1和C2:是对焦点与瞳孔(或镜头)连接直线上,在焦点前后两个深蓝色的圆圈,叫:弥散圆。如果弥散圆大小超出了瞳孔(或镜头)能识别的范围,图像就会模糊,就像对焦的内容与相机底片不能一一对准。
- 瞳孔(光圈):从上面的C1和C2的后半部分的描述,我们可以知道光圈的大小也会影响景深。大光圈(大瞳孔)可以营造出突出前景以及模糊背景的效果(生活中就是拍那些室内光线弱一些的场景),小光圈(小瞳孔)一般需要对图像焦距内容与背景能清晰融合,一般现实生活中拍摄户外用。瞳孔(镜头)也可以说是摄影中的:光圈,光圈就如同瞳孔,遇到弱光瞳孔会放大,接受更多的光子信息,遇到强光就瞳孔会缩小,减少接受光子,起到保护眼睛的作用,而保护眼睛的同时,这样就会对人眼影像内容的质量会有一个自动控制调节的功能:将过低,或是过高的光亮(颜色)信息自动提升或是降低亮度,看到这里,我终于知道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
最后
以上就是震动金毛为你收集整理的Unity Shader PostProcessing - 7 - DepthOfField(DOF)景深基本概念实现总结Project的全部内容,希望文章能够帮你解决Unity Shader PostProcessing - 7 - DepthOfField(DOF)景深基本概念实现总结Project所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复