线性深度缓冲区
Linear depth buffer
许多人使用通常的第三行透视矩阵是这样的:
(0 0 (n+f)/(n-f) 2*n*f/(n-f))
但是它在远裁剪面附近的浮动精度有问题。结果是 z-fighting。
如何使用 z 的线性变换?让我们把矩阵第三行改成这样:
(0 0 -2/(f-n) (-f-n)/(f-n))
就是z从[-n, -f]到[-1, 1]的线性变换。然后,我们将在顶点着色器中添加行:
gl_Position.z *= gl_Position.w;
透视分割后z值会恢复。
为什么不到处使用它?我在互联网上找到了很多文章。他们都使用了一个普通的矩阵。
我描述的线性变换是否存在我没有看到的问题?
更新:这不是 的副本。我的问题不是关于如何做线性深度缓冲。就我而言,缓冲区已经是线性的。不明白,为什么不用这个方法?内部 webgl 管道中是否存在陷阱?
我们不使用线性变换,因为这会在所有距离 上同样存在精度问题。至少现在,精度问题只会出现在很远的地方,你不太可能注意到。线性映射将错误均匀分布,这使得错误更容易发生在靠近相机的地方。
您描述的方法根本行不通。双曲线 Z 缓冲区的一个优点是我们可以在屏幕 space 中 线性 插入生成的深度值。如果将 gl_Position.z
乘以 gl_Position.w
,得到的 z 值在屏幕 space 中将不再是线性的,但深度测试仍将使用线性插值。这会导致您的图元在 z 维度上发生弯曲,从而导致附近图元之间完全错误的遮挡和交叉(尤其是当一个图元的顶点位于另一个图元的中心附近时)。
使用线性深度缓冲区的唯一方法是在片段着色器中实际对 Z 值进行非线性插值。这是可以做到的(归结为只是线性变换每个片段的透视校正插值 w 值,因此它有时被称为“W 缓冲”),但是你正在失去早期 Z 测试的好处 - 更糟- 层次深度测试。
一种提高深度测试精度的有趣方法是结合使用浮点缓冲区和反向 Z 投影矩阵,如本 Depth Precision Visualized blog article 中所述。
更新
来自您的评论:
Depth in screen space is linear interpolation of NDC, how I understand form here. In my case, it will be linear interpolation of linear interpolation of z from camera space. Thus, depth in screen space interpolated already.
你误会了。可能的要点是屏幕 space 中的线性插值仅在 如果 您使用的 Z 值已经双曲线扭曲(如 NDC Z)时才有效。如果要用eye-space Z,这个可以不线性插值。我画了一些情况图:
这是 eye-space 和 NDC 的俯视图。所有图纸实际上都是按比例绘制的。绿色光线是穿过某个像素的视线。这个像素恰好是直接代表那个三角形(绿点)的中点的像素。
应用投影矩阵并除以 w
后,我们处于归一化设备坐标中。请注意,现在视线的方向只是 +z
,并且所有像素的所有视线都变得平行(因此我们可以在光栅化时忽略 Z)。由于 z 值的双曲线关系,绿点现在不再正好位于中心,而是被挤压到远平面。然而,重要的一点是这个点现在位于由图元的(双曲线扭曲的)端点形成的直线上 - 因此我们可以简单地在屏幕 space.[=19 中线性插值 z_ndc
=]
如果您使用线性深度缓冲区,绿点现在再次位于基元中心的 z 处,但该点不在直线上 - 您实际上弯曲了基元。
由于深度测试将使用线性插值,它只会从顶点着色器获取最右边绘图中的点作为输入,但会对其进行线性插值 - 用直线连接这些点。因此,基元之间的交集将不会在实际需要的地方。
另一种思考方式:假设您绘制了一些延伸到 z 维度的图元,并带有一些透视投影。由于透视,距离较远的东西会显得较小。因此,如果您只是在屏幕 space 中向右移动一个像素,则该步骤所覆盖的 z 范围实际上会在图元距离较远时变大,而随着距离的接近,它会变得越来越小。因此,如果您只是以相同大小的步长向右移动,则您所做的 z 步长将根据基元的方向和位置而有所不同。然而,我们想要使用线性插值,所以我们想要为每个 x 步设置相同的 z 步长。我们唯一要做的就是扭曲 space z 在 - 中,除以 w
引入的双曲线失真正是这样做的。
许多人使用通常的第三行透视矩阵是这样的:
(0 0 (n+f)/(n-f) 2*n*f/(n-f))
但是它在远裁剪面附近的浮动精度有问题。结果是 z-fighting。 如何使用 z 的线性变换?让我们把矩阵第三行改成这样:
(0 0 -2/(f-n) (-f-n)/(f-n))
就是z从[-n, -f]到[-1, 1]的线性变换。然后,我们将在顶点着色器中添加行:
gl_Position.z *= gl_Position.w;
透视分割后z值会恢复。
为什么不到处使用它?我在互联网上找到了很多文章。他们都使用了一个普通的矩阵。 我描述的线性变换是否存在我没有看到的问题?
更新:这不是
我们不使用线性变换,因为这会在所有距离 上同样存在精度问题。至少现在,精度问题只会出现在很远的地方,你不太可能注意到。线性映射将错误均匀分布,这使得错误更容易发生在靠近相机的地方。
您描述的方法根本行不通。双曲线 Z 缓冲区的一个优点是我们可以在屏幕 space 中 线性 插入生成的深度值。如果将 gl_Position.z
乘以 gl_Position.w
,得到的 z 值在屏幕 space 中将不再是线性的,但深度测试仍将使用线性插值。这会导致您的图元在 z 维度上发生弯曲,从而导致附近图元之间完全错误的遮挡和交叉(尤其是当一个图元的顶点位于另一个图元的中心附近时)。
使用线性深度缓冲区的唯一方法是在片段着色器中实际对 Z 值进行非线性插值。这是可以做到的(归结为只是线性变换每个片段的透视校正插值 w 值,因此它有时被称为“W 缓冲”),但是你正在失去早期 Z 测试的好处 - 更糟- 层次深度测试。
一种提高深度测试精度的有趣方法是结合使用浮点缓冲区和反向 Z 投影矩阵,如本 Depth Precision Visualized blog article 中所述。
更新
来自您的评论:
Depth in screen space is linear interpolation of NDC, how I understand form here. In my case, it will be linear interpolation of linear interpolation of z from camera space. Thus, depth in screen space interpolated already.
你误会了。可能的要点是屏幕 space 中的线性插值仅在 如果 您使用的 Z 值已经双曲线扭曲(如 NDC Z)时才有效。如果要用eye-space Z,这个可以不线性插值。我画了一些情况图:
这是 eye-space 和 NDC 的俯视图。所有图纸实际上都是按比例绘制的。绿色光线是穿过某个像素的视线。这个像素恰好是直接代表那个三角形(绿点)的中点的像素。
应用投影矩阵并除以 w
后,我们处于归一化设备坐标中。请注意,现在视线的方向只是 +z
,并且所有像素的所有视线都变得平行(因此我们可以在光栅化时忽略 Z)。由于 z 值的双曲线关系,绿点现在不再正好位于中心,而是被挤压到远平面。然而,重要的一点是这个点现在位于由图元的(双曲线扭曲的)端点形成的直线上 - 因此我们可以简单地在屏幕 space.[=19 中线性插值 z_ndc
=]
如果您使用线性深度缓冲区,绿点现在再次位于基元中心的 z 处,但该点不在直线上 - 您实际上弯曲了基元。
由于深度测试将使用线性插值,它只会从顶点着色器获取最右边绘图中的点作为输入,但会对其进行线性插值 - 用直线连接这些点。因此,基元之间的交集将不会在实际需要的地方。
另一种思考方式:假设您绘制了一些延伸到 z 维度的图元,并带有一些透视投影。由于透视,距离较远的东西会显得较小。因此,如果您只是在屏幕 space 中向右移动一个像素,则该步骤所覆盖的 z 范围实际上会在图元距离较远时变大,而随着距离的接近,它会变得越来越小。因此,如果您只是以相同大小的步长向右移动,则您所做的 z 步长将根据基元的方向和位置而有所不同。然而,我们想要使用线性插值,所以我们想要为每个 x 步设置相同的 z 步长。我们唯一要做的就是扭曲 space z 在 - 中,除以 w
引入的双曲线失真正是这样做的。