屏幕中每像素剪裁三角形 space
Clipping triangles in screen space per pixel
据我了解,在 OpenGL 中,多边形通常在 clip space 中被裁剪,并且只有那些三角形(或三角形的一部分,如果裁剪过程将它们分开)在与 +- w 的比较中幸存下来。然后,这需要实施多边形裁剪算法,例如 Sutherland-Hodgman。
我正在实现我自己的 CPU 光栅器,现在我想避免这样做。我有可用的顶点 NDC 坐标(不是真正的 规范化 ,因为我没有剪裁任何东西,所以位置可能不在 [-1, 1] 范围内)。我想为所有像素插入这些值,并且只绘制 NDC 坐标在 x、y 和 z 维度中落在 [-1, 1] 范围内的像素。然后我会另外执行深度测试。
这行得通吗?如果是,插值会是什么样子?我可以使用 OpenGl spec (page 427 14.9) formula for attribute interpolation as described here 吗?或者,我是否应该使用用于所有 3 个坐标的深度 (z) 插值的公式 14.10(我真的不明白为什么在那里使用不同的)?
更新:
我尝试通过两种方法对每个像素的 NDC 值进行插值:
w0, w1, w2
是顶点的重心权重。
1) float x_ndc = w0 * v0_NDC.x + w1 * v1_NDC.x + w2 * v2_NDC.x;
float y_ndc = w0 * v0_NDC.y + w1 * v1_NDC.y + w2 * v2_NDC.y;
float z_ndc = w0 * v0_NDC.z + w1 * v1_NDC.z + w2 * v2_NDC.z;
2)
float x_ndc = (w0*v0_NDC.x/v0_NDC.w + w1*v1_NDC.x/v1_NDC.w + w2*v2_NDC.x/v2_NDC.w) /
(w0/v0_NDC.w + w1/v1_NDC.w + w2/v2_NDC.w);
float y_ndc = (w0*v0_NDC.y/v0_NDC.w + w1*v1_NDC.y/v1_NDC.w + w2*v2_NDC.y/v2_NDC.w) /
(w0/v0_NDC.w + w1/w1_NDC.w + w2/v2_NDC.w);
float z_ndc = w0 * v0_NDC.z + w1 * v1_NDC.z + w2 * v2_NDC.z;
裁剪+深度测试总是这样:
if (-1.0f < z_ndc && z_ndc < 1.0f && z_ndc < currentDepth &&
1.0f < y_ndc && y_ndc < 1.0f &&
-1.0f < x_ndc && x_ndc < 1.0f)
情况 1) 对应于使用等式 14.10 进行插值。情况 2) 对应于使用等式 14.9 进行插值。
结果documented in gifs on imgur.
1) 当第二个立方体在相机后面或当我进入立方体时会发生奇怪的事情。
2) 奇怪的伪影不可见,但随着相机接近顶点,它们开始消失。因为这是 perspective correct interpolation of attributes 顶点(更靠近相机?)具有更大的权重,所以一旦顶点被剪裁,该信息就会以强权重插值到三角形像素。
这一切是意料之中还是我做错了什么?
我看不出有任何原因这行不通。但它会比传统剪辑慢很多。请注意,靠近投影中心的三角形可能会遇到麻烦,因为它们会非常小并且可能会导致重心坐标计算出现问题。
等式 14.9 和 14.10 之间的区别在于,深度基本上是 z/w(并重新映射到 [0, 1])。由于透视划分已经发生,因此必须在插值期间将其保留。
剪裁近平面并不是绝对必要的,除非三角形在相机中达到或超过 0-space Z。一旦发生这种情况,齐次坐标数学就会变得奇怪。
大多数硬件只会在三角形延伸超过剪辑外的屏幕宽度时才费心剪辑 space 或者如果它们穿过零的相机 Z。这种裁剪称为 "guard-band clipping",它节省了大量性能,因为裁剪并不便宜。
所以是的,数学可以很好地工作。在设置扫描线时,您要做的主要事情是找出每条扫描线 start/end 在屏幕上的位置。两种方式的插值数学都是相同的。
据我了解,在 OpenGL 中,多边形通常在 clip space 中被裁剪,并且只有那些三角形(或三角形的一部分,如果裁剪过程将它们分开)在与 +- w 的比较中幸存下来。然后,这需要实施多边形裁剪算法,例如 Sutherland-Hodgman。
我正在实现我自己的 CPU 光栅器,现在我想避免这样做。我有可用的顶点 NDC 坐标(不是真正的 规范化 ,因为我没有剪裁任何东西,所以位置可能不在 [-1, 1] 范围内)。我想为所有像素插入这些值,并且只绘制 NDC 坐标在 x、y 和 z 维度中落在 [-1, 1] 范围内的像素。然后我会另外执行深度测试。
这行得通吗?如果是,插值会是什么样子?我可以使用 OpenGl spec (page 427 14.9) formula for attribute interpolation as described here 吗?或者,我是否应该使用用于所有 3 个坐标的深度 (z) 插值的公式 14.10(我真的不明白为什么在那里使用不同的)?
更新: 我尝试通过两种方法对每个像素的 NDC 值进行插值:
w0, w1, w2
是顶点的重心权重。
1) float x_ndc = w0 * v0_NDC.x + w1 * v1_NDC.x + w2 * v2_NDC.x;
float y_ndc = w0 * v0_NDC.y + w1 * v1_NDC.y + w2 * v2_NDC.y;
float z_ndc = w0 * v0_NDC.z + w1 * v1_NDC.z + w2 * v2_NDC.z;
2)
float x_ndc = (w0*v0_NDC.x/v0_NDC.w + w1*v1_NDC.x/v1_NDC.w + w2*v2_NDC.x/v2_NDC.w) /
(w0/v0_NDC.w + w1/v1_NDC.w + w2/v2_NDC.w);
float y_ndc = (w0*v0_NDC.y/v0_NDC.w + w1*v1_NDC.y/v1_NDC.w + w2*v2_NDC.y/v2_NDC.w) /
(w0/v0_NDC.w + w1/w1_NDC.w + w2/v2_NDC.w);
float z_ndc = w0 * v0_NDC.z + w1 * v1_NDC.z + w2 * v2_NDC.z;
裁剪+深度测试总是这样:
if (-1.0f < z_ndc && z_ndc < 1.0f && z_ndc < currentDepth &&
1.0f < y_ndc && y_ndc < 1.0f &&
-1.0f < x_ndc && x_ndc < 1.0f)
情况 1) 对应于使用等式 14.10 进行插值。情况 2) 对应于使用等式 14.9 进行插值。
结果documented in gifs on imgur. 1) 当第二个立方体在相机后面或当我进入立方体时会发生奇怪的事情。 2) 奇怪的伪影不可见,但随着相机接近顶点,它们开始消失。因为这是 perspective correct interpolation of attributes 顶点(更靠近相机?)具有更大的权重,所以一旦顶点被剪裁,该信息就会以强权重插值到三角形像素。
这一切是意料之中还是我做错了什么?
我看不出有任何原因这行不通。但它会比传统剪辑慢很多。请注意,靠近投影中心的三角形可能会遇到麻烦,因为它们会非常小并且可能会导致重心坐标计算出现问题。
等式 14.9 和 14.10 之间的区别在于,深度基本上是 z/w(并重新映射到 [0, 1])。由于透视划分已经发生,因此必须在插值期间将其保留。
剪裁近平面并不是绝对必要的,除非三角形在相机中达到或超过 0-space Z。一旦发生这种情况,齐次坐标数学就会变得奇怪。
大多数硬件只会在三角形延伸超过剪辑外的屏幕宽度时才费心剪辑 space 或者如果它们穿过零的相机 Z。这种裁剪称为 "guard-band clipping",它节省了大量性能,因为裁剪并不便宜。
所以是的,数学可以很好地工作。在设置扫描线时,您要做的主要事情是找出每条扫描线 start/end 在屏幕上的位置。两种方式的插值数学都是相同的。