在一個場景中,如果有有些物體被其他物體遮住了不可見。那麼我們就不需要繪制它。在復雜的場景中,這可以減少大量的頂點和像素的處理,大幅度的提高幀率。遮擋查詢就是允許我們判斷一組圖形在進行了深度測試之後是否可見。
為了顯示遮擋查詢對性能的提升,我們需要一個對照組(不使用遮擋查詢來渲染場景)。
首先我們先繪制“主遮擋物”。這個主遮擋物不需要太多的細節,一般是牆,天花板,地板之類的物體。在下面的例子中我們,使用6面牆來組成這個主遮擋物。
void DrawOccluder() { glColor3f(0.5f, 0.25f, 0.0f); glPushMatrix(); glScalef(30.0f, 30.0f, 1.0f); glTranslatef(0.0f, 0.0f, 50.0f); glutSolidCube(10.0f); glTranslatef(0.0f, 0.0f, -100.0f); glutSolidCube(10.0f); glPopMatrix(); glPushMatrix(); glScalef(1.0f, 30.0f, 30.0f); glTranslatef(50.0f, 0.0f, 0.0f); glutSolidCube(10.0f); glTranslatef(-100.0f, 0.0f, 0.0f); glutSolidCube(10.0f); glPopMatrix(); glPushMatrix(); glScalef(30.0f, 1.0f, 30.0f); glTranslatef(0.0f, 50.0f, 0.0f); glutSolidCube(10.0f); glTranslatef(0.0f, -100.0f, 0.0f); glutSolidCube(10.0f); glPopMatrix(); }
現在我們在每一個單元格中,放置一個高度分挌化的紋理球體。這些球體可能是被遮擋物,也可能是遮擋物。
void DrawSphere(GLint sphereNum) { ... glutSolidSphere(50.0f, 200, 200); ... }void DrawModels(void) { //開啟紋理 自動生成紋理坐標 glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); //繪制27個不同顏色的球體 for (r = 0; r < 3; r++) { for (g = 0; g < 3; g++) { for (b = 0; b < 3; b++) { glColor3f(r * 0.5f, g * 0.5f, b * 0.5f); glPushMatrix(); glTranslatef(100.0f * r - 100.0f, 100.0f * g - 100.0f, 100.0f * b - 100.0f); DrawSphere((r*9)+(g*3)+b); glPopMatrix(); } } } glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); }
在我的機器上沒有遮擋查詢下渲染的幀率是20左右。
在遮擋查詢中,如果一個物體的邊界都是不可見的,那麼就代表這個物體不可見。所以我們只需檢測物體外圍的包圍體可見,就可以判斷物體是否被遮擋。物體外圍的包圍體包含著整個物體,這也就意味著包圍體的體積是大於等於物體的體積的。對於一個球體來說,包圍體可以有很多種,最常見的就是立方盒子,四面體等。
為什麼要選擇包圍體去判斷遮擋,而不是直接用球體的。因為球體太過於復雜,包含的頂點也多,渲染較耗時。遮擋之所以能夠提升性能,就是我們可以在無光照,無紋理等其他效果的下,並且不需要改變緩沖區的值,先渲染簡單的包圍體。通過這些包圍體進行深度測試,我們就能判斷出哪些物體被遮擋,那些被遮擋的物體就可以不需要被渲染(不需要調用任何渲染該物體的命令),如果這個物體擁有非常多的頂點,那麼遮擋在這個時候就能大幅度的提高性能。
遮擋查詢的步驟:
首先為這些物體生成查詢對象ID 調用glGenQueries
調用glBeginQuery開始遮擋查詢
渲染包圍體
調用glEndQuery 結束遮擋查詢
調用glGetQueryObject[u]iv,根據ID提取遮擋查詢的結果,並根據結果進行相應的操作
glDeleteQueries 刪除ID,回收資源
查詢對象的標識符(ID/名稱)是一個無符號整數,我們可以通過glGenQueries函數生成,也可以自己定義。一般用OpenGL提供的glGenQueries會比較方便。
void glGenQueries(GLsizei n, GLuint *ids);
第一個參數是生成ID的個數,第二個參數是來存放這些ID的數組。0是保留的ID,不會被產生。我們還可以通過glIsQuery來判斷一個ID是否是一個遮擋查詢對象的ID。
void glIsQuery(GLuint id);
如果是返回GL_TRUE,不是則返回GL_FALSE。
有了遮擋查詢對象的ID後,可以開始遮擋查詢了。例如
glBeginQuery(GL_SAMPLES_PASSED, 1); glBegin(GL_TRIANGLES); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(-1.0f, 5.0f, 0.0f); glVertex3f(6.0f, 20.0f, 0.0f); glEnd(); glEndQuery(GL_SAMPLES_PASSED);
void glBeginQuery(GLenum target, GLuint id);
其中target必須是GL_SAMPLES_PASSED. id是用來標識這次遮擋查詢的ID。
void glEndQuery(GLenum target);結束這次遮擋查詢,其中target必須是GL_SAMPLES_PASSED。
在完成對需要遮擋查詢的物體渲染之後,我們需要提取遮擋查詢的結果,可以通過glGetQueryObject[u]iv來提取結果,函數將返回片段或采樣的數量。
void glGetQueryObjectiv(GLenum id, GLenum pname, GLint *param);
void glGetQueryObjectuiv(GLenum id, GLenum pname, GLuint *param);
id是這個遮擋查詢對象的id,pname如果是GL_QUERY_RESULT, param將包含了通過深度測試的片段或樣本(如果啟用了多重采樣)的數量,如果數量為0,則表示這個物體完全被遮擋。
在完成遮擋查詢操作時,可能會有延遲。我們可以通過設置pname為GL_QUERY_RESULT_AVAILABLE來檢查是否完成了。如果遮擋查詢有效地完成了,則param將為GL_TRUE,否則為GL_FALSE.
例:
int count = 1000; //等待1000次循環 GLuint queryReady = GL_FALSE; while (!queryReady && count--) { glGetQueryObjectuiv(1, GL_QUERY_RESULT_AVAILABLE, &queryReady); } GLuint samples; glGetQueryObjectuiv(1, GL_QUERY_RESULT, &samples); if(samples > 0) DrawSomething();
使用完遮擋查詢對象之後,調用glDeleteQueries回收資源。
void glDeleteQueries(GLsizei n, const GLuint *ids);
修改前面的例子,我們可以先渲染27個球體的包圍體,進行遮擋查詢,如果有哪個包圍體被完全遮擋,我們就不需要繪制這個球體了。代碼片段如下:
void DrawModels(void) { GLint r, g, b; //繪制主遮擋物 DrawOccluder(); //在繪制包圍體時,越簡單越好。關掉紋理,光照等等。 //不需要往緩沖區中寫值。 glShadeModel(GL_FLAT); glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); glDisable(GL_NORMALIZE); glDepthMask(GL_FALSE); glColorMask(0, 0, 0, 0); // 畫27個立方體 for (r = 0; r < 3; r++) { for (g = 0; g < 3; g++) { for (b = 0; b < 3; b++) { if (showBoundingVolume) glColor3f(r * 0.5f, g * 0.5f, b * 0.5f); glPushMatrix(); glTranslatef(100.0f * r - 100.0f, 100.0f * g - 100.0f, 100.0f * b - 100.0f); //開始遮擋查詢 glBeginQuery(GL_SAMPLES_PASSED, queryIDs[(r*9)+(g*3)+b]); //繪制包圍體 glutSolidCube(100.0f); //結束遮擋查詢 glEndQuery(GL_SAMPLES_PASSED); glPopMatrix(); } } } //恢復正常的渲染狀態 glDisable(GL_POLYGON_STIPPLE); glShadeModel(GL_SMOOTH); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glEnable(GL_NORMALIZE); glColorMask(1, 1, 1, 1); glDepthMask(GL_TRUE); //開啟紋理 自動生成紋理坐標 glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); //繪制27個不同顏色的球體 for (r = 0; r < 3; r++) { for (g = 0; g < 3; g++) { for (b = 0; b < 3; b++) { glColor3f(r * 0.5f, g * 0.5f, b * 0.5f); glPushMatrix(); glTranslatef(100.0f * r - 100.0f, 100.0f * g - 100.0f, 100.0f * b - 100.0f); //函數中根據,遮擋查詢的結果來判斷是否要繪制這個球體 DrawSphere((r*9)+(g*3)+b); glPopMatrix(); } } } glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); }
void DrawSphere(GLint sphereNum) { GLboolean occluded = GL_FALSE; if (occlusionDetection) { GLint passingSamples; //檢查物體是否被完全遮擋 glGetQueryObjectiv(queryIDs[sphereNum], GL_QUERY_RESULT, &passingSamples); if (passingSamples == 0) occluded = GL_TRUE; } //沒有被遮擋則繪制 if (!occluded) { glutSolidSphere(50.0f, 200, 200); } }
有了遮擋查詢後,幀率達到了32左右,當然還要看觀察場景的角度。如果從某個角度看大部分球體都被遮擋了,性能的提升更大。
OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm
OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581