线性深度缓冲

lzv*_*lzv 2 opengl-es depth-buffer webgl

许多人使用通常的透视矩阵与第三行像这样:

(0  0  (n+f)/(n-f) 2*n*f/(n-f))
Run Code Online (Sandbox Code Playgroud)

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

(0  0  -2/(f-n) (-f-n)/(f-n))
Run Code Online (Sandbox Code Playgroud)

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

gl_Position.z *= gl_Position.w;
Run Code Online (Sandbox Code Playgroud)

透视分割后,z值将被恢复.

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

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

der*_*ass 9

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

使用线性深度缓冲区的唯一方法是在片段着色器中自己对Z值进行非线性插值.这可以完成(并且煮沸下来只是线性地转换每个片段的透视校正内插w值,因此它有时被称为"W缓冲"),但是你正在失去早期Z测试的好处 - 更糟糕的是 - 分层深度测试.

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

UPDATE

来自你的评论:

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

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

在此输入图像描述

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

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

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

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

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