最近看了Crazyjumper(文章地址)的博客裏的一篇文章,爲了學習和記錄特寫此文,如有不妥之處請指出學習,謝謝!
模板測試首先,模板測試需要一個模板緩衝區,這個緩衝區是在初始化OpenGL時指定的。如果使用GLUT工具包,可以在調用glutInitDisplayMode函數時在參數中加上GLUT_STENCIL,例如:
在Windows操作系統中,即使沒有明確要求使用模板緩衝區,有時候也會分配模板緩衝區。但爲了保證程序的通用性,最好還是明確指定使用模板緩衝區。如果確實沒有分配模板緩衝區,則所有進行模板測試的像素全部都會通過測試。通過glEnable/glDisable可以啓用或禁用模板測試。
OpenGL在模板緩衝區中爲每個像素保存了一個“模板值”,當像素需要進行模板測試時,將設定的模板參考值與該像素的“模板值”進行比較,符合條件的通過測試,不符合條件的則被丟棄,不進行繪製。條件的設置與Alpha測試中的條件設置相似。但注意Alpha測試中是用浮點數來進行比較,而模板測試則是用整數來進行比較。比較也有八種情況:始終通過、始終不通過、大於則通過、小於則通過、大於等於則通過、小於等於則通過、等於則通過、不等於則通過。
這段代碼設置模板測試的條件爲:“小於3則通過”。glStencilFunc的前兩個參數意義與glAlphaFunc的兩個參數類似,第三個參數的意義爲:如果進行比較,則只比較mask中二進制爲1的位。例如,某個像素模板值爲5(二進制101),而mask的二進制值爲00000011,因爲只比較最後兩位,5的最後兩位爲01,其實是小於3的,因此會通過測試。如何設置像素的“模板值”呢?glClear函數可以將所有像素的模板值復位。代碼如下:
可以同時復位顏色值和模板值:
正如可以使用glClearColor函數來指定清空屏幕後的顏色那樣,也可以使用glClearStencil函數來指定復位後的“模板值”。每個像素的“模板值”會根據模板測試的結果和深度測試的結果而進行改變。
該函數指定了三種情況下“模板值”該如何變化。第一個參數表示模板測試未通過時該如何變化;第二個參數表示模板測試通過,但深度測試未通過時該如何變化;第三個參數表示模板測試和深度測試均通過時該如何變化。如果沒有起用模板測試,則認爲模板測試總是通過;如果沒有啓用深度測試,則認爲深度測試總是通過)變化可以是:GL_KEEP(不改變,這也是默認值),GL_ZERO(回零),GL_REPLACE(使用測試條件中的設定值來代替當前模板值),GL_INCR(增加1,但如果已經是最大值,則保持不變),GL_INCR_WRAP(增加1,但如果已經是最大值,則從零重新開始),GL_DECR(減少1,但如果已經是零,則保持不變),GL_DECR_WRAP(減少1,但如果已經是零,則重新設置爲最大值),GL_INVERT(按位取反)。在新版本的OpenGL中,允許爲多邊形的正面和背面使用不同的模板測試條件和模板值改變方式,於是就有了glStencilFuncSeparate函數和glStencilOpSeparate函數。這兩個函數分別與glStencilFunc和glStencilOp類似,只在最前面多了一個參數face,用於指定當前設置的是哪個面。可以選擇GL_FRONT, GL_BACK, GL_FRONT_AND_BACK。注意:模板緩衝區與深度緩衝區有一點不同。無論是否啓用深度測試,當有像素被繪製時,總會重新設置該像素的深度值(除非設置glDepthMask(GL_FALSE);)。而模板測試如果不啓用,則像素的模板值會保持不變,只有啓用模板測試時纔有可能修改像素的模板值。(這一結論是我自己的實驗得出的,暫時沒發現什麼資料上是這樣寫。如果有不正確的地方,歡迎指正)另外,模板測試雖然是從OpenGL 1.0就開始提供的功能,但是對於個人計算機而言,硬件實現模板測試的似乎並不多,很多計算機系統直接使用CPU運算來完成模板測試。因此在一些老的顯卡,或者是多數集成顯卡上,大量而頻繁的使用模板測試可能造成程序運行效率低下。即使是當前配置比較高端的個人計算機,也儘量不要使用glStencilFuncSeparate和glStencilOpSeparate函數。從前面所講可以知道,使用剪裁測試可以把繪製區域限制在一個矩形的區域內。但如果需要把繪製區域限制在一個不規則的區域內,則需要使用模板測試。
我們仍然來看一個實際的例子。這是一個比較簡單的場景:空間中有一個球體,一個平面鏡。我們站在某個特殊的觀察點,可以看到球體在平面鏡中的鏡像,並且鏡像處於平面鏡的邊緣,有一部分因爲平面鏡大小的限制,而無法顯示出來。整個場景的效果如下圖:代碼如下:
#include <gl/glut.h>
#define WIDTH 400
#define HEIGHT 400
//渲染前的初始化
void Initialize()
{
glClearColor(0.0f, 0.0f, 0.0f , 1.0f);
glShadeModel (GL_SMOOTH);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
GLfloat ambient[]={0.2f, 0.2f, 0.2f, 1.0f};
GLfloat light_position[] = {3.0f, 0.0f, -1.0f, 1.0f};
GLfloat light_diffuse[] = {0.5f, 0.5f, 1.0f, 1.0f};//一般漫反射光值與反射光值相同,畢竟是屬於同一光源是吧
GLfloat light_specular[] = {0.5f, 0.5f, 1.0f, 1.0f};
//設置光位置和屬性
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
//選擇光照模型
glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);//默認
glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
//啓用光照
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);//開啓0號光源
}
//重置渲染窗口和視圖
void Reshape(int width, int height)
{
// 創建透視效果視圖
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,4.828f,1000.0f);
glViewport(0, 0, width, height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(7, 0, 6, 0, 0,0, 0, 1, 0);
}
void DrawSphere() //畫球
{
glPushMatrix();
glTranslatef(0.0f, 0.0f, 2.0f);
glutSolidSphere(0.5, 30, 30);
glPopMatrix();
}
void Display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawSphere(); //畫鏡外球
glDisable(GL_LIGHTING);
glClearStencil(1); //設置清空模版緩存的後默認值爲0
glClear(GL_STENCIL_BUFFER_BIT); //清空模版緩存
glStencilFunc(GL_ALWAYS, 2, 0xFF); //模版測試總是通過
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); //使得之後經測試的像素點的模版值都爲1
glEnable(GL_STENCIL_TEST); //開啓模版測試
glDepthMask(GL_FALSE); //將深度緩衝設爲只讀,使得之後畫的圖形無Z關係,只存在重疊關係
glRectf(-2.0f, 3.0f, 2.0f, -3.0f); //畫鏡面
glDepthMask(GL_TRUE); //將深度緩衝設爲可寫
glEnable(GL_LIGHTING);
glStencilFunc(GL_EQUAL, 2, 0xFF); //將模版測試的條件設置爲模版值等與1的可通過,其餘的都放棄
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glPushMatrix();
glScaled(1, 1, -1); //關於Y軸對稱
DrawSphere(); //畫鏡中球
glPopMatrix();
glDisable(GL_STENCIL_TEST); //關閉模板測試,防止鏡外球被過濾
glutSwapBuffers();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE|GLUT_STENCIL);
glutInitWindowPosition(200, 200);
glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow("OpenGL模版緩存演示");
Initialize();
glutReshapeFunc(&Reshape);
glutDisplayFunc(&Display);
glutMainLoop();//進入渲染和消息循環
return 0;
}
代碼模版測試過程示意圖(個人理解):