【Unity3D】远处的物体会闪烁问题(深度冲突) Reversed-Z
知识点:深度冲突、像素闪烁现象、Reversed-Z(反向Z)、浮点数精度问题
前提概要:深度值都是由32位浮点数存储
原因:深度冲突,多个物体之间无法正确地渲染远近关系,出现上一帧可能是A物体在B物体前面,下一帧则反过来是B物体在A物体前面,每帧的这种远近关系情况都会变化情况下,就会产生闪烁表现。
问题1:为什么多个物体之间有明确的深度值却在计算机里无法得到正确的远近关系?
因为深度值是浮点类型float32,其浮点数记录无法保证精确度的,它仅有23个二进制有效位保存数据,8位是指数位,1位是符号位,其中23位只是小数有效位,若只谈纯整数它有24位有效位,2^24代表16777216个整数,例如:1.1111111....111 小数点后面是23个1,指数是2^23,那么就会得到 11111111...1111 (24个1)的二进制数,这个最前面的1是默认存在的(IEEE754规范)
具体浮点数精度问题解释:【Unity3d】C#浮点数丢失精度问题_c#double精度-CSDN博客
答案:由于无法有足够的精度准确表达出浮点数深度值,所以深度相近的2个物体可能会发生闪烁。
注意并不是只有远处的物体会发生闪烁,只要2个深度相近到计算机无法用浮点数精确表达的数值,那么就会发生不准确而闪烁。
问题2:那么为什么远处的物体更容易引发闪烁现象?而近处的物体只要不是靠的特别近(非重叠)都很难发生闪烁现象【本文重点】
答案:由于我们的深度值是由32位浮点数存储,浮点数的存储结构决定了它的性质是越大的浮点数它的精度越小,越小的浮点数它的精度越大;精度是指有效小数位数的数量。
为什么有这个性质?(数值越小精度越大,数值越大精度越小)
很简单的道理,当浮点数整数越大时,浮点数整数部分占据23位有效二进制位越多,自然留给浮点数小数部分的有效二进制位就越少,自然精度越小;反之当你浮点数整数越小时,整数部分占据23位有效二进制越少,自然留给小数部分的有效二进制位就越多,自然精度越大。
Reversed-Z 反向Z原理为什么可以降低发生闪烁的现象概率?
想避免远处物体闪烁,就得提高远处物体的深度值存储精度,1是用更多二进制位去存储深度,2是利用浮点数(数值越小精度越大,数值越大精度越小)性质去改善深度精度情况,采用2方案则是利用Reversed-Z技术,具体介绍参考如下文章
反向Z(Reversed-Z)的深度缓冲原理
在看完上篇文章之后大概会有个了解就是摄像机NDC归一化之后的远平面深度为0,近平面深度为1。(正常是近平面是0,远平面是1)
上面2张图,左图(Y轴是View深度,X轴是反向的NDC深度)红点代表有效数值标记,越密集代表越多有效值(精度高),反之精度低。右图(Y轴是精度程度,X轴是View深度)
无论左图还是右图都要以View Space Depth(View深度)为主轴看;
例如,左图Y轴[80,100]范围,其红色点密集,代表深度大的物体能准确表达深度;其他范围同理,然后你会发现Y轴[0,5]之类的近处小范围它的红点疏散,实际上看似很少红点,当右图统计[0,20]都有着不错的准确性,可能作者没有取更精确的刻度,或许[0,5]的精度会变少,但反向Z技术保证了[5,100]的范围精度是可靠的,即保证大部分深度范围的物体不会闪烁,小部分范围闪烁,相比较之前的情况要好。
原文将near和far数值继续拉大到10000单位,主要看右图,远处的精度变小了,近处的精度明显变大,但整体上来看,中、远距离的物体精度仍然有所保证,它们的远近关系仍然可以比较不错的表达出来,而降低闪烁现象。
下面这张是未使用反向Z之前的效果,可看到远处物体的精度几乎是接近0,代表远处物体的深度无法正常准确表达,会大概率发生闪烁现象。
可以尝试复刻原文,画出更精确刻度的右图来获得反向Z后的不同距离的物体深度精度情况。
注意:OpenGL NDC是(-1,1)范围的,所以无法利用浮点数性质,因为远平面和近平面都是趋近于1,而非0,也就只有中间的物体它的精度值是很高的...尬住了
好像Unity在DirectX平台下默认帮我们处理Reversed-Z,但OpenGL则不会了,具体如何避免闪烁,大多数做法就是把Near拉大,就能改善不少,缺点是摄像机到近平面距离的物体看不到了。其次就是隐藏或裁剪掉远距离的物体了,好像没啥办法。