一.简介
景深一直是我最喜欢的效果之一,最早接触CE3的时候,发现CE引擎默认就支持景深的效果,当时感觉这个效果特别酷炫,如今投身于Unity的怀抱中,准备用Unity实现以下传说中的景深效果。
所谓景深,是摄影的一个专业术语:在聚焦完成后,在焦点前后的范围内都能形成清晰的像,这一前一后的距离范围,便叫做景深,也是被摄物体能清晰成像的空间深度。在景深范围内景物影像的清晰度并不完全一致,其中焦点上的清晰度是最高的,其余的影像清晰度随着它与焦点的距离成正比例下降。先附上一张正常的照片和使用景深控制的照片:
通过左右两张照片的对比,我们很容易发现,通过景深处理的照片,我们可以很容易地抓住照片的重点部分。这也就是景深最大的用处,能够突出主题,并且可以使画面更有层次感。
在摄影技术中的景深,是通过调整相机的焦距,光圈来控制景深的,这里就不多说了。而我们的游戏中要想出现这种效果,就需要下一番功夫了。首先拆分一下图像的效果,图像中主要分为两部分,后面的模糊背景和前面清晰的“主题”部分。后面的背景模糊我们可以通过前面的两篇文章Unity Shader-后处理:高斯模糊,Unity Shader后处理-均值模糊来实现,而前景部分就是一张清晰的场景图,最后通过一定的权值将两者混合,离摄像机(准确地说是焦距)越远的部分,模糊图片的权重越高,离摄像机越近的部分,清晰图片的权重越高。那么问题来了,我们怎么知道哪个部分离摄像机更近呢?
二.Camera Depth Texture
上面说到,我们要怎么得到摄像机渲染的这个场景的图片中哪个部分离我们更远,哪个部分离我们更近?答案就是Camera Depth Texture这个东东。从字面上理解这个就是相机深度纹理。在Unity中,相机可以产生深度,深度+法线或者运动方向纹理,这是一个简化版本的G-Buffer纹理,我们可以用这个纹理进行一些后处理操作。这张纹理图记录了屏幕空间所有物体距离相机的距离。深度纹理中每个像素所记录的深度值是从0 到1 非线性分布的。精度通常是 24 位或16 位,这主要取决于所使用的深度缓冲区。当读取深度纹理时,我们可以得到一个0-1范围内的高精度值。如果你需要获取到达相机的距离或者其他线性关系的值,那么你需要手动计算它。
关于这张图是怎么样产生的,在Unity5之前是通过一个叫做Shader ReplaceMent的操作完成的。这个操作简单来说就是临时把场景中所有的shader换成我们想要的shader,然后渲染到张图上,我们通过Shader ReplaceMent操作,将场景中的对象shader换成按照深度写入一张纹理图。我们可以在5.X版本之前Unity自带的Shader中找到这个生成深度图的shader:Camera-DepthTexture.shader,这里我摘出一小段Tags中RenderType为Opaque的subshader:
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : POSITION;
#ifdef UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE
float2 depth : TEXCOORD0;
#endif
};
v2f vert( appdata_base v )
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
UNITY_TRANSFER_DEPTH(o.depth);
return o;
}
fixed4 frag(v2f i) : COLOR {
UNITY_OUTPUT_DEPTH(i.depth);
}
ENDCG
}
}
我们看到,当物体的渲染Tag为Opaque也就是不透明的时候,会写入深度纹理。而这个文件中其他的几个subshader也分别对针对不同类型的type,比如RenderType为TransparentCutout的subshader,就增加了一句下面的判断,去掉了所有应该透明的地方:
clip( texcol.a*_Color.a - _Cutoff );
而且这个shader中没有出现RnderType为Transparent类型的,因为透明的物体不会写入我们的深度贴图,也就是说我们开启了alpha blend类型的对象是不会写入深度的。
上面的代码中有几个宏定义,我们可以从UnityCG.cginc文件中找到这几个宏定义的实现:
// Depth render texture helpers
#if defined(UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE)
#define UNITY_TRANSFER_DEPTH(oo) oo = o.pos.zw
#define UNITY_OUTPUT_DEPTH(i) return i.x/i.y
#else
#define UNITY_TRANSFER_DEPTH(oo)
#define UNITY_OUTPUT_DEPTH(i) return 0
#endif
结合上面sha