跳转至内容

OpenGL 编程/Glescraft 2

来自维基教科书,开放的世界中的开放书籍
一个体素世界。

前面的教程中,你已经了解了如何渲染一块体素。但是,所有体素都被绘制了,即使那些不可见的体素也是如此。此外,当相邻的体素共享相同的颜色或纹理时,可以合并一些三角形。在本部分中,我们将尝试通过移除不可见的体素来减少所需的顶点数量。

移除不可见的体素面

[编辑 | 编辑源代码]

一般来说,很难确定一个三角形是否对所有可能的摄像机位置都是不可见的。然而,在我们的体素世界中,我们可以确定一个体素的一侧是不可见的,如果在该侧旁边有一个体素。如果是这种情况,我们可以省略绘制构成该体素的两个三角形。

  for(int x = 0; x < CX; x++) {
    for(int y = 0; y < CY; y++) {
      for(int z = 0; z < CZ; z++) {
        // Empty block?
        if(!blk[x][y][z])
          continue;

        // View from negative x, only draw if there is no block in front of it
        if(x > 0 && !blk[x - 1][y][z]) {
          vertex[i++] = byte4(x,     y,     z,     blk[x][y][z]);        
          vertex[i++] = byte4(x,     y,     z + 1, blk[x][y][z]);        
          vertex[i++] = byte4(x,     y + 1, z,     blk[x][y][z]);        
          vertex[i++] = byte4(x,     y + 1, z,     blk[x][y][z]);        
          vertex[i++] = byte4(x,     y,     z + 1, blk[x][y][z]);        
          vertex[i++] = byte4(x,     y + 1, z + 1, blk[x][y][z]);        
        }

        ...

注意,此实现仅检查一个块内的可见性。如果我们有多个块,那么如果x == 0,则应该检查相邻块中是否有体素可能阻挡可见性。

练习

  • 也为其他 5 个方向实现此检查。
  • 给定一个完全填充的块(所有blk[x][y][z]都非零),通过移除不可见的体素可以保存多少顶点?
  • 块中的哪些体素配置会导致最大数量的顶点?
  • 尝试使用 GL_LINES 而不是 GL_TRIANGLES 渲染块。

合并相邻面

[编辑 | 编辑源代码]

尽管人们可以想到各种合并相邻三角形的算法,但一种快速的方法是检查我们是否在同一类型的体素行中具有两个可见体素。如果为真,那么我们不是添加两个新的三角形,而是更改前两个三角形以覆盖当前体素。以下是它的实现方式

  for(int x = 0; x < CX; x++) {
    for(int y = 0; y < CY; y++) {
      bool visible = false;

      for(int z = 0; z < CZ; z++) {
        // Empty block?
        if(!blk[x][y][z]) {
          visible = false;
          continue;
        }

        // Check if we are the same type as the previous block, if so merge the triangles.
        if(visible && blk[x][y][z] == blk[x][y][z - 1]) {
          vertex[i - 5] = byte4(x,     y,     z + 1, blk[x][y][z]);        
          vertex[i - 2] = byte4(x,     y,     z + 1, blk[x][y][z]);        
          vertex[i - 1] = byte4(x,     y + 1, z + 1, blk[x][y][z]);        
        }

        else

        // View from negative x, only draw if there is no block in front of it
        if(x > 0 && !blk[x - 1][y][z]) {
          vertex[i++] = byte4(x,     y,     z,     blk[x][y][z]);        
          vertex[i++] = byte4(x,     y,     z + 1, blk[x][y][z]);        
          vertex[i++] = byte4(x,     y + 1, z,     blk[x][y][z]);        
          vertex[i++] = byte4(x,     y + 1, z,     blk[x][y][z]);        
          vertex[i++] = byte4(x,     y,     z + 1, blk[x][y][z]);        
          vertex[i++] = byte4(x,     y + 1, z + 1, blk[x][y][z]);        
          visible = true;
        } else {
          visible = false;
        }

        ...

此实现跨最内层循环(在本例中为 z)合并面。我们跟踪沿 z 轴的先前体素的可见性,并相应地合并。重要的是要注意,我们现在必须对每个面进行完全扫描,以保持我们的循环变量 "i" 准确且易于用于顶点更新。由于渲染的开销几乎完全是 GPU 绑定的,因此额外的循环不会过分阻碍我们。

标志visible跟踪先前体素是否可见。如果是,我们可以确定顶点缓冲区中的前两个三角形确实属于该体素。现在,由于我们最内层的 for 循环变量是z,我们正在查看两个仅在 z 坐标上不同的体素。因此,我们应该扩展先前体素的三个顶点(它们与当前体素相邻)以覆盖当前体素。这三个顶点是那些在其中包含 "z + 1" 的顶点。

练习

  • 也为其他 5 个方向实现此检查。
  • 使用此特定算法可以潜在节省多少顶点?
  • 此算法仅沿一个方向合并体素面。你能想到一个简单的方法来沿其他方向合并它们吗?
  • 我们也可以与相邻块的三角形合并吗?
  • 尝试使用 GL_LINES 而不是 GL_TRIANGLES 渲染块,以查看哪些面被合并。

< OpenGL 编程

浏览和下载 完整代码
华夏公益教科书