OpenGL 程式設計/模板緩衝區
當您使用 OpenGL 繪製內容時,您會在螢幕上看到顏色,但請記住,除了顏色緩衝區之外還有其他緩衝區。您已經熟悉深度緩衝區,它可以防止背景畫素在存在更近畫素的情況下顯示。現在是介紹模板緩衝區的時候了。
模板緩衝區是另一個供您自定義使用的緩衝區:您可以在其中儲存每個畫素的資訊,並指示 OpenGL 根據這些資訊採取不同的操作。
要正確操作模板緩衝區,需要理解一些要點,所以讓我們花點時間學習一下。
這主要是一個詞彙問題:每個畫素的資訊都是一些位平面 - 即位。
例如,通常有 8 個位平面,這意味著螢幕上的每個畫素都關聯一個位元組,您可以儲存最多 個不同的值。您可以使用 glGetIntegerv(GL_STENCIL_BITS, &i) 檢查位平面的數量。
可以使用 glStencilFunc 和 glStencilOp 操作模板緩衝區: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
請注意,使用 sfail 和 dpfail 時,顏色緩衝區保持不變。
如果您繪製一個體積形狀,請記住,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_circle 以 2D 模式 繪製一個圓圈。我們不會討論這些函式,因為它們與模板功能無關,但您可以檢視原始碼(請參閱頁面底部的連結)。
請注意,無需使用單獨的程式在模板緩衝區中繪製。在這種情況下是有意義的,因為我們繪製了 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 中的條件並使用 sfail。sfail 在測試失敗時不會嘗試計算畫素片段,因此速度更快。
示例
- 首先使用
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_STENCIL8 或 DEPTH24_STENCIL8,只支援 DEPTH_COMPONENT16 和 STENCIL_INDEX8)。
如果您需要結合使用 後期處理 和模板化,則需要避免使用幀緩衝區物件,而應使用 glCopyTexSubImage2D 技術。
- ↑ "OpenGL 圖形系統:規範 - 版本 4.2(核心配置檔案)" (PDF). Khronos.org. 2011-08-22. 檢索於 2011-10-05.