跳轉到內容

OpenGL 程式設計/迷你門戶平滑

來自華夏公益教科書,開放世界的開放書籍

(進行中)

上一節 中,我們實現了一個基本的執行門戶系統。

一個令人討厭的問題是我們穿過門戶時遇到的閃爍。讓我們看看如何解決它。

攝像機視錐體

為了理解閃爍,我們需要了解 OpenGL 攝像機的工作原理。

當我們使用它的變換矩陣來定位攝像機時,我們實際上並沒有定位螢幕本身。正如我們在圖中看到的,OpenGL 攝像機是一個截斷的錐體(在數學行話中稱為視錐體)。我們在 onIdle 函式中設定投影矩陣時使用 glm::perspective 定義了它。

  // Projection
  glm::mat4 camera2screen = glm::perspective(
    45.0f,                            // FOVy
    1.0f*screen_width/screen_height,  // aspect ratio
    0.1f,                             // zNear
    100.0f                            // zFar
  );

透視矩陣將 zNearzFar 之間的所有物體投影到近裁剪平面。它不會顯示攝像機位置和近裁剪平面之間的任何內容。

當攝像機與門戶的距離小於 zNear 時,就會出現閃爍:在這種情況下,攝像機位置在門戶之前,但螢幕已經在門戶之後!閃爍實際上是在我們傳送攝像機之前,快速閃過門戶後面的場景。

直觀的思路是提前傳送:如果攝像機距離門戶 zNear 很近,我們可以立即將它傳送。當面對門戶時,這很好用 - 但是如果我們橫向(側向移動)穿過門戶呢?在這種情況下,問題將無法解決。

真正的問題主要出在模板緩衝區,因為模板緩衝區定義了繪製的門戶場景部分。模板緩衝區是透過將門戶渲染到其中構建的,因此會受到攝像機近裁剪平面移到門戶之外的影響。如果我們可以在攝像機近裁剪平面在門戶後面時繼續在模板緩衝區上繪製,我們就贏了。

我們在本文件中提出的解決方案是使門戶具有體積,與攝像機位置和近裁剪平面之間的體積相匹配。當近裁剪平面透過門戶正面時,一個定製的背面將出現並被渲染到模板緩衝區。向所有方向。

基本上,這都是關於作弊;但我們從一開始就在作弊 :)

修改 zNear

[編輯 | 編輯原始碼]

仍然存在一個細微的問題:即使近裁剪平面在門戶體積形狀內,也可能在門戶內部存在物體,通常是地面。

更完整的解決方案是透過計算門戶體積和攝像機近裁剪矩形之間的交集來更精確地繪製模板緩衝區。

但通常情況下,你會將門戶應用於牆壁,因此在門戶後面不應該有任何東西。

在我們的演示中,可以使用 zNear 的合理較小值(例如 0.01)來解決地面問題。

/* Global */
static float zNear = 0.01;
static float fovy = 45;

使用此值,只有當玩家在看腳的同時穿過門戶時,才能稍微注意到地面。

zNear 設定為非常小的值(例如 0.000001)很誘人。但是,如果 zNearzFar 相比太小,深度緩衝區將失去其精度。然後你會注意到你的網格中的閃爍,因為三角形將交替地彼此繪製。所以不要 :)

修改 zNear 在開發我們的修復時會很方便,因為我們可以將其設定為 1.0 等較大的值,以更清晰地檢視近裁剪平面的延期。

繪製體積門戶

[編輯 | 編輯原始碼]

待辦事項:解釋數學

void create_portal(Mesh* portal, int screen_width, int screen_height, float zNear, float fovy) {
  portal->vertices.clear();
  portal->elements.clear();

  float aspect = 1.0 * screen_width / screen_height;
  float fovy_rad = fovy * M_PI / 180;
  float fovx_rad = fovy_rad / aspect;
  float dz = max(zNear/cos(fovx_rad), zNear/cos(fovy_rad));
  float dx = tan(fovx_rad) * dz;
  float dy = tan(fovy_rad) * dz;
  glm::vec4 portal_vertices[] = {
    glm::vec4(-1, -1, 0, 1),
    glm::vec4( 1, -1, 0, 1),
    glm::vec4(-1,  1, 0, 1),
    glm::vec4( 1,  1, 0, 1),

    glm::vec4(-(1+dx), -(1+dy), 0-dz, 1),
    glm::vec4( (1+dx), -(1+dy), 0-dz, 1),
    glm::vec4(-(1+dx),  (1+dy), 0-dz, 1),
    glm::vec4( (1+dx),  (1+dy), 0-dz, 1),
  };
  for (unsigned int i = 0; i < sizeof(portal_vertices)/sizeof(portal_vertices[0]); i++) {
    portal->vertices.push_back(portal_vertices[i]);
  }
  GLushort portal_elements[] = {
    0,1,2, 2,1,3,
    4,5,6, 6,5,7,
    0,4,2, 2,4,6,
    5,1,7, 7,1,3,
  };
  for (unsigned int i = 0; i < sizeof(portal_elements)/sizeof(portal_elements[0]); i++) {
    portal->elements.push_back(portal_elements[i]);
  }
}

我們看到門戶依賴於螢幕寬度和螢幕高度(用於透視縱橫比)。因此,當 OpenGL 視窗調整大小時,我們需要重建門戶形狀 - 讓我們修改 GLUT 的reshape 回撥

void onReshape(int width, int height) {
  screen_width = width;
  screen_height = height;
  glViewport(0, 0, screen_width, screen_height);
  create_portal(&portals[0], screen_width, screen_height, zNear, fovy);
  create_portal(&portals[1], screen_width, screen_height, zNear, fovy);
}

< OpenGL 程式設計

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