array 发表于 2011-8-3 13:12:15

[原创翻译]恋上深度缓存

译者的话
这篇文章其实已经有些年头了,但是其中有关深度缓存的精彩论述,却依然十分值得我们借鉴。因此在这里把它翻译过来,供初学或者概念还不是特别清晰的朋友们参考;也希望抛砖引玉,能够看到更多的朋友分享自己的经验和心得体会。再者近一段因为事情繁忙和写书的任务,一直没有什么免费教程推出,在这里翻译这篇小文,聊表心意。:-)
这篇文章初看和OSG的关系不大,但实际上却涉及到OSG中一个十分重要的机制:远近平面的自动计算,较有经验的开发者应该知道,OSG采用计算节点的近似深度的方法,首先获取远平面的值(最远节点的近似深度值),然后乘以nearFarRatio来得到近平面的值——对于地球这类庞大的场景,如果nearFarRatio不够小的话,地面会在眼前被裁切;过小的话,却又会引发Z-fighting的问题。有的朋友因此对OSG抱怨连连,然而设置了DO_NOT_COMPUTE_NEAR_FAR并自己指定了看似满意的zNear和zFar之后,却依然取得不了好的结果。OSG这种方式的原因为何?出现Z-fighting的根源在哪里?期待您能够从这篇小文中找出一部分答案来。
译者水平有限,难免有错误,敬请指正。版权没有,仅在转载时注明原作者和译者为盼。 :-)

原文作者:Steve Baker
原文地址:http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html

概述
据我所知,OpenGL开发中一个最常见的问题,就是Z缓存(深度缓存)的精度问题。
大部分用于PC的早期3D显卡都支持16bit的Z缓存,有的还支持24bit——最好的那些则支持32bit。如果你能拥有32bit的Z缓存的话,Z精度对你来说可能就不是什么问题了。但是,如果你希望自己的程序有足够好的可移植性的话,你最好还是考虑一下其它的方案。
Z缓存会存在精度问题,这是因为Z缓存决定了物体之间的遮挡关系——如果你无法提供足够的精度来判断两个毗邻物体的距离的话,它们就会随机地在屏幕上交替遮挡显示——产生大的曲纹或者条带块的效果。
这就是我们通常所说的“斑驳”或者“Z-fighting”,这对于用户而言是很大的困扰。

近裁切面
我们通常会使用gluPerspective()或glFrustum()来设置近裁切面(zNear)——当然我们也可以直接设置GL_PROJECTION矩阵来完成同样的工作。
有的图形开发者管zNear叫做“hither”,管zFar叫做“yonder”。
初学者经常会把zNear设置为一个非常小的值,因为他们担心那些非常靠近人眼的多边形会被zNear裁切掉——并且看起来也没有什么特别的理由不这么做。
然而,把zNear放置的过于靠近人眼,这恰恰是斑驳现象的成因(在大多数情况下)——本文的后面部分将揭示这一事实。

深度的分辨率
人们往往会忽视这样一个事实:在几乎所有的系统中,Z缓存都是非线性的。保存在Z缓存内存中的真实数值是与物体的Z坐标相关的:
z_buffer_value = (1<<N) * ( a + b / z )
这里:N = Z的精度位数;a = zFar / ( zFar - zNear );b = zFar * zNear / ( zNear - zFar );z = 眼睛到物体的距离。而作为结果的z_buffer_value是一个整数。

这就是说,Z值(也就是精度值)与z_buffer_value的倒数成比例关系——换句话说,与眼睛的距离过近时,就算很小的距离改变都会极大地影响Z值的精度。
这种倒数关系是很有意义的,因为通常来说你需要为靠近人眼的物体显示更多的细节——因此你需要为它们提供更好的Z精度支持。
但是,一个我们不期望的后果是:有相当一部分的Z缓存的精度位数被浪费了——它们被用来存储那些过于靠近zNear却又不切实际的物体细节。如果你将近裁切面设置得更加靠近眼睛,那么会有更多的精度被用于执行渲染近处对象的工作,而带来更大的精度损耗。
事实上在大多数情况下,斑驳现象是可以被减小的——如果你能够把近裁切面设置的足够远离人眼,那你甚至能完全消除这一问题。

Z值计算器
你可以用下面的Javascript工具来检查自己的远近平面设置,以及它对Z精度的影响(请在原文网站上使用):
http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
(译者:如果你的远近平面设置不合理,那么你会发现对于一些较接近的Z distance值,得到的Value in Z Buffer是一样的——这就会在实际渲染中带来Z-fighting的问题)

后果很严重?
我们没有必要讨论一个绝对的zNear值(无论是英尺,米,光年还是埃级别的单位)会有什么问题;即使把Z值转化为保存在Z缓存中的数据之后,我们依然没有必要讨论它在OpenGL中的绝对数值。这个议题本来就没有意义。
而我们真正需要考虑的是,在一个场景中,人眼到物体的距离和人眼到zNear距离的比值关系。其公式为:
delta = z * z / ( zNear * (1<<N) - z )
这里:N = Z的精度位数;z = 眼睛到物体的距离;delta = 在这个范围内可以分辨的最小Z差值。

这是一个近似公式——它假设zNear比zFar要小得多——在实际工程中也确实如此。
从另一个角度来考虑这个公式,我们可以将其解释为:由于Z缓存的精度影响,物体与人眼之间的z值存在一个n%的误差。为了简便起见,我将结果值与zNear的关系定义为“Z*n%”或者“Zn%”。
因此,Z5%意味着在zNear所划分的范围内“Z值会有5%的误差”。
对于16bit的Z缓存来说,Z5%对应的结果为3500。这个值也会稍微受到zFar的影响,例如如果zFar的值非常小的话,这一结果会变得更大——不过对于实际工程而言,3500是一个足够好的标准了。
在实际工程中,这个值的含义是:如果你把zNear设置为1米(当然度量单位是什么都没有问题),那么对于距离眼睛3500米处的物体而言,它的Z值存在着5%的误差。

对于16bit的Z缓存而言:
Z10%   = ~8000 *
Z5%    =3500
Z1%    =   666
Z0.1%=    66
Z0.01% =   6
* 事实上可视的范围越大,zFar对于精度的影响也越大——不过对于大多数实际工程而言,它在这里带来的影响还是微乎其微的。

上面的列表告诉我们,Z1%为666,而Z10%大于8000。因此如果我们设置zNear为1米,那么在666米以内的物体,其深度精度都会高于1%,同理,3500米以内的物体深度精度高于5%,而8000米以内的深度精度高于10%。
这样的话,如果你的zNear设置为10厘米,那么你的渲染结果会在350米(3500*0.1)的距离上出现5%的精度误差——而对于3.5公里开外的物体而言,精度误差将达到大约33%——也就是说误差会高于1公里。这种情形下,几英里之外一架在高山背后飞过的飞机可能会突然出现在高山之前,错误地展现在你的视野当中!
由此可见,在16bit的深度系统中,zNear的设置将是至关重要的。
对于24bit的Z缓存来说,这个比值是之前的256倍,因此Z1%约为170000,Z5%的数值约为10万。
对于32bit的Z缓存,Z1%的值都可能达到450万左右,因此我们完全不必再关心Z5%和Z10%的结果了!

注意事项
Z精度误差还可能有其他一些成因(当然对于16bit的Z缓存而言,这些成因与Z的存储方式带来的误差相比都是微不足道的)。
有的系统支持可变的深度缓存算法。主要包括两种不同的类型:
W-buffer(也叫做OOZ-buffer,即One-Over-Z)记录了通常的Z缓存数值的倒数。它的计算稍微困难一些,不过其结果的W值是线性的,并且始终只存在一致误差。这一方案可以在大范围观测时大幅提升Z的精度,但是在物体与人眼的距离较近时,精度却反而会降低,这恰恰与我们的期望相反。有的PC显卡(例如3Dfx)支持OOZ缓存,但是对于OpenGL而言这没有什么用处。
浮点数/对数Z值(这两者之前其实很相近)。它们也是为了将Z值范围上的精度位数平均分配而产生的——但是它们不会完全改变距离人眼较近时分辨率提高的特性。SGI的Infinite Reality提供了这一功能——并且他们宣称,这个功能可以仅仅使用15位来达到与24bit系统等价的Z精度结果。

结论
尽可能地把zNear设置为远离眼睛的值,如是而已。

tianxiao888 发表于 2011-8-3 13:18:35

第一个来支持哈~~~看看先

oman 发表于 2011-8-3 13:35:51

坐个板凳,顶一个

CR苏杭 发表于 2011-8-3 14:58:11

opengl:不管你的z是啥,我的z就是标准化的0-1 float。要想精度高,就尽量缩小你要表现的你的z区间。

sky11811 发表于 2011-8-3 20:47:08

楼上精辟。

array 发表于 2011-8-4 08:08:36

opengl:不管你的z是啥,我的z就是标准化的0-1 float。要想精度高,就尽量缩小你要表现的你的z区间。
CR苏杭 发表于 2011-8-3 14:58 http://bbs.osgchina.org/images/common/back.gif

其实也不一定,OpenGL可以支持大于1的深度缓存值,具体按看osgfpdepth这个例子~~

sky11811 发表于 2011-8-4 20:21:16

这个区间可以设置。

luo9168 发表于 2011-12-23 11:31:57

读了这篇文章,感觉我太外行了。

west036 发表于 2011-12-31 12:29:19

又学习了一下,感谢Array!
我有两点疑问还没搞清楚

1. OSG中是否支持32bit的深度缓存?如果支持如何开启?

2. 如何得知我的显卡支持32bit的深度缓存?

sunnk 发表于 2012-9-27 15:08:35

学习了

meifazhu1 发表于 2016-5-14 00:53:47

本帖最后由 meifazhu1 于 2016-5-14 01:32 编辑

mark
页: [1]
查看完整版本: [原创翻译]恋上深度缓存