模板緩衝區的概念,至今依然不是特別理解,這篇博客寫的很好,相信對大家的理解也有幫助。
模板緩存是深度緩存的擴充,當你需要控制哪一個像素需要被渲染,哪一個像素需要被忽略時,模板緩存能夠爲你提供更多的方法。和深度緩存一樣,模板緩存存儲了所有像素的模板值,但是這次你必須手動控制這些值如何改變。記住,如果一個像素深度測試失敗了的話,模板測試將不會再決定該像素是否繪製,而像素會反過來影響模板緩存中的值。
爲了讓大家對模板緩存有一個直觀的認識,讓我們來分析一下下面這個例子:
如上圖所示:模板緩存首先被清0,我們的立方體將正常顯示,然後我們將模板緩存中的一個矩形區域設置爲1,我們的立方體在繪製時,我們將只繪製模板值爲1的像素區域,從而達到控制像素繪製與否的目的。現在你對模板緩存的作用有了一個初步瞭解了,讓我們來看看OpenGL中是如何使用它的:
和深度緩存一樣,首先你要啓動模板測試來讓模板緩存生效,
設置模板值
然後需要設置緩存中各個像素的模板值, 我們在確定模板的形狀後,我們通常只需要調用一次繪製命令就可以達到利用模板裁剪畫面的效果,如果你想像上面的例子那樣創建一個矩形的裁剪區域,只需要繪製一個2D的矩形區域就行了。只不過在你繪製之前需要使用以下幾個API來控制一下模板緩存的值:
首先是glStencilFunc,這個api是用來模板測試使用的比較函數以及參數的,參數如下:
func:比較函數
ref: 比較函數用來比較的參數值。
mask: 掩碼,如果模板緩存包含了s個位平面,那麼mask參數中較低的s個位數據將分別於模板緩存中的值,以及ref值進行位與操作,然後再進行具體的比較。
第一個參數中的比較函數可以是以下任意一種:
GL_NEVER:無論模板值爲何值,都不能通過模板測試
GL_LESS:測試模板值是否小於ref值
GL_LEQUAL:測試模板值是否小於等於ref值
GL_GREATER:測試模板值是否大於ref值
GL_GEQUAL:測試模板值是否大於等於ref值
GL_EQUAL:測試模板值是否等於ref值
GL_NOTEQUAL:測試模板值是否不等於ref值
GL_ALWAYS:無論模板值爲何值,總是能通過模板測試。
例如:如果你不想讓小於2的模板值通過模板測試,你只需要這樣調用:
然後是glStencilOp,這個api是用來指定深度測試以及模板測試以後對模板值的操作,參數如下:
sfail: 模板測試失敗後執行的操作
dpfail: 模板測試通過但深度測試不通過時候執行的操作。
dppass: 模板測試和深度測試都通過後,或者深度測試未開啓時執行的操作。
以上三個參數的值可以是以下任意一種:
GL_KEEP: 保持當前值
GL_ZERO: 將模板值清0
GL_REPLACE: 模板值將會被替換爲glStencilFunc中的ref參數值。
GL_INCR: 如果模板值小於最大值,模板值將會+1
GL_INCR_WRAP: 和GL_INCR一樣,只是遇到最大值時,模板值會清0。
GL_DECR:如果模板值大於0,模板值將會-1。
GL_DECR_WRAP: 和GL_DECR一樣,只是當模板值減到0時,會變爲最大值。
GL_INVERT: 將模板值按位取反。
最後,glStencilMask可以爲模板緩存設置掩碼,由於模板緩存中每個模板值是一個無符號的整形數,因此我們的掩碼也是一個0-0xFFFFFFFF範圍內的數,每個模板值都會與掩碼做與運算後再使用,默認掩碼爲0xFFFFFFFF。
現在回到我們的例子,我們要把一個矩形區域的模板值設置爲1,我們需要進行如下操作:
這裏我們可以看到,我們通過glStencilFunc函數讓任意模板值都能通過模板測試,這樣如果深度測試也通過的話就會根據glStencilOp的第三個參數設置的GL_REPLACE將模板值都替換爲ref參數值1,從而達到了將所有模板值設置爲1的目的。而圖中的矩形區域並不需要真正的繪製,它只需要用來指定哪些像素的模板值生效,這樣就不需要我們去逐像素的去設置模板緩存值了。
glColorMask函數允許你指定哪些數據輸入顏色緩存,4個參數分別對應RGBA值,這裏我們將4個參數都置爲GL_FALSE,意味着所有顏色數據都不會輸入到顏色緩存中。而深度緩存需要另外調用glDepthMask並傳入GL_FALSE參數來屏蔽深度值。這樣我們矩形區域外的所有像素的顏色和深度都被屏蔽了,所以我們的繪製就不會影響到這些像素。這樣操作操作比調用glClear來清除顏色和深度緩存要乾淨得多。
繪製中使用模板值
在瞭解瞭如何設置模板緩存後,使用模板緩存變得非常簡單。你只需要通過一個測試函數來決定哪些像素需要繪製,然後將之前禁用的深度和顏色重新啓用就可以了。
如果你調用這個方法來設置了測試函數,只有模板緩存中值爲1的像素能通過這個測試。一個像素只有在通過深度測試和模板測試後才能繪製,所以沒有必要去設置glStencilOp。在例子中,只有那個矩形區域中的模板值被設置爲1,所以只有在這個區域中的像素會被繪製。
一個小細節需要注意的是,我們在繪製我們的立方體時,仍會影響到模板緩存中的值,爲了避免這種影響,我們將模板緩存的掩碼設置爲0。這樣一來,我們以後的任何寫入操作都會被屏蔽掉。
鏡面反射的例子
現在我們在我們繪製的立方體下方加入一個反射鏡面,首先在立方體的頂點數組中添加如下數據:
然後,在繪製立方體過後單獨繪製這個鏡面:
現在我們創建一個反射效果,只需要將原有的立方體向下平移並相對Z軸翻轉就行了:
這裏將反射面的顏色設置爲黑色,這樣就不會有紋理圖片能夠通過這個反射面。繪製效果如下:
有兩個值得一提的地方:
1:下方的立方體被反射面遮住是因爲未能通過上一章我們提到的深度測試。
2:超出反射面範圍的立方體依然能夠被看見
第一個問題很好解決,只需要臨時屏蔽掉向深度緩存寫入值就可以了:
第二個問題通過控制深度緩存似乎不好解決,我們需要屏蔽掉反射面範圍之外的那些像素,所以我們的模板緩存就可以一展身手了!
我們可以將整個繪製過程細分爲以下幾個階段,以便我們分析每個階段應該做什麼:
1.繪製上方的立方體
2.打開模板測試,再將我們需要繪製區域的模板值寫成1。
3.繪製反射面
4.設置模板函數,讓模板值爲1的像素通過模板測試。
5.繪製一個顛倒的立方體。
6.關閉模板測試。
繪製代碼如下:
最後我們只需要將反射面上的圖像灰化,這需要在我們的片段着色器中實現,overrideColor是用來灰化圖案的顏色:
然後在繪製我們的反射立方體時傳入一個灰度值,uniColor是glGetUniformLocation的返回值:
大功告成!我們只用最基本的OpenGL的API就可以實現一個看似很複雜的效果,很神奇吧!