1. 程式人生 > >OpenGL入門學習(十二) 【轉】

OpenGL入門學習(十二) 【轉】

 

片斷測試其實就是測試每一個畫素,只有通過測試的畫素才會被繪製,沒有通過測試的畫素則不進行繪製。OpenGL提供了多種測試操作,利用這些操作可以實現一些特殊的效果。
我們在前面的課程中,曾經提到了“深度測試”的概念,它在繪製三維場景的時候特別有用。在不使用深度測試的時候,如果我們先繪製一個距離較近的物體,再繪製距離較遠的物體,則距離遠的物體因為後繪製,會把距離近的物體覆蓋掉,這樣的效果並不是我們所希望的。
如果使用了深度測試,則情況就會有所不同:每當一個畫素被繪製,OpenGL就記錄這個畫素的“深度”(深度可以理解為:該畫素距離觀察者的距離。深度值越大,表示距離越遠),如果有新的畫素即將覆蓋原來的畫素時,深度測試會檢查新的深度是否會比原來的深度值小。如果是,則覆蓋畫素,繪製成功;如果不是,則不會覆蓋原來的畫素,繪製被取消。這樣一來,即使我們先繪製比較近的物體,再繪製比較遠的物體,則遠的物體也不會覆蓋近的物體了。
實際上,只要存在深度緩衝區,無論是否啟用深度測試,OpenGL在畫素被繪製時都會嘗試將深度資料寫入到緩衝區內,除非呼叫了glDepthMask(GL_FALSE)來禁止寫入。這些深度資料除了用於常規的測試外,還可以有一些有趣的用途,比如繪製陰影等等。

除了深度測試,OpenGL還提供了剪裁測試、Alpha測試和模板測試。

1、剪裁測試
剪裁測試用於限制繪製區域。我們可以指定一個矩形的剪裁視窗,當啟用剪裁測試後,只有在這個視窗之內的畫素才能被繪製,其它畫素則會被丟棄。換句話說,無論怎麼繪製,剪裁視窗以外的畫素將不會被修改。
有的朋友可能玩過《魔獸爭霸3》這款遊戲。遊戲時如果選中一個士兵,則畫面下方的一個方框內就會出現該士兵的頭像。為了保證該頭像無論如何繪製都不會越界而覆蓋到外面的畫素,就可以使用剪裁測試。

可以通過下面的程式碼來啟用或禁用剪裁測試:

glEnable(GL_SCISSOR_TEST);   // 啟用剪裁測試
glDisable(GL_SCISSOR_TEST); // 禁用剪裁測試



可以通過下面的程式碼來指定一個位置在(x, y),寬度為width,高度為height的剪裁視窗。

glScissor(x, y, width, height);


注意,OpenGL視窗座標是以左下角為(0, 0),右上角為(width, height)的,這與Windows系統視窗有所不同。

還有一種方法可以保證畫素只繪製到某一個特定的矩形區域內,這就是視口變換(在第五課第3節中有介紹)。但視口變換和剪裁測試是不同的。視口變換是將所有內容縮放到合適的大小後,放到一個矩形的區域內;而剪裁測試不會進行縮放,超出矩形範圍的畫素直接忽略掉。
2、Alpha測試
在前面的課程中,我們知道畫素的Alpha值可以用於混合操作。其實Alpha值還有一個用途,這就是Alpha測試。當每個畫素即將繪製時,如果啟動了Alpha測試,OpenGL會檢查畫素的Alpha值,只有Alpha值滿足條件的畫素才會進行繪製(嚴格的說,滿足條件的畫素會通過本項測試,進行下一種測試,只有所有測試都通過,才能進行繪製),不滿足條件的則不進行繪製。這個“條件”可以是:始終通過(預設情況)、始終不通過、大於設定值則通過、小於設定值則通過、等於設定值則通過、大於等於設定值則通過、小於等於設定值則通過、不等於設定值則通過。
如果我們需要繪製一幅圖片,而這幅圖片的某些部分又是透明的(想象一下,你先繪製一幅相片,然後繪製一個相框,則相框這幅圖片有很多地方都是透明的,這樣就可以透過相框看到下面的照片),這時可以使用Alpha測試。將圖片中所有需要透明的地方的Alpha值設定為0.0,不需要透明的地方Alpha值設定為1.0,然後設定Alpha測試的通過條件為:“大於0.5則通過”,這樣便能達到目的。當然也可以設定需要透明的地方Alpha值為1.0,不需要透明的地方Alpha值設定為0.0,然後設定條件為“小於0.5則通過”。Alpha測試的設定方式往往不只一種,可以根據個人喜好和實際情況需要進行選擇。

可以通過下面的程式碼來啟用或禁用Alpha測試:

glEnable(GL_ALPHA_TEST);   // 啟用Alpha測試
glDisable(GL_ALPHA_TEST); // 禁用Alpha測試



可以通過下面的程式碼來設定Alpha測試條件為“大於0.5則通過”:

glAlphaFunc(GL_GREATER, 0.5f);



該函式的第二個引數表示設定值,用於進行比較。第一個引數是比較方式,除了GL_LESS(小於則通過)外,還可以選擇:
GL_ALWAYS(始終通過),
GL_NEVER(始終不通過),
GL_LESS(小於則通過),
GL_LEQUAL(小於等於則通過),
GL_EQUAL(等於則通過),
GL_GEQUAL(大於等於則通過),
GL_NOTEQUAL(不等於則通過)。
現在我們來看一個實際例子。一幅照片圖片,一幅相框圖片,如何將它們組合在一起呢?為了簡單起見,我們使用前面兩課一直使用的24位BMP檔案來作為圖片格式。(因為釋出到網路上,為了節約容量,我所釋出的是JPG格式。大家下載後可以用Windows XP自帶的畫圖工具開啟,並另存為24位BMP格式)
http://blog.programfan.com/upfile/200710/2007100711109.jpghttp://blog.programfan.com/upfile/200710/20071007111014.jpg
注:第一幅圖片是著名網路遊戲《魔獸世界》的一幅桌面背景,用在這裡希望沒有涉及版權問題。如果有什麼不妥,請及時指出,我會立即更換。

在24位的BMP檔案格式中,BGR三種顏色各佔8位,沒有儲存Alpha值,因此無法直接使用Alpha測試。注意到相框那幅圖片中,所有需要透明的位置都是白色,所以我們在程式中設定所有白色(或很接近白色)的畫素Alpha值為0.0,設定其它畫素Alpha值為1.0,然後設定Alpha測試的條件為“大於0.5則通過”即可。這種使用某種特殊顏色來代表透明顏色的技術,有時又被成為Color Key技術。
利用前面第11課的一段程式碼,將圖片讀取為紋理,然後利用下面這個函式來設定“當前紋理”中每一個畫素的Alpha值。

/* 將當前紋理BGR格式轉換為BGRA格式
* 紋理中畫素的RGB值如果與指定rgb相差不超過absolute,則將Alpha設定為0.0,否則設定為1.0
*/
void texture_colorkey(GLubyte r, GLubyte g, GLubyte b, GLubyte absolute)
{
     GLint width, height;
     GLubyte* pixels = 0;

     // 獲得紋理的大小資訊
     glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
     glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

     // 分配空間並獲得紋理畫素
     pixels = (GLubyte*)malloc(width*height*4);
    if( pixels == 0 )
        return;
     glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);

     // 修改畫素中的Alpha值
     // 其中pixels[i*4], pixels[i*4+1], pixels[i*4+2], pixels[i*4+3]
     //    分別表示第i個畫素的藍、綠、紅、Alpha四種分量,0表示最小,255表示最大
     {
         GLint i;
         GLint count = width * height;
        for(i=0; i<count; ++i)
         {
            if( abs(pixels[i*4] - b) <= absolute
              && abs(pixels[i*4+1] - g) <= absolute
              && abs(pixels[i*4+2] - r) <= absolute )
                 pixels[i*4+3] = 0;
            else
                 pixels[i*4+3] = 255;
         }
     }

     // 將修改後的畫素重新設定到紋理中,釋放記憶體
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
         GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
    free(pixels);
}有了紋理後,我們開啟紋理,指定合適的紋理座標並繪製一個矩形,這樣就可以在螢幕上將圖片繪製出來。我們先繪製相片的紋理,再繪製相框的紋理。程式程式碼如下:
void display(void)
{
    static int initialized    = 0;
    static GLuint texWindow   = 0;
    static GLuint texPicture = 0;

     // 執行初始化操作,包括:讀取相片,讀取相框,將相框由BGR顏色轉換為BGRA,啟用二維紋理
    if( !initialized )
     {
         texPicture = load_texture("pic.bmp");
         texWindow   = load_texture("window.bmp");
         glBindTexture(GL_TEXTURE_2D, texWindow);
         texture_colorkey(255, 255, 255, 10);

         glEnable(GL_TEXTURE_2D);

         initialized = 1;
     }

     // 清除螢幕
     glClear(GL_COLOR_BUFFER_BIT);

     // 繪製相片,此時不需要進行Alpha測試,所有的畫素都進行繪製
     glBindTexture(GL_TEXTURE_2D, texPicture);
     glDisable(GL_ALPHA_TEST);
     glBegin(GL_QUADS);
         glTexCoord2f(0, 0);      glVertex2f(-1.0f, -1.0f);
         glTexCoord2f(0, 1);      glVertex2f(-1.0f,   1.0f);
         glTexCoord2f(1, 1);      glVertex2f( 1.0f,   1.0f);
         glTexCoord2f(1, 0);      glVertex2f( 1.0f, -1.0f);
     glEnd();

     // 繪製相框,此時進行Alpha測試,只繪製不透明部分的畫素
     glBindTexture(GL_TEXTURE_2D, texWindow);
     glEnable(GL_ALPHA_TEST);
     glAlphaFunc(GL_GREATER, 0.5f);
     glBegin(GL_QUADS);
         glTexCoord2f(0, 0);      glVertex2f(-1.0f, -1.0f);
         glTexCoord2f(0, 1);      glVertex2f(-1.0f,   1.0f);
         glTexCoord2f(1, 1);      glVertex2f( 1.0f,   1.0f);
         glTexCoord2f(1, 0);      glVertex2f( 1.0f, -1.0f);
     glEnd();

     // 交換緩衝
     glutSwapBuffers();
}
其中:load_texture函式是從第11課中照搬過來的(該函式還使用了一個power_of_two函式,一個BMP_Header_Length常數,同樣照搬),無需進行修改。main函式跟其它課程的基本相同,不再重複。
程式執行後,會發現相框與相片的銜接有些不自然,這是因為相框某些邊緣部分雖然肉眼看上去是白色,但其實RGB值與純白色相差並不少,因此程式計算其Alpha值時認為其不需要透明。解決辦法是仔細處理相框中的每個畫素,在需要透明的地方塗上純白色,這也許是一件很需要耐心的工作。
大家可能會想:前面我們學習過混合操作,混合可以實現半透明,自然也可以通過設定實現全透明。也就是說,Alpha測試可以實現的效果幾乎都可以通過OpenGL混合功能來實現。那麼為什麼還需要一個Alpha測試呢?答案就是,這與效能相關。Alpha測試只要簡單的比較大小就可以得到最終結果,而混合操作一般需要進行乘法運算,效能有所下降。另外,OpenGL測試的順序是:剪裁測試、Alpha測試、模板測試、深度測試。如果某項測試不通過,則不會進行下一步,而只有所有測試都通過的情況下才會執行混合操作。因此,在使用Alpha測試的情況下,透明的畫素就不需要經過模板測試和深度測試了;而如果使用混合操作,即使透明的畫素也需要進行模板測試和深度測試,效能會有所下降。還有一點:對於那些“透明”的畫素來說,如果使用Alpha測試,則“透明”的畫素不會通過測試,因此畫素的深度值不會被修改;而使用混合操作時,雖然畫素的顏色沒有被修改,但它的深度值則有可能被修改掉了。
因此,如果所有的畫素都是“透明”或“不透明”,沒有“半透明”時,應該儘量採用Alpha測試而不是採用混合操作。當需要繪製半透明畫素時,才採用混合操作。
3、模板測試
模板測試是所有OpenGL測試中比較複雜的一種。

首先,模板測試需要一個模板緩衝區,這個緩衝區是在初始化OpenGL時指定的。如果使用GLUT工具包,可以在呼叫glutInitDisplayMode函式時在引數中加上GLUT_STENCIL,例如:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL);
在Windows作業系統中,即使沒有明確要求使用模板緩衝區,有時候也會分配模板緩衝區。但為了保證程式的通用性,最好還是明確指定使用模板緩衝區。如果確實沒有分配模板緩衝區,則所有進行模板測試的畫素全部都會通過測試。

通過glEnable/glDisable可以啟用或禁用模板測試。
glEnable(GL_STENCIL_TEST);   // 啟用模板測試
glDisable(GL_STENCIL_TEST); // 禁用模板測試

OpenGL在模板緩衝區中為每個畫素儲存了一個“模板值”,當畫素需要進行模板測試時,將設定的模板參考值與該畫素的“模板值”進行比較,符合條件的通過測試,不符合條件的則被丟棄,不進行繪製。
條件的設定與Alpha測試中的條件設定相似。但注意Alpha測試中是用浮點數來進行比較,而模板測試則是用整數來進行比較。比較也有八種情況:始終通過、始終不通過、大於則通過、小於則通過、大於等於則通過、小於等於則通過、等於則通過、不等於則通過。
glStencilFunc(GL_LESS, 3, mask);
這段程式碼設定模板測試的條件為:“小於3則通過”。glStencilFunc的前兩個引數意義與glAlphaFunc的兩個引數類似,第三個引數的意義為:如果進行比較,則只比較mask中二進位制為1的位。例如,某個畫素模板值為5(二進位制101),而mask的二進位制值為00000011,因為只比較最後兩位,5的最後兩位為01,其實是小於3的,因此會通過測試。

如何設定畫素的“模板值”呢?glClear函式可以將所有畫素的模板值復位。程式碼如下:
glClear(GL_STENCIL_BUFFER_BIT);
可以同時復位顏色值和模板值:
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
正如可以使用glClearColor函式來指定清空屏幕後的顏色那樣,也可以使用glClearStencil函式來指定復位後的“模板值”。

每個畫素的“模板值”會根據模板測試的結果和深度測試的結果而進行改變。
glStencilOp(fail, zfail, zpass);
該函式指定了三種情況下“模板值”該如何變化。第一個引數表示模板測試未通過時該如何變化;第二個引數表示模板測試通過,但深度測試未通過時該如何變化;第三個引數表示模板測試和深度測試均通過時該如何變化。如果沒有起用模板測試,則認為模板測試總是通過;如果沒有啟用深度測試,則認為深度測試總是通過)
變化可以是:
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函式。

從前面所講可以知道,使用剪裁測試可以把繪製區域限制在一個矩形的區域內。但如果需要把繪製區域限制在一個不規則的區域內,則需要使用模板測試。
例如:繪製一個湖泊,以及周圍的樹木,然後繪製樹木在湖泊中的倒影。為了保證倒影被正確的限制在湖泊表面,可以使用模板測試。具體的步驟如下:
(1) 關閉模板測試,繪製地面和樹木。
(2) 開啟模板測試,使用glClear設定所有畫素的模板值為0。
(3) 設定glStencilFunc(GL_ALWAYS, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);繪製湖泊水面。這樣一來,湖泊水面的畫素的“模板值”為1,而其它地方畫素的“模板值”為0。
(4) 設定glStencilFunc(GL_EQUAL, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);繪製倒影。這樣一來,只有“模板值”為1的畫素才會被繪製,因此只有“水面”的畫素才有可能被倒影的畫素替換,而其它畫素則保持不變。
們仍然來看一個實際的例子。這是一個比較簡單的場景:空間中有一個球體,一個平面鏡。我們站在某個特殊的觀察點,可以看到球體在平面鏡中的映象,並且映象處於平面鏡的邊緣,有一部分因為平面鏡大小的限制,而無法顯示出來。整個場景的效果如下圖:
http://blog.programfan.com/upfile/200710/20071007111019.jpg

繪製這個場景的思路跟前面提到的湖面倒影是接近的。
假設平面鏡所在的平面正好是X軸和Y軸所確定的平面,則球體和它在平面鏡中的映象是關於這個平面對稱的。我們用一個draw_sphere函式來繪製球體,先呼叫該函式以繪製球體本身,然後呼叫glScalef(1.0f, 1.0f, -1.0f); 再呼叫draw_sphere函式,就可以繪製球體的映象。
另外需要注意的地方就是:因為是繪製三維的場景,我們開啟了深度測試。但是站在觀察者的位置,球體的映象其實是在平面鏡的“背後”,也就是說,如果按照常規的方式繪製,平面鏡會把映象覆蓋掉,這不是我們想要的效果。解決辦法就是:設定深度緩衝區為只讀,繪製平面鏡,然後設定深度緩衝區為可寫的狀態,繪製平面鏡“背後”的映象。
有的朋友可能會問:如果在繪製映象的時候關閉深度測試,那映象不就不會被平面鏡遮擋了嗎?為什麼還要開啟深度測試,又需要把深度緩衝區設定為只讀呢?實際情況是:雖然關閉深度測試確實可以讓映象不被平面鏡遮擋,但是映象本身會出現若干問題。我們看到的映象是一個球體,但實際上這個球體是由很多的多邊形所組成的,這些多邊形有的代表了我們所能看到的“正面”,有的則代表了我們不能看到的“背面”。如果關閉深度測試,而有的“背面”多邊形又比“正面”多邊形先繪製,就會造成球體的背面反而把正面擋住了,這不是我們想要的效果。為了確保正面可以擋住背面,應該開啟深度測試。
繪製部分的程式碼如下:
void draw_sphere()
{
     // 設定光源
     glEnable(GL_LIGHTING);
     glEnable(GL_LIGHT0);
     {
         GLfloat
             pos[]      = {5.0f, 5.0f, 0.0f, 1.0f},
             ambient[] = {0.0f, 0.0f, 1.0f, 1.0f};
         glLightfv(GL_LIGHT0, GL_POSITION, pos);
         glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
     }

     // 繪製一個球體
     glColor3f(1, 0, 0);
     glPushMatrix();
     glTranslatef(0, 0, 2);
     glutSolidSphere(0.5, 20, 20);
     glPopMatrix();
}

void display(void)
{
     // 清除螢幕
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

     // 設定觀察點
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     gluPerspective(60, 1, 5, 25);
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     gluLookAt(5, 0, 6.5, 0, 0, 0, 0, 1, 0);

     glEnable(GL_DEPTH_TEST);

     // 繪製球體
     glDisable(GL_STENCIL_TEST);
     draw_sphere();

     // 繪製一個平面鏡。在繪製的同時注意設定模板緩衝。
     // 另外,為了保證平面鏡之後的映象能夠正確繪製,在繪製平面鏡時需要將深度緩衝區設定為只讀的。
     // 在繪製時暫時關閉光照效果
     glClearStencil(0);
     glClear(GL_STENCIL_BUFFER_BIT);
     glStencilFunc(GL_ALWAYS, 1, 0xFF);
     glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
     glEnable(GL_STENCIL_TEST);

     glDisable(GL_LIGHTING);
     glColor3f(0.5f, 0.5f, 0.5f);
     glDepthMask(GL_FALSE);
     glRectf(-1.5f, -1.5f, 1.5f, 1.5f);
     glDepthMask(GL_TRUE);

     // 繪製一個與先前球體關於平面鏡對稱的球體,注意光源的位置也要發生對稱改變
     // 因為平面鏡是在X軸和Y軸所確定的平面,所以只要Z座標取反即可實現對稱
     // 為了保證球體的繪製範圍被限制在平面鏡內部,使用模板測試
     glStencilFunc(GL_EQUAL, 1, 0xFF);
     glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
     glScalef(1.0f, 1.0f, -1.0f);
     draw_sphere();

     // 交換緩衝
     glutSwapBuffers();

     // 截圖
     grab();
}

其中display函式的末尾呼叫了一個grab函式,它儲存當前的圖象到一個BMP檔案。這個函式本來是在第十課和第十一課中都有所使用的。但是我發現它有一個bug,現在進行了修改:在函式最開頭的部分加上一句:glReadBuffer(GL_FRONT);即可。注意這個函式最好是在繪製完畢後(如果是使用雙緩衝,則應該在交換緩衝後)立即呼叫。
大家可能會有這樣的感覺:模板測試的設定是如此複雜,它可以實現的功能應該很多,肯定不止這樣一個“限制畫素的繪製範圍”。事實上也是如此,不過現在我們暫時只講這些。

其實,如果不需要繪製半透明效果,有時候可以用混合功能來代替模板測試。就繪製映象這個例子來說,可以採用下面的步驟:
(1) 清除螢幕,在glClearColor中設定合適的值確保清除屏幕後畫素的Alpha值為0.0
(2) 關閉混合功能,繪製球體本身,設定合適的顏色(或者光照與材質)以確保所有被繪製的畫素的Alpha值為0.0
(3) 繪製平面鏡,設定合適的顏色(或者光照與材質)以確保所有被繪製的畫素的Alpha值為1.0
(4) 啟用混合功能,用GL_DST_ALPHA作為源因子,GL_ONE_MINUS_DST_ALPHA作為目標因子,這樣就實現了只有原來Alpha為1.0的畫素才能被修改,而原來Alpha為0.0的畫素則保持不變。這時再繪製映象物體,注意確保所有被繪製的畫素的Alpha值為1.0。
在有的OpenGL實現中,模板測試是軟體實現的,而混合功能是硬體實現的,這時候可以考慮這樣的代替方法以提高執行效率。但是並非所有的模板測試都可以用混合功能來代替,並且這樣的代替顯得不自然,複雜而且容易出錯。
另外始終注意:使用混合來模擬時,即使某個畫素原來的Alpha值為0.0,以致於在繪製後其顏色不會有任何變化,但是這個畫素的深度值有可能會被修改,而如果是使用模板測試,沒有通過測試的畫素其深度值不會發生任何變化。而且,模板測試和混合功能中,畫素模板值的修改方式是不一樣的。
4、深度測試
在本課的開頭,已經簡單的敘述了深度測試。這裡是完整的內容。

深度測試需要深度緩衝區,跟模板測試需要模板緩衝區是類似的。如果使用GLUT工具包,可以在呼叫glutInitDisplayMode函式時在引數中加上GLUT_DEPTH,這樣來明確指定要求使用深度緩衝區。
深度測試和模板測試的實現原理很類似,都是在一個緩衝區儲存畫素的某個值,當需要進行測試時,將儲存的值與另一個值進行比較,以確定是否通過測試。兩者的區別在於:模板測試是設定一個值,在測試時用這個設定值與畫素的“模板值”進行比較,而深度測試是根據頂點的空間座標計算出深度,用這個深度與畫素的“深度值”進行比較。也就是說,模板測試需要指定一個值作為比較參考,而深度測試中,這個比較用的參考值是OpenGL根據空間座標自動計算的。

通過glEnable/glDisable函式可以啟用或禁用深度測試。
glEnable(GL_DEPTH_TEST);   // 啟用深度測試
glDisable(GL_DEPTH_TEST); // 禁用深度測試

至於通過測試的條件,同樣有八種,與Alpha測試中的條件設定相同。條件設定是通過glDepthFunc函式完成的,預設值是GL_LESS。
glDepthFunc(GL_LESS);

與模板測試相比,深度測試的應用要頻繁得多。幾乎所有的三維場景繪製都使用了深度測試。正因為這樣,幾乎所有的OpenGL實現都對深度測試提供了硬體支援,所以雖然兩者的實現原理類似,但深度測試很可能會比模板測試快得多。當然了,兩種測試在應用上很少有交集,一般不會出現使用一種測試去代替另一種測試的情況。
小結:
本次課程介紹了OpenGL所提供的四種測試,分別是剪裁測試、Alpha測試、模板測試、深度測試。OpenGL會對每個即將繪製的畫素進行以上四種測試,每個畫素只有通過一項測試後才會進入下一項測試,而只有通過所有測試的畫素才會被繪製,沒有通過測試的畫素會被丟棄掉,不進行繪製。每種測試都可以單獨的開啟或者關閉,如果某項測試被關閉,則認為所有畫素都可以順利通過該項測試。
剪裁測試是指:只有位於指定矩形內部的畫素才能通過測試。
Alpha測試是指:只有Alpha值與設定值相比較,滿足特定關係條件的畫素才能通過測試。
模板測試是指:只有畫素模板值與設定值相比較,滿足特定關係條件的畫素才能通過測試。
深度測試是指:只有畫素深度值與新的深度值比較,滿足特定關係條件的畫素才能通過測試。
上面所說的特定關係條件可以是大於、小於、等於、大於等於、小於等於、不等於、始終通過、始終不通過這八種。
模板測試需要模板緩衝區,深度測試需要深度緩衝區。這些緩衝區都是在初始化OpenGL時指定的。如果使用GLUT工具包,則可以在glutInitDisplayMode函式中指定。無論是否開啟深度測試,OpenGL在畫素被繪製時都會嘗試修改畫素的深度值;而只有開啟模板測試時,OpenGL才會嘗試修改畫素的模板值,模板測試被關閉時,OpenGL在畫素被繪製時也不會修改畫素的模板值。
利用這些測試操作可以控制畫素被繪製或不被繪製,從而實現一些特殊效果。利用混合功能可以實現半透明,通過設定也可以實現完全透明,因而可以模擬畫素顏色的繪製或不繪製。但注意,這裡僅僅是顏色的模擬。OpenGL可以為畫素儲存顏色、深度值和模板值,利用混合實現透明時,畫素顏色不發生變化,但深度值則會可能變化,模板值受glStencilFunc函式中第三個引數影響;利用測試操作實現透明時,畫素顏色不發生變化,深度值也不發生變化,模板值受glStencilFunc函式中前兩個引數影響。
此外,修正了第十課、第十一課中的一個函式中的bug。在grab函式中,應該在最開頭加上一句glReadBuffer(GL_FRONT);以保證讀取到的內容正好就是顯示的內容。

因為論壇支援附件了,我會把程式原始碼和所使用的圖片上傳到附件裡,方便大家下載。