跳轉到內容

OpenGL 程式設計/科學 OpenGL 教程 05

來自華夏公益教科書,開放的書籍,開放的世界
隱藏線消除

上一個教程 中,我們使用網格線繪製了三維圖形。這使您可以透檢視形,但對於更復雜的函式,這可能相當令人困惑。將圖形繪製為連續的、不透明的表面會更理想。在本教程中,我們將瞭解如何做到這一點。

正面和背面

[編輯 | 編輯原始碼]

由於我們的圖形不是封閉表面,而是一個帶有凸起的片狀物體,因此根據方向,有可能看到圖形的兩個側面。當 OpenGL 繪製三角形時,它會自動確定三角形的哪個側面朝向相機,即正面或背面(另請參閱 雙面表面 GLSL 教程)。我們將稍微更改上一個教程中的片段著色器,以使朝向背面的三角形僅繪製為正面三角形亮度的一半,從而可以輕鬆區分我們正在檢視圖形的哪一側。我們還將介紹統一顏色,以便我們可以根據繪製的是表面還是網格來調節顏色。

#version 120

varying vec4 graph_coord;
uniform vec4 color;

void main(void) {
  float factor;

  if(gl_FrontFacing)
          factor = 1.0;
  else
          factor = 0.5;

  gl_FragColor = (graph_coord / 2.0 + 0.5) * color * factor;
}

繪製表面

[編輯 | 編輯原始碼]

在上一個教程中,我們建立了一個 VBO,它包含圖形的所有 x 和 y 座標。我們還建立了一個 IBO,它追蹤水平和垂直網格線。要繪製表面,我們將重用 VBO,但必須建立一個新的 IBO 來描述如何繪製組成圖形表面的三角形。

GLushort indices[100 * 100 * 6];
int i = 0;

// Triangles
for(int y = 0; y < 100; y++) {
  for(int x = 0; x < 100; x++) {
    indices[i++] = y * 101 + x;
    indices[i++] = y * 101 + x + 1;
    indices[i++] = (y + 1) * 101 + x + 1;

    indices[i++] = y * 101 + x;
    indices[i++] = (y + 1) * 101 + x + 1;
    indices[i++] = (y + 1) * 101 + x;
  }
}

GLuint surface_ibo;
glGenBuffers(1, &surface_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surface_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof indices, indices, GL_STATIC_DRAW);

要使用新著色器繪製表面,我們使用以下命令。

GLfloat white[4] = {1, 1, 1, 1};
glUniform4fv(uniform_color, 1, white);

glEnableVertexAttribArray(attribute_coord2d);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(attribute_coord2d, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surface_ibo);
glDrawElements(GL_TRIANGLES, 100 * 100 * 6, GL_UNSIGNED_SHORT, 0);

如果這樣做,並且旋轉圖形,您會注意到,根據方向,表面並不總是正確繪製。在最遠端的三角形首先繪製的方向上,沒有問題。但是,當最靠近端的三角形首先繪製時,更靠後的三角形可能會覆蓋最靠近端的三角形。為了防止這種情況發生,當然我們需要啟用深度緩衝區。在main()函式中,使用

glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);

並且在init_resources()中新增

glEnable(GL_DEPTH_TEST);

也不要忘記在繪製新幀之前清除深度緩衝區。

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

練習

  • 我們不能簡單地反轉繪製三角形的順序嗎?也許在glVertexAttribPointer()呼叫中使用一個負步長引數?
  • 嘗試以各種方式更改三角形中頂點的順序。
  • 嘗試透過使用 glEnable(GL_CULL_FACE) 和 glCullFace(GL_FRONT_AND_BACK) 來獲得相同的結果,而無需指定背面三角形。

在表面上繪製網格

[編輯 | 編輯原始碼]

雖然表面有其吸引力,但更難看到細微的曲線。在表面上繪製網格會很不錯。我們已經知道如何做到這一點,因此我們只需切換回網格 IBO,並在繪製完表面後使用 GL_LINES 進行繪製。但是,我們必須為網格線指定與表面不同的顏色。讓我們使它們比表面亮兩倍。

GLfloat bright[4] = {2, 2, 2, 1};
glUniform4fv(uniform_color, 1, bright);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glDrawElements(GL_LINES, 100 * 101 * 4, GL_UNSIGNED_SHORT, 0);

當您這樣做時,您會注意到兩件事。網格線看起來很糟糕,但您再也看不到“隱藏的”網格線了。這是因為深度測試預設情況下會確保僅繪製比該位置任何現有片段更靠近相機的片段。表面首先繪製,因此不會繪製任何位於其後面的片段。但是,如果網格線使用與表面相同的頂點,您會期望深度完全相同。使用預設深度測試,您會認為根本不應該出現任何網格線。實際上,線條的繪製方式與三角形不同,浮點舍入誤差會導致一些網格片段略微位於表面前面,而一些略微位於表面後面。

要修復網格線,它們應該比表面更靠近相機,或者表面應該比表面更遠。我們可以透過對 MVP 矩陣應用平移來做到這一點,但 OpenGL 中有一個專門解決此問題的呼叫。

glPolygonOffset(1, 1);
glEnable(GL_POLYGON_OFFSET_FILL);

這將導致三角形(但不是線條或點)以略微增加的深度值繪製,結果是網格線現在將按預期出現。

練習

  • 使用 F4 鍵使您可以開啟和關閉多邊形偏移。
  • 預設深度測試函式為“小於”(GL_LESS)。嘗試使用glDepthFunc(GL_LEQUAL)將其更改為“小於或等於”。這有幫助嗎?
  • 嘗試不同的glPolygonOffset()值,更大或負數。
  • 在繪製表面和網格之間,嘗試清除深度緩衝區或顏色緩衝區。
  • 是否可以在繪製表面之前繪製網格?

< OpenGL 程式設計

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