线性深度缓冲

时间:2017-12-13 20:54:52

标签: opengl-es webgl depth-buffer

许多人使用通常的透视矩阵和第三行,如下所示:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="Element434291_STARTTIME" value="Element434291_STARTTIME">
<input id="Element238721_STARTTIME" value="Element238721_STARTTIME">

但它在远剪裁面附近浮动精度有问题。结果是z战斗。 如何使用z的线性变换?让我们将矩阵第三行改为:

(0  0  (n+f)/(n-f) 2*n*f/(n-f))

它将是从[-n,-f]到[-1,1]的线性变换z。然后,我们将在顶点着色器中添加该行:

(0  0  -2/(f-n) (-f-n)/(f-n))

透视分割后,z值将恢复。

为什么不到处使用?我在互联网上发现了很多文章。所有这些都使用了通常的矩阵。 我描述的线性变换是否存在我看不到的问题?

更新:这不是this的重复。我的问题不是关于如何做线性深度缓冲。就我而言,缓冲区已经是线性的。我不明白,为什么这个方法没用?内部webgl管道中是否存在陷阱?

2 个答案:

答案 0 :(得分:9)

您所描述的方法根本不起作用。双曲线Z缓冲区的一个优点是我们可以在屏幕空间中插值得到的深度值线性。如果将gl_Position.z乘以gl_Position.w,则得到的z值将不再在屏幕空间中呈线性,但深度测试仍将使用线性插值。这会导致基元在z维度上弯曲,导致相邻基元之间的完全错误的遮挡和交叉(特别是如果基元的顶点位于另一个的中心附近)。

使用线性深度缓冲区的唯一方法是在片段着色器中自己对Z值进行非线性插值。这可以完成(并且可以直接将线性变换为每个片段的透视校正内插w值,因此有时称为&#34; W缓冲&#34;),但是你会失败早期Z测试的好处 - 更糟糕的是 - 层次深度测试的好处。

提高深度测试精度的一种有趣方法是将浮点缓冲区与反向Z投影矩阵结合使用,如本Depth Precision Visualized blog article中所述。

<强>更新

来自你的评论:

  

屏幕空间的深度是NDC的线性插值,我在这里理解表格。在我的例子中,它将是来自相机空间的z的线性插值的线性插值。因此,屏幕空间中的深度已经插值。

你误解了这一点。可能的主要观点是屏幕空间中的线性插值仅在 中使用已经双曲线失真的Z值(如NDC Z)时才有效。如果你想使用眼睛空间Z,这可以进行线性插值。我做了一些情况图:

enter image description here

这是关于眼睛空间和NDC的自上而下的视图。所有图纸实际上都是按比例的。绿色光线是穿过某个像素的视图光线。该像素恰好是直接表示该三角形(绿点)的中点的像素。

在应用投影矩阵并且w除以后,我们处于规范化的设备坐标中。请注意,观看光线的方向现在只是+z,并且所有像素的所有视图光线都变为平行(因此我们可以在光栅化时忽略Z)。由于z值的双曲线关系,绿点现在不再恰好位于中心,而是朝向远平面挤压。然而,重要的一点是,这一点现在位于由图元的(双曲线扭曲的)端点形成的直线上 - 因此我们可以简单地在屏幕空间中线性插值z_ndc

如果使用线性深度缓冲区,绿点现在再次位于基元中心的z处,但该点不在星际线上 - 实际上是弯曲基元。

由于深度测试将使用线性插值,因此它将获取最右边图形中的点作为顶点着色器的输入,但将线性插值 - 通过直线连接这些点。因此,基元之间的交集将不是它实际必须的位置。

想到这一点的另一种方法:想象一下你用一些透视投影绘制一些带有z维度的原始图元。由于透视,更远的东西会显得更小。因此,如果你只是在屏幕空间中向右移动一个像素,那么如果图元很远,那么该步骤覆盖的z范围实际上会更大,而随着距离越近,它将变得越来越小。因此,如果你只是向右移动相同大小的步骤,你所做的z步骤将根据基元的方向和位置而变化。但是,我们想要使用线性插值,因此我们希望为每个x步长制作相同的z步长。我们唯一要做的就是扭曲空间z在 - 并且由w除法引入的双曲线失真正是这样做的。

答案 1 :(得分:3)

我们不使用线性变换,因为它会在所有距离上同样存在精度问题。至少现在,精度问题只会出现在很远的地方,你不太可能注意到。线性映射将错误均匀地隔开,这使得错误更可能发生在靠近相机的位置。