跳轉到內容

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.

< OpenGL 程式設計

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