shader复习与深入:Depth of Field(景深)
发表于2016-08-11
Depth of Field(DOF)与HDR类似,也是一种图形图像后处理手法。它渊源于物理光学中透镜成像过程中的焦散造成的前景和远景模糊化的现象,与HDR一样,都是为了增强Computer Graphics的真实感,在实时渲染中是很常见的一种技术。——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
拍摄时我们说的“景深”,一般是指能够清晰成像的距离范围。譬如相机前的4~12米这段距离内的景物最终会清晰地反映在照片上,那这段距离就被认为是“景深”,而这段距离之外的地方(我这里分别称为前景和远景)在照片上会呈现虚化的现象。Depth of Field(DOF)就是为了描述这种现象而出现的——我们的图形世界本来就是由一个虚拟的摄像机(Camera)来“捕捉”的啊。
还是使用HDR那篇文章([shader复习与深入:HDR(高动态范围)] )中使用过的场景罢,不是说近处和远处模糊些而已吗?那就在渲染出原帧图像后,再根据它另外生成一张模糊图像(嘛,DownSample、Gaussion Blur什么的,HDR也需要这么张,正好拿来用了),把这两张图像根据场景距离(像素距离)做个混合就OK了:
glsl代码 (原帧图像, fragment shader)
1 2 3 4 5 | float fDistScale = length(varying_worldpos) / 10.0; fDistScale = clamp(fDistScale * fDistScale, 0.0, 1.0); FragDataScene.a = fDistScale; //输出,用a通道存储距离因子 |
1 2 3 4 5 6 7 8 9 10 11 12 | void main( void ) { vec4 texCol = texture2D(basetex, varying_texcoord); vec4 texOriginal = texCol; //... vec4 texBlur = texture2D(bluredtex, varying_texcoord); FragDataScene = mix(texCol, texBlur, cos (6.28 * texOriginal.a) * 0.5 + 0.5); } |
这里用了个比较无趣的方法把0 → 1的距离因子映射成0→ 1→ 0的余弦参量,作为混合参数了。更适合的应该是根据场景距离让混合因子呈现更“U”型而非现在的“类V”型,以让“景深”部分有更加足够的清晰度。嘛,还可以吐槽一下这个“距离因子"——最好还是取“顶点到像平面的垂直距离”而非顶点到摄像机中心的直线距离,这里贪图方便取后者来插值出像素距离了,而且规范化的过程也很不规范(其实是很随便地根据场景大概用了个10.0的“magic number”)。嘛,意思一下咯。
好了。结束本文?还是继续唠叨吧……首先,为什么会出现Depth of Field?
在物理学的小孔成像模型中,场景中所有点产生的光束都无一例外地经过这个无限小的“孔”打到成像面上,所以视野中场景每个点都会与成像面上的某个点形成一一对应的关系,成像面图像是活生生的场景图像的倒映。小孔成像是利用了光线直线传播(传统意义下)这个原理,简单,而且理论上成像总是足够清晰的,也作为现代摄像机原理的理论基石。但是,理论与现实是有距离的,首先你就不能找到一个“无限小”的孔,能够接纳所有光线而不减弱它们的强度(不然就得非常长时间曝光以增加进入的光线)。
顺便一提的就是我们的OpenGL(当然也包括D3D了)的摄像机原理就是小孔成像,还记得那平顶锥么?摄像机原点就是那小孔了,而它的近平面位于小孔之前,是正映的成像面。正因为我们绘制的东西没所谓光强弱的概念,这种“即时成像”性使它能直接套用这个理论。所以我们无论绘制什么东西,深度无论大小,它都会成“清晰的像”。这是很好,但是就没有“缺陷美”了——我们真实使用的正常相机、乃至我们的眼球,所依据的是“透镜成像”原理,我们会有一个焦点,以及焦点范围(景深),在该范围外的物体(我们不关心的东西)会虚化……
在摄像机的透镜成像中,场景中某点产生的光线束是通过凸透镜后产生折射,再打到成像平面上的。场景中每一个点所发出的实际光线都是球式外散的,所以如果是使用有一定径度(或者说,光圈[Aperture])的透镜来就收的话,同一时间所接收的该点的光线量,相比小孔成像来说,就是很大量的了,所以收集足够光线所需要的曝光时间很短(当然也要看光圈大小了)。那这种方式造成的副作用就是我们关注的景深了:凸透镜都有焦点(位于主轴上,平行于主轴的光线都会聚在这个点上,关于物距像距和焦距的各种大小关系下的成像特点,看来得回去看高中物理了),距凸透镜中心的距离为焦距f,场景某点距凸透镜中心的距离为物距u,成像面距凸透镜中心的距离为像距v,那么就有以下这条经典公式了:
1/f = 1/u + 1/v
我们控制摄像机的参数,得到某一固定值f和某一固定值v,那么视野场景中就只有十分有限的点,其距离u能够满足上面的公式。这有什么区别呢?能满足上式的点,它发出的那些光束经过折射后就一定能汇聚到成像面上的一个固定的点了——这个成像面上的点与场景中那个点是一对一的,其光强是该点所有光束的总和,也就是说,很亮很清晰。那么场景中更多的是不满足上式的点啊!不妨设某个这样的点的物距是u1,代入上式,将有这样的结果:
1/f = 1/u1 + 1/v1
可见,这个v1不同于成像面的像距v ,所以这样的点发出的光束将不在成像面而在像距为v1的地方汇集。无论那个聚点在成像面前还是后,都会造成该点的光线在传播到成像面的时候是“散”的——形成一个小圆,就是所谓的弥散圆(Circle of Confusion, CoC)了,这样在成像面上该场景点的像不仅位置散开而且能量(亮度)也散开,造成模糊。啊,那既然除了物距恰好是u的点外的大部分场景点都会造成散焦,为什么景深是一个距离范围而不是距离值呢?——因为与u相差不太大的点,它们造成的弥散圆也是比较小的,小到甚至眼睛察觉不到它的存在,所以也被认为是清晰的。这样在u的前后就有一个范围,保证成像的清晰度——所谓的Depth of Field。
上述就是Dof的基本理论,其中有两个值得注意的地方:一个是前景、背景和景深部分的场景其实是不存在一个明显的界限的,所以实现时的模糊因子与距离的关系曲线不应该有明显的角拐点。另一个就是Circle of Confusion(CoC),这对于一个更精确的Depth of Field模型的建立来说是至关重要的。
Depth of Field,用于在计算机图形学中正是着力于去还原这种透镜成像中的“不完美”的手段。
基于CoC的Depth of Field模型
基于相似三角形和上述公式得出的CoC尺寸大小(GPU Gems 1):
DCoC = abs(Daperture * (f * (ux - u)) / (ux * (u - f)))
可以认为CoC的大小代表了该点在最终渲染输出中的模糊程度,在GPU Gems 3中有篇较为经典的文章(Practical Post-Process Depth of Field),就利用了Coc去计算前景中像素的模糊程度。为什么集中对前景执行呢?因为在前景中的像素点的光束都会在像平面后侧聚集,具有一个Upper bound,其Coc的径值的增长速度也快于背景中的像素,在距离摄像机越近,这种模糊的表现感就更容易影响最终出来的效果。在深度突变的场合(譬如前景中有一个物体,景深范围中有另一个物体,当前者对后者产生半遮挡的时候),前景的模糊不应该影响到其余的场景像素,但也不应产生过突的边缘,此文采用的是用前景像素模糊前后的CoC推算新的CoC(嘛,具体我也觉得模糊了)。
总的来说,Depth of Field,这种花费也不菲的后处理技术,更适合于某些特定场景,譬如视觉焦点需要集中在某些目标物(武器、标记等等)的时候使用。不然,这可真的是为了“瑕疵”而疯狂了吧。
本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明