跳轉到內容

OpenGL 程式設計/模板緩衝區

來自 Wikibooks,開放世界中的開放書籍

當您使用 OpenGL 繪製內容時,您會在螢幕上看到顏色,但請記住,除了顏色緩衝區之外還有其他緩衝區。您已經熟悉深度緩衝區,它可以防止背景畫素在存在更近畫素的情況下顯示。現在是介紹模板緩衝區的時候了。

模板緩衝區是另一個供您自定義使用的緩衝區:您可以在其中儲存每個畫素的資訊,並指示 OpenGL 根據這些資訊採取不同的操作。

要正確操作模板緩衝區,需要理解一些要點,所以讓我們花點時間學習一下。

位平面

[編輯 | 編輯原始碼]

這主要是一個詞彙問題:每個畫素的資訊都是一些位平面 - 即位。

例如,通常有 8 個位平面,這意味著螢幕上的每個畫素都關聯一個位元組,您可以儲存最多 個不同的值。您可以使用 glGetIntegerv(GL_STENCIL_BITS, &i) 檢查位平面的數量。

測試和操作

[編輯 | 編輯原始碼]

可以使用 glStencilFuncglStencilOp 操作模板緩衝區:glStencilFunc 指定要應用於模板緩衝區每個畫素的測試,然後 glStencilOp 指定根據測試結果要執行的操作。

glStencilFunc 採用 3 個引數,構建以下測試 (ref & mask) OP (stencil & mask)

  • OP:GL_NEVER、GL_ALWAYS、GL_EQUAL、GL_NOTEQUAL、GL_LESS、GL_LEQUAL、GL_GEQUAL、GL_GREATER 之一
  • ref:比較中使用的固定整數
  • mask:應用於 ref 和模板畫素的掩碼;如果使用 8 個位平面,可以使用 0xFF 停用掩碼

glStencilOp 採用 3 個引數

  • sfail:來自 glStencilFunc 的測試失敗
  • dpfail:來自 glStencilFunc 的測試透過,但深度緩衝區測試失敗
  • dppass:來自 glStencilFunc 的測試透過,並且深度緩衝區透過或已停用

這 3 個引數中的每一個都是要對模板緩衝區執行的操作,其中之一是 GL_KEEP、GL_ZERO、GL_REPLACE、GL_INCR、GL_INCR_WRAP、GL_DECR、GL_DECR_WRAP、GL_INVERT(預設值是 GL_KEEP)。

因此,我們看到兩個不同的測試是連結的,導致了3 種情況

  • 首先是 glStencilFunc,如果它失敗,則應用 sfail 並停止
  • 然後是深度測試(如果深度緩衝區可用且已啟用),如果它失敗,則執行 dpfail 並停止
  • 如果兩個測試都透過,則在顏色緩衝區中評估片段著色器並應用 dppass

請注意,使用 sfaildpfail 時,顏色緩衝區保持不變。

如果您繪製一個體積形狀,請記住,OpenGL 可能會多次繪製同一個模板畫素,具體取決於重疊三角形的順序和/或深度緩衝區的可用性;在這種情況下,您的操作將被執行多次。

旋轉立方體,被裁剪

在我們的示例中,我們將繪製一個移動的圓圈,場景將被裁剪在圓圈內。

/* main */
  glutInitDisplayMode(GLUT_RGBA|GLUT_ALPHA|GLUT_DOUBLE|GLUT_DEPTH|GLUT_STENCIL);
/* onDisplay */
  glClear(GL_DEPTH_BUFFER_BIT);
  glEnable(GL_STENCIL_TEST);
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  glDepthMask(GL_FALSE);
  glStencilFunc(GL_NEVER, 1, 0xFF);
  glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);  // draw 1s on test fail (always)

  // draw stencil pattern
  glStencilMask(0xFF);
  glClear(GL_STENCIL_BUFFER_BIT);  // needs mask=0xFF
  draw_circle();

  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glDepthMask(GL_TRUE);
  glStencilMask(0x00);
  // draw where stencil's value is 0
  glStencilFunc(GL_EQUAL, 0, 0xFF);
  /* (nothing to draw) */
  // draw only where stencil's value is 1
  glStencilFunc(GL_EQUAL, 1, 0xFF);

  draw_scene();

  glDisable(GL_STENCIL_TEST);

draw_scene教程 05 中繪製彩色立方體,而 draw_circle2D 模式 繪製一個圓圈。我們不會討論這些函式,因為它們與模板功能無關,但您可以檢視原始碼(請參閱頁面底部的連結)。

請注意,無需使用單獨的程式在模板緩衝區中繪製。在這種情況下是有意義的,因為我們繪製了 2D 模式,但完全可以在同一個場景中繪製 3D 模式,就像在 Mini-Portal 教程中一樣。

專門處理顏色、深度和/或模板緩衝區

[編輯 | 編輯原始碼]

在示例中,我們看到首先我們專門在模板緩衝區上繪製模板形狀,然後專門在顏色緩衝區上使用它作為遮罩。

當您想修改模板緩衝區而不修改顏色緩衝區和/或深度緩衝區時,您可以遮蔽這些緩衝區

  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  glDepthMask(GL_FALSE);

不要忘記設定它們。您可以透過這種方式儲存以前的值

  GLboolean save_color_mask[4];
  GLboolean save_depth_mask;
  glGetBooleanv(GL_COLOR_WRITEMASK, save_color_mask);
  glGetBooleanv(GL_DEPTH_WRITEMASK, &save_depth_mask);

  /* Do something */

  glColorMask(save_color_mask[0], save_color_mask[1], save_color_mask[2], save_color_mask[3]);
  glDepthMask(save_depth_mask);

如果您想在顏色緩衝區上應用更改而不修改模板緩衝區,您可以

  • 設定 glStencilMask(0x00) - 這意味著在任何情況下都不會寫入任何內容
  • 設定 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) - 這將對所有情況應用一個無操作

在操作模板緩衝區時很難理解問題所在,因為我們沒有直接檢視它的方法。

如果您使用的是非 ES OpenGL,您可以呼叫 glReadPixels 獲取整個模板緩衝區並進行外部檢查,但這很繁瑣。

另一種方法是用兩個三角形填充螢幕,並進行模板測試(例如,只匹配非零模板畫素)。您可能需要在執行此操作時啟用/停用顏色緩衝區。

然後交換 OpenGL 顏色緩衝區並暫停,以便您可以直觀地檢查結果

      glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
      glClear(GL_COLOR_BUFFER_BIT);
      glStencilMask(0x00);
      glStencilFunc(GL_LEQUAL, 1, 0xFF);
      fill_screen();
      glutSwapBuffers();
      cout << "swap" << endl;
      sleep(1);
      glStencilMask(0xFF);
      glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

您的 fill_screen() 函式最好使用一個專用的程式,該程式不包含任何 MVP 矩陣,儘管您可以重用現有程式並傳遞 v=glm::mat4(1)m=glm::inverse(projection)

我們看到 glStencilOp 中有三個可能的動作

  • sfail
  • dpfail
  • dppass

使用 sfail 操作在模板緩衝區上表達您的操作確實違反直覺,因為存在雙重否定(不是“當此條件滿足時執行此操作”,而是“當此條件不滿足時不要執行此操作”。

但是,如果您使用 dppass 來表達您的操作,請記住,OpenGL 會檢查模板和深度測試,還會使用片段著色器計算畫素值。如果您只是在模板緩衝區中繪製一個形狀,這將是一個巨大的效能損失 - 因為您根本不需要呼叫片段著色器。

建議是首先使用 dppass 實現您的繪製演算法以確保清晰,並在它正常工作後,反轉 glStencilFunc 中的條件並使用 sfailsfail 在測試失敗時不會嘗試計算畫素片段,因此速度更快。

示例

  • 首先使用
    glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);  // +1 when current pixel != 0
  • 然後最佳化
    glStencilFunc(GL_EQUAL, 0, 0xFF);
    glStencilOp(GL_INCR, GL_KEEP, GL_KEEP);  // +1 when !(current pixel == 0)

OpenGL 4.2 核心配置檔案規範提到

但是,當深度和模板附件都存在時,實現只需要支援兩個附件都引用同一個影像的幀緩衝區物件。 [1]

這意味著您可能無法將兩個渲染緩衝區附加到同一個幀緩衝區。

特別是在 OpenGL ES 2.0 中,組合格式在 glRenderbufferStorage 中不受支援 (GL_FRAMEBUFFER_UNSUPPORTED)(通常不支援 DEPTH32F_STENCIL8DEPTH24_STENCIL8,只支援 DEPTH_COMPONENT16STENCIL_INDEX8)。

如果您需要結合使用 後期處理 和模板化,則需要避免使用幀緩衝區物件,而應使用 glCopyTexSubImage2D 技術。

參考文獻

[編輯 | 編輯原始碼]
  1. "OpenGL 圖形系統:規範 - 版本 4.2(核心配置檔案)" (PDF). Khronos.org. 2011-08-22. 檢索於 2011-10-05.

< OpenGL 程式設計

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