基於Qt的OpenGL程式設計(3.x以上GLSL可程式設計管線版)---(十九)模板測試
(Vries的教程是我看過的最好的可程式設計管線OpenGL教程,沒有之一,其原地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/02%20Stencil%20testing/ 關於模板測試的詳細知識瞭解請看原教程,本篇旨在對Vires基於visual studio平臺的程式設計思想與c++程式碼做純Qt平臺的移植,重在記錄自身學習之用)
程式原始碼連結:https://pan.baidu.com/s/1-oSUhlUAreL89e4Kr1w4rg 提取碼:c1g4
編譯環境:Qt5.9
編譯器:MSVC
IDE:QtCreator
一.箱子輪廓
Vries在上節深度測試用的的箱子場景基礎上,繼續引用了模板測試,準確來講,片段的丟棄順序應該是先判斷模板測試,在判斷深度測試,只有這些測試都通過的片段,才能被最終渲染到視窗。
開啟線框模式,可以看出箱子輪廓是一個略大於箱子的正方體。
按照正常的深度測試來說,體積略大的輪廓正方體包住了箱子正方體,顯示出的結果應該是圖3這樣,全是深綠色。而不是圖3圖4兩圖結合起來成為圖1所示的那種感覺。圖1這樣的效果都是因為用了模板測試的功勞。
1.1 模板測試
模板測試簡單來說像是一個挑揀工具,也是根據一個緩衝來選擇視口的某片段是否通過。這個緩衝叫做模板緩衝,在一個模板緩衝中,每個片段的模板值預設是8位的,即具有256個不同的模板值,根據某片段的某模板值與模板緩衝中對應位置的模板值對比,我們可以選擇該片段是通過還是丟棄。
在上圖這個例子裡,我們認定模板值為“1”的片段通過測試,為“0”的片段丟棄。
通過
glEnable(GL_STENCIL_TEST);
開啟模板測試,在這行程式碼之後,預設情況下,所有繪製的物體都會以某種值的方式寫進模板緩衝,該值暫時未知,不知是否為0。
這裡,Vries的教程裡多了一句,
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
幀的每次迭代繪製,均需清理模板緩衝中的模板值,但因為QOpenGLWidget中paintGL()函式的特殊機制,他會自動清除每種
1.2 模板函式
(1) glStencilMask(Gluint mask)
在glEnable(GL_STENCIL_TEST)開啟後,用於控制物體的片段是否寫進模板緩衝,mask作用在於設定一個掩碼,與將寫入模板緩衝的模板值做與(And)運算,常用的mask有兩個值:
glStencilMask(0xFF); //And運算之後,寫入模板緩衝的模板值不變,允許片段模板值寫入模板緩衝
glStencilMask(0x00); //And運算之後,寫入模板緩衝的模板值會是0,也就是禁用寫入的意思。
(2) glStencilFunc(GLenum func, GLint ref, GLuint mask)
模板緩衝的條件比較函式,決定具有某模板值的片段在該比較函式條件下是否可以通過模板測試。
- func 模板測試函式,有八種可用選項GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL and GL_ALWAYS.其中,預設值為GL_ALWAYS
- ref 制定了模板測試的參考值,被限制在一定範圍之內。
- mask 為指定一個掩碼,在ref值與模板緩衝中已儲存的值比較之前,分別於兩個值做與運算。初始化所有位為1。(這句翻譯的很彆扭,英文原文為 specifies a mask that is
AND
ed with both the reference value and the stored stencil value before the test compares them. Initially set to all1
s.)
舉例說明:
glStencilFunc(GL_EQUAL, 1, 0xFF); //這句程式碼意思為模板值為1的片段可以通過模板測試,其他模板值的片段被丟棄。
(3) glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
模板測試功能函式,確定了在何種情況下可以對模板緩衝內容做更改,即何時更新模板緩衝內容。
有三個引數:
- sfail, 模板測試測試失敗時應採取的行為
- dpfail, 模板測試通過,但深度測試失敗時應採取的行為
- dppass,模板測試與深度測試都通過時,應採取的行為
每一種選項的行為有以下選擇
預設情況下為glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP),即無論測試是否通過,均不會對模板緩衝內容做更改,這條規則下的模板測試,無論是否開啟,視口最終渲染出的內容都是一樣的。
1.3箱子輪廓程式碼詳解
文章一開始描述的箱子輪廓場景程式碼,其關鍵部分程式碼比較複雜,需要解釋一下。
簡要分為以下幾步:
- 開啟模板緩衝,規定緩衝替換條件glStencilOp
- 關閉寫進緩衝,畫地板
- 開啟寫進緩衝,畫兩個箱子
- 關閉寫進緩衝,畫稍大的箱子輪廓
- 開啟寫進緩衝
core->glEnable(GL_STENCIL_TEST); //一經開啟,預設向模板緩衝裡寫緩衝
core->glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); //規定所有測試都通過後,使用ref值寫進模板緩衝。
/********** 畫地板 ************/
core->glStencilMask(0x00); //禁止將地板的片段寫進模板緩衝
ResourceManager::getShader("plane").use();
core->glActiveTexture(GL_TEXTURE0);
ResourceManager::getTexture("metal").bind();
plane->draw(GL_TRUE);
/********** 畫兩個箱子,並將其片段的模板值模板緩衝裡,即模板緩衝只允許這兩個箱子所佔的區域是“1” ************/
core->glStencilFunc(GL_ALWAYS, 1, 0xFF); //允許以下繪製的所有片段以“1”的值寫進緩衝
core->glStencilMask(0xFF);//可以寫進模板緩衝
QMatrix4x4 model;
model.translate(-1.0f, 0.0001f, -1.0f);
ResourceManager::getShader("cube").use().setMatrix4f("model", model);
core->glActiveTexture(GL_TEXTURE0);
ResourceManager::getTexture("marble").bind();
cube->draw(GL_TRUE);
model.setToIdentity();
model.translate(2.0f, 0.0001f, 0.0f);
ResourceManager::getShader("cube").use().setMatrix4f("model", model);
cube->draw(GL_TRUE);
/********** 畫箱子的輪廓,輪廓比箱子稍大,因為箱子所佔的模板緩衝值是“1”, 所以在不允許在“1”的地方畫輪廓 ************/
core->glStencilMask(0x00);//關閉寫進模板緩衝,現在模板緩衝裡只有兩個箱子的緩衝值是“1”
core->glDisable(GL_DEPTH_TEST); //關掉深度測試保證了箱子的輪廓是完整的
core->glStencilFunc(GL_NOTEQUAL, 1, 0xFF); //規定在模板緩衝中“1”所佔的位置,片段不允許通過
model.setToIdentity();
model.translate(-1.0f, 0.0001f, -1.0f);
model.scale(1.1f);
ResourceManager::getShader("cube_SingleColor").use().setMatrix4f("model", model);
cube->draw();
model.setToIdentity();
model.translate(2.0f, 0.0001f, 0.0f);
model.scale(1.1f);
ResourceManager::getShader("cube_SingleColor").use().setMatrix4f("model", model);
cube->draw();
core->glStencilMask(0xFF);//重新開啟寫進模板緩衝
core->glStencilFunc(GL_ALWAYS, 1, 0xFF); //因為glClear()函式並不能清除掉模板緩衝,所以我們需要還原緩衝模板緩衝功能函式glStencilFunc
core->glEnable(GL_DEPTH_TEST); //重新開啟深度測試