一、从透镜说起
透镜是一种将光线聚合或分散的设备,透镜一般分为两类,中间厚边缘薄的叫凸透镜,中间薄边缘后的叫凹透镜;也可以根据光线折射的类型分为汇聚透镜,发散透镜;
通常,透镜是有两个面的(面镜只有一个面),因此可以再细分为双凸透镜,双凹透镜,平凸透镜,平凹透镜,凹凸透镜,双月透镜(两个面的曲率相同)等。透镜导致的光学折射现象非常多,我们自己也可以尝试去实现一些透镜效果,如放大镜效果、近视眼镜效果、鱼眼效果、雨滴的折射等;
光心:透镜的物理中心,通常经过光心处的光线我们会假设它不会改变方向
光轴:透镜前后表面的球面中心点的连线称为透镜的光轴
凸透镜,一束光以平行于光轴的方向照射后会在另一侧集中于一点,这一点就是焦点,该点到镜心的距离就是焦距。
成像、物距、焦距、实像、虚像、对焦、失焦等简单概念,可自行查询,https://baike.baidu.com/item/%E5%87%B8%E9%80%8F%E9%95%9C%E6%88%90%E5%83%8F%E8%A7%84%E5%BE%8B
凸透镜成像:光线经过透镜后汇聚在一点,一个物体的所有光线经过透镜后就形成了一个实像(也可能是虚像,这里只考虑实像情况);
但是需要注意的是,只有焦平面上的点才能映射到像平面上,而像平面通常就是胶片的位置,如下图所示,P点作为焦平面上的点是可以被清晰成像的;
但是,对于不在焦平面上的点,它的光线不能汇聚成一点,因此就会出现模糊的现象,这就是失焦,和焦平面距离越远的点失焦情况就越严重;这里焦平面到光心的距离就是物距,而像平面到光心的距离就是像距。焦距和像距之间有一个固定的关系(,其中u为物距,v为像距,f为焦距;),通常所说的调焦也就是调整两者大小来进行对焦。
二、计算机中的景深
在计算机图形学中,我们使用的虚拟相机通常是类似于针孔相机的,特点就是镜头尺寸为零,光从场景到胶片的路径只有一条。这种情况下就不会出现景深的效果,因此要模拟这种效果,就需要为数字相机加入更多的特性。
如下两张图是对景深模糊一个比较直观的解释,
景深的定义和相关参数
景深是指相机对焦点前后相对清晰的成像范围。在光学中,尤其是录像或是摄影,景深用于描述在空间中,可以清楚成像的距离范围。虽然透镜只能够将光聚到某一固定的距离,远离此点则会逐渐模糊,但是在某一段特定的距离内,图像模糊的程度是肉眼无法察觉的,这段距离称之为景深。
景深通常由物距、镜头焦距,以及镜头的光圈值所决定(相对于焦距的光圈大小)。除了在近距离时,一般来说景深是由物体的放大率以及透镜的光圈值决定。固定光圈值时,增加放大率,不论是更靠近拍摄物或是使用长焦距的镜头,都会减少景深的距离;减少放大率时,则会增加景深。如果固定放大率时,增加光圈值(缩小光圈)则会增加景深;减小光圈值(增大光圈)则会减少景深。
https://photography.tutsplus.com/articles/understanding-the-factors-that-affect-depth-of-field--photo-6844
景深和表现效果
景深越大,在景深范围内的物体就越多,模糊效果就越不明显;相反,景深越小,聚焦效果就越明显,模糊效果也就越明显;
景深的实现技术
分层景深:将对象基于深度分为若干个层,模糊每个层以后将这些图层合成一张图像;这种方式的实现比较简单,但是会产生很多伪像。
前向映射Z-Buffer:这个术语我也翻译不准确,但是是景深实现中比较通用的技术,也是本文后面重点介绍的,它的基本思路是:基于深度图来计算每个像素的CoC值,通过CoC值来判定焦点区域以及焦外区域,对焦外区域做散景模糊(一种图像模糊算法);
三、景深的具体实现
catlikecoding教程中实现了一个和unity后处理插件中相似的DOF效果,这里贴出原文以及一篇译文供学习参考;
https://catlikecoding.com/unity/tutorials/advanced-rendering/depth-of-field/
https://ak-techartist.github.io/2018/04/25/depth-of-field/
这里会大概总结一下实现的关键步骤以及为了优化效果等解决的关键问题;
1.CoC的处理
首先拿到深度值,然后设定一个焦距以及景深范围;这里把所有的值截取到-1和1,小于0为前景,大于0为背景;
float coc = (depth - _FocusDistance) / _FocusRange;
coc = clamp(coc, -1, 1);
2. 焦外效果
焦外效果主要就是一个模糊效果,所以在文章中花了很大篇幅讲述它是如何一点点得到一个不错的散景效果的;
在做模糊效果时,卷积核的大小以及迭代次数都会对性能有明显消耗,这里为了兼顾性能和效果使用的是一个自定义的卷积核,并且对图像提前做了降采样(通过降采样再升采样可以模糊,并且降采样可以减少散景模糊的计算消耗);
3.对焦
有了焦外效果,这是就需要根据CoC值来决定各个像素的效果显示了,通过将CoC的值和卷积核半径的值来进行比较,来判断某一点对当前像素的影响,这里主要的一个优化是,对于是否受到某一点的影响不是简单的取舍,而是采用了权重计算的方式,这样能得到一个更平滑的效果;
另外,由于我们的模糊是全局模糊了,会导致焦点内的像素也是模糊的,因此需要通过source纹理和dof纹理(焦外模糊的纹理)做一个混合来做一个锐化的效果;如下,使用coc值的绝对值来做一个插值计算:
half4 source = tex2D(_MainTex, i.uv);
half coc = tex2D(_CoCTex, i.uv).r;
half4 dof = tex2D(_DoFTex, i.uv);half dofStrength = smoothstep(0.1, 1, abs(coc));
half3 color = lerp(source.rgb, dof.rgb, dofStrength);
return half4(color, source.a );
4.前景和后景分开处理
上述方式会有一个严重的问题,如下所示,我们可以看到在箭头所指处,也就是一个焦外前景在一个焦点背景之上的时候,由于这时的CoC值还是背景的CoC值(因为CoC纹理并没有做这种模糊,模糊效果只是焦外自己处理的),那么本来应该前景模糊在背景之上,这里就出现了问题;
这个效果不正确的主要原因不在于那个混合因子,而是我们的焦外模糊的计算是有问题的,我们在之前的计算中忽略了前景模糊应该覆盖背景这一特点,而是将前景模糊和背景模糊的卷积计算混在了一起;因此,解决这个问题的思路就是区分前景和背景,然后在合并前景和背景时采取前景覆盖背景的方式即可。
四、参考
GPU Gems:景深的一些实现技术
高品质后处理:十种图像模糊算法的总结与实现