歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> OpenGL超級寶典學習筆記——顯示列表

OpenGL超級寶典學習筆記——顯示列表

日期:2017/3/1 9:32:13   编辑:Linux編程

前言

在先前的章節中,我們已經討論OpenGL基本的一些渲染技術。這些基本的技巧足夠渲染簡單的圖像,然而在渲染精細的畫面逼真的畫面的時候(非常多的頂點和紋理),如果使用之前的方式渲染(立即模式)速度就很慢了,考慮到性能的原因(特別是實時渲染)我們需要以更快的方式完成畫面的渲染。精細的畫面有大量的數據需要CPU和GPU去處理,而且把數據從應用程序發送到顯卡有帶寬和顯存的瓶頸。

顯示列表

到目前為止,我們的圖元都是在一對glBegin/glEnd之間調用glVertex來組裝的。這種方式是非常靈活(可以動態地修改數據)而且易於使用的。但考慮性能的時候,以這種方式傳送圖元到顯卡性能是最差。考慮下面的步驟:畫一個受光照的帶紋理的三角形

glBegin(GL_TRIANGLES);
    glNormal3f(x, y, z);
    glTexCoord2f(s, t);
    glVertex3f(x, y, z);

    glNormal3f(x, y, z);
    glTexCoord2f(s, t);
    glVertex3f(x, y, z);

    glNormal3f(x, y, z);
    glTexCoord2f(s, t);
    glVertex3f(x, y, z);
glEnd();

就一個簡單的三角形就有11個函數調用。函數調用需要壓棧,出棧等操作。如果一個復雜的圖形有1萬個或者更多三角形組成,那這些函數調就會消耗許多CPU時間。可以想象顯卡空閒著等待CPU收集這些數據然後發送這批圖元。這種批量提交圖元的方式稱為立即模式。OpenGL提供了更好的方式了處理這些數據。

批處理

OpenGL是圖形硬件的一個軟件接口。你可以想象成OpenGL的命令被轉換為一些指定的硬件的命令和驅動的操作,然後傳到顯卡上立即執行。實際上,這些命令不是立即傳到圖形顯卡上執行的,而是有一個本地的緩沖區,當這個緩沖區的命令累積到一個臨界值的時候,就會被清空(flush)到硬件上去。

之所以這麼做的主要原因是與圖形硬件圖形需要花費較長的CPU時間。這個時間對於我們來說可能是非常短的,但對於一個一秒鐘運行幾十億個周期的CPU來說是相當緩慢的。打個比方有一艘從美國的英國的輪船,我們不會在一個人登船之後,就啟程到英國,然後返回再接另一個人。而是會盡可能地等到輪船滿載了之後才出發。對於計算機來說,通過系統總線一次性發送大量的數據,比分多次發送少量的數據要快。

發送緩沖數據到圖形硬件是一個異步操作,這樣CPU就可以空閒出來去處理其他的事情而不用等到這批渲染命令傳送完成。這樣圖形硬件和CPU的並行是非常高效的。

有三個事件會出發緩沖區的刷新(flush)。第一個是緩沖區滿了的時候,會把這批命令發給圖形硬件。我們沒有權限去訪問這個緩沖區和調整這個緩沖區的大小。第二個是執行交換緩沖區命令(swapbuffer)的時候。緩沖區交換是告訴驅動程序,表面已經完成了一個特定的場景,所有的命令應該被執行去渲染場景。如果是使用單緩沖區的,則需要調用手工調用刷新:

void glFlush(void);

有些OpenGL的命令並不會進行緩沖,例如glReadPixels和glDrawPixels。這些函數要訪問幀緩沖區進行直接的讀寫數據。在讀寫幀緩沖區是命令隊列必須被清空。還有一種是強制刷新命令緩沖區,然後等待渲染任務的完成。函數調用如下:

void glFinish(void);

這個函數較少用到,一般用於多線程或者多渲染環境。

批預處理

OpenGL的命令要從高級的命令語言被翻譯成低級的機器級的命令才能被機器所理解。如果命令非常多,那麼翻譯也需要消耗一定的時間。所以為了提高性能,有些萬年不變的OpenGL命令,可以翻譯後就保存起來,而不是每次都去翻譯。例如畫一個圓環(gltDrawTorus)的命令和頂點數據總是相同的,圓環是有一系列的三角形組成的,其中需要用到一些三角函數的命令。 我們只是通過改變模型視圖矩陣,來變換圓環的位置而已。

一個較好的解決方案是把這些命令和數據先進行預處理,然後保存起來,以後調用的時候可以快速地拷貝到命令緩沖區然後執行。在OpenGl中這些預編譯的命令列表稱為顯示列表。OpenGL使用glNewList/glEndList來包括顯示列表。

glNewList(<unsigned integer name>, GL_COMPILE);

//some OpenGL code

glEndList();

glNewList的第一個參數是顯示列表的名稱,用unsigned int類型來表示。第二個參數GL_COMPILE則告訴OpenGL編譯這些列表,但不立即執行他們。你可以指定GL_COMPILE_AND_EXCUTE這樣就編譯後立即執行一次(在渲染場景的函數可能會這樣做)。一般在初始化的時候,我們會預先編譯(GL_COMPILE)好這些顯示列表,然後在渲染場景時調用。

顯示列表的名稱可以是任意的無符號整數。當名稱命名重復時,後一個顯示列表會覆蓋掉前一個。為防止這種情況發生,我們可以使用OpenGL提供的函數

GLuint glGenLists(GLsizei range);

這個函數會生成指定個數的顯示列表的名稱。顯示列表的名稱是按數字順序保留的,返回值是第一個名稱。例如:glGenLists(3); 如果返回值是5,那就代表5,6,7這三個名稱是保留的,供給你使用的。下次再調用glGenLists就是從8開始了。執行顯示列表命令的函數:

void glCallList(GLuint list);

或者執行一組顯示列表命令:

void glCallLists(GLsizei n, GLenum type, const GLvoid *lists);

n代表顯示列表的個數,type是顯示列表數組lists的類型,lists是指向顯示列表名稱的指針。

顯示列表例子

在這裡修改之前的第八章的sphereworld的例子,使用顯示列表來渲染球體,圓環和地面。顯示列表非常容易使用,只需要對這個例子做一個簡單的修改即可。首先在初始化(SetupRC)產生一些顯示列表的名稱(unsigned int),然後把繪制球體,圓環和地面的代碼進行預編譯。代碼如下:

static unsigned int spherelist_1; static unsigned int spherelist_2; static unsigned int torulist; static unsigned int groundlist; void SetupRC()
{
....
... //給顯示列表命名 spherelist_1 = glGenLists(4);
  spherelist_2 = spherelist_1 + 1;
  torulist = spherelist_1 + 2;
  groundlist = spherelist_1 + 3; //預編譯這些顯示列表 glNewList(spherelist_1, GL_COMPILE);
    glutSolidSphere(0.3, 20, 20);
  glEndList();


  glNewList(spherelist_1, GL_COMPILE);
    glutSolidSphere(0.3, 20, 20);
  glEndList();

  glNewList(torulist, GL_COMPILE);
    gltDrawTorus(0.25f, 0.15f, 25, 25);
  glEndList();

  glNewList(groundlist, GL_COMPILE);
    RenderGround();
  glEndList();

}

在添加一個右鍵菜單,來切換兩種模式。在渲染時進行判斷。執行顯示列表的函數是 void glCallList(GLuint list)

if (iMode == LISTMODE)
  {
    glCallList(groundlist);
  } else {
    RenderGround();
  }

.... if (iMode == LISTMODE)
    glCallList(torulist); else gltDrawTorus(0.25f, 0.15f, 25, 25);
...

記錄渲染一次所花費處理器時間,並顯示在窗口標題上。僅僅通過一一次渲染的時間就能夠對比出不同了。

static void RenderScene()
{
  clock_t t = clock();
  ....
  ....

  glutSwapBuffers();
  t = clock() - t; char buffer[128] = {0,};
  wsprintf(buffer, "render clicks is %d", t);
  glutSetWindowTitle(buffer);
}

看一下結果:

正常模式下,花的處理器時間是5左右:

顯示列表模式下是1左右:

就這麼一個簡單的場景就有了5倍的差距。可見使用顯示列表能極大的提高性能。但顯示列表有一個缺點,就是數據得是靜態的,靈活性差。

注意:並不是所有的OpenGL函數都可以在顯示列表中存儲且通過顯示列表執行。一般來說,用於傳遞參數或返回數值的函數語句不能存入顯示列表,因為這張表有可能在參數的作用域之外被調用;如果在定義顯示列表時調用了這樣的函數,則它們將按瞬時方式執行並且不保存在顯示列表中,有時在調用執行顯示列表函數時會產生錯誤。以下列出的是不能存入顯示列表的OpenGL函數:

  glDeleteLists()    glIsEnable()
  glFeedbackBuffer()   glIsList()
  glFinish()       glPixelStore()
  glGenLists()      glRenderMode()
  glGet*()        glSelectBuffer()

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

Copyright © Linux教程網 All Rights Reserved