跳轉至內容

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 程式設計

瀏覽和下載 完整程式碼
華夏公益教科書