OpenGL 编程/Glescraft 5
在第一个教程中,我们已经看到了如何渲染许多立方体。在第二个教程中,我们已经看到了如何只绘制那些立方体的可能可见的那些面。尽管如此,当所有块都被绘制时,仍然有许多三角形被处理,它们要么在屏幕外,要么背对着相机(因此将被剔除)。如果三角形在屏幕外或被剔除,则片段着色器永远不会被调用,因为没有像素需要绘制。但是,顶点着色器仍然必须处理所有这些不可见三角形的顶点。世界越大,在顶点着色器中花费的时间就越多。因此,最好只将那些实际上将被绘制到屏幕上的顶点发送到显卡。
在几乎所有大型 3D 场景中,大约一半的三角形是正面三角形,另一半是背面三角形。通过只将潜在的正面三角形发送到顶点着色器,我们可以有效地将顶点着色器中花费的时间减少一半。在我们的体素世界中,我们的立方体有六个面,每个轴两个。让我们只关注 x 轴。如果立方体的 x 坐标小于摄像机的 x 坐标,那么无论相机朝哪个方向,只有指向 x 轴正方向的立方体面才是可见的。类似地,如果立方体的 x 坐标大于摄像机的 x 坐标,则只有指向 x 轴负方向的面才是可见的。当摄像机的 x 位置“在”立方体内部时,则两个面都不可见。
但是,很难对每个单独的体素执行此操作。最好将整个块的 VBO 分成两部分;一部分用于指向 x 轴正方向的所有面,另一部分用于指向 x 轴负方向的所有面。如果相机位于块的 x 轴正方向一侧,我们只需要渲染 VBO 的第一部分,如果它位于负方向一侧,则只需要渲染第二部分。当摄像机的 x 位置“在”块内部时,我们就会遇到问题。在这种情况下,我们可以通过渲染整个 VBO 来确保安全。
我们可以对 y 和 z 轴执行相同的操作。
练习
- 为什么相机朝哪个方向并不重要?
- 为什么当相机“在”立方体内部时,两个面都不可见?
虽然原则上可以拥有视角为 360 度的相机(例如,鱼眼镜头),但大多数 3D 应用程序和游戏都限制自己使用视角仅为 90 度的相机。主要原因是我们正在将 3D 世界投影到一块至少距离我们眼睛有一定距离的平面屏幕上。我们想要塞到屏幕上的视角越宽,引入的失真就越大。如果你有一个非常大的球形屏幕或多个环绕你的屏幕,情况就会不同,但这超出了大多数人的预算。如果你只有一块屏幕,那么 90 度的视角几乎没有失真。但是,这意味着另外 270 度在屏幕之外。我们试图渲染那些在屏幕之外的东西只是在浪费时间。如果我们试图确定每个三角形的可见性,我们不妨让 GPU 为我们完成这项工作。但是,我们可以尝试确定整个块的可见性,并跳过渲染那些我们确定其任何部分都不在屏幕上的块。
要检查块的可见性,我们查看其中心的坐标。我们可以使用模型视图投影矩阵来确定中心在屏幕上的哪个位置。
glm::vec3 center; // coordinates of center of chunk
glm::mat4 mvp; // model-view-projection matrix
glm::vec4 coords = mvp * glm::vec4(center, 1);
coords.x /= coords.w;
coords.y /= coords.w;
if(coords.x < -1 || coords.x > 1 || coords.y < -1 || coords.y > 1 || coords.z < 0) {
// skip this chunk
} else {
// render this chunk
}
在将 MVP 应用于中心的坐标后,结果将以裁剪坐标形式呈现。如果我们随后将 x 和 y 坐标除以 z 坐标,则 x 和 y 值现在将以“标准化设备坐标”形式呈现,其中点 (-1, -1) 是窗口的左下角,(1, 1) 是右上角。因此,如果 x 和 y 坐标超出了该范围,则它将在窗口之外。对于 z 坐标,将其保留在裁剪坐标中更有用。z 坐标的负值表示它在相机后面,因此它也不可见。
我们在这里忽略的是,即使块的中心可能在可见窗口之外,块的某些部分仍然可能可见。为了解决这个问题,我们将使用一个安全裕量,该裕量由一个足够大的包围球的直径决定,该包围球足以容纳整个块。
float diameter = sqrtf(CX * CX + CY * CY + CZ * CZ);
if(coords.z < -diameter) {
// skip this chunk
}
diameter /= fabsf(coords.w);
if(fabsf(coords.x) > 1 + diameter || fabsf(coords.y > 1 + diameter)) {
// skip this chunk
} else {
// render this chunk
}
请注意,我们还需要将计算出的直径除以 w 坐标,使其与 x 和 y 坐标位于相同的坐标空间。
这种技术不仅可以用于跳过绘制不可见块,还可以用于仅对可见块执行其他与块相关的操作。当相机突然转向,并且场景的整个不同部分都被显示出来时,你可能需要突然更新许多 VBO。由于这可能需要相当长的时间,因此有必要每帧只更新一个或几个 VBO,这样帧率就不会下降。然后,有必要优先更新靠近摄像机的块的 VBO。我们可以通过计算以下长度轻松地确定块到摄像机的距离:coords向量
float distance = glm::length(coords);
因此,应该优先考虑距离最小的块distance.