咱們已經知道,使用OpenGL其實只要調用一系列的OpenGL函數就能夠了。然而,這種方式在一些時候可能致使問題。好比某個畫面中,使用了數千個多邊形來表現一個比較真實的人物,OpenGL爲了產生這數千個多邊形,就須要不停的調用glVertex*函數,每個多邊形將至少調用三次(由於多邊形至少有三個頂點),因而繪製一個比較真實的人物就須要調用上萬次的glVertex*函數。更糟糕的是,若是咱們須要每秒鐘繪製60幅畫面,則每秒調用的glVertex*函數次數就會超過數十萬次,乃至接近百萬次。這樣的狀況是咱們所不肯意看到的。
同時,考慮這樣一段代碼:算法
const int segments = 100; const GLfloat pi = 3.14f; int i; glLineWidth(10.0); glBegin(GL_LINE_LOOP); for(i=0; i<segments; ++i) { GLfloat tmp = 2 * pi * i / segments; glVertex2f(cos(tmp), sin(tmp)); } glEnd();
這段代碼將繪製一個圓環。若是咱們在每次繪製圖象時調用這段代碼,則雖然能夠達到繪製圓環的目的,可是cos、sin等開銷較大的函數被屢次調用,浪費了CPU資源。若是每個頂點不是經過cos、sin等函數獲得,而是使用更復雜的運算方式來獲得,則浪費的現象就更加明顯。
通過分析,咱們能夠發現上述兩個問題的共同點:程序屢次執行了重複的工做,致使CPU資源浪費和運行速度的降低。使用顯示列表能夠較好的解決上述兩個問題。
在編寫程序時,遇到重複的工做,咱們每每是將重複的工做編寫爲函數,在須要的地方調用它。相似的,在編寫OpenGL程序時,遇到重複的工做,能夠建立一個顯示列表,把重複的工做裝入其中,並在須要的地方調用這個顯示列表。
使用顯示列表通常有四個步驟:分配顯示列表編號、建立顯示列表、調用顯示列表、銷燬顯示列表。
1、分配顯示列表編號
OpenGL容許多個顯示列表同時存在,就好象C語言容許程序中有多個函數同時存在。C語言中,不一樣的函數用不一樣的名字來區分,而在OpenGL中,不一樣的顯示列表用不一樣的正整數來區分。
你能夠本身指定一些各不相同的正整數來表示不一樣的顯示列表。可是若是你不夠當心,可能出現一個顯示列表將另外一個顯示列表覆蓋的狀況。爲了不這一問題,使用glGenLists函數來自動分配一個沒有使用的顯示列表編號。
glGenLists函數有一個參數i,表示要分配i個連續的未使用的顯示列表編號。返回的是分配的若干連續編號中最小的一個。例如,glGenLists(3);若是返回20,則表示分配了20、2一、22這三個連續的編號。若是函數返回零,表示分配失敗。
可使用glIsList函數判斷一個編號是否已經被用做顯示列表。
2、建立顯示列表
建立顯示列表實際上就是把各類OpenGL函數的調用裝入到顯示列表中。使用glNewList開始裝入,使用glEndList結束裝入。glNewList有兩個參數,第一個參數是一個正整數表示裝入到哪一個顯示列表。第二個參數有兩種取值,若是爲GL_COMPILE,則表示如下的內容只是裝入到顯示列表,但如今不執行它們;若是爲GL_COMPILE_AND_EXECUTE,表示在裝入的同時,把裝入的內容執行一遍。
例如,須要把「設置顏色爲紅色,而且指定一個座標爲(0, 0)的頂點」這兩條命令裝入到編號爲list的顯示列表中,而且在裝入的時候不執行,則能夠用下面的代碼:編程
glNewList(list, GL_COMPILE); glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glEnd(); //注意:顯示列表只能裝入OpenGL函數,而不能裝入其它內容。例如: int i = 3; glNewList(list, GL_COMPILE); if( i > 20 ) glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glEnd();
其中if這個判斷就沒有被裝入到顯示列表。之後即便修改i的值,使i>20的條件成立,則glColor3f這個函數也不會被執行。由於它根本就不存在於顯示列表中。
另外,並不是全部的OpenGL函數均可以裝入到顯示列表中。例如,各類用於查詢的函數,它們沒法被裝入到顯示列表,由於它們都具備返回值,而glCallList和glCallLists函數都不知道如何處理這些返回值。在網絡方式下,設置客戶端狀態的函數也沒法被裝入到顯示列表,這是由於顯示列表被保存到服務器端,各類設置客戶端狀態的函數在發送到服務器端之前就被執行了,而服務器端沒法執行這些函數。分配、建立、刪除顯示列表的動做也沒法被裝入到另外一個顯示列表,但調用顯示列表的動做則能夠被裝入到另外一個顯示列表。服務器
3、調用顯示列表
使用glCallList函數能夠調用一個顯示列表。該函數有一個參數,表示要調用的顯示列表的編號。例如,要調用編號爲10的顯示列表,直接使用glCallList(10);就能夠了。
使用glCallLists函數能夠調用一系列的顯示列表。該函數有三個參數,第一個參數表示了要調用多少個顯示列表。第二個參數表示了這些顯示列表的編號的儲存格式,能夠是GL_BYTE(每一個編號用一個GLbyte表示),GL_UNSIGNED_BYTE(每一個編號用一個GLubyte表示),GL_SHORT,GL_UNSIGNED_SHORT,GL_INT,GL_UNSIGNED_INT,GL_FLOAT。第三個參數表示了這些顯示列表的編號所在的位置。在使用該函數前,須要用glListBase函數來設置一個偏移量。假設偏移量爲k,且glCallLists中要求調用的顯示列表編號依次爲l1, l2, l3, ...,則實際調用的顯示列表爲l1+k, l2+k, l3+k, ...。
例如:網絡
GLuint lists[] = {1, 3, 4, 8}; glListBase(10); glCallLists(4, GL_UNSIGNED_INT, lists);
則實際上調用的是編號爲11, 13, 14, 18的四個顯示列表。
注:「調用顯示列表」這個動做自己也能夠被裝在另外一個顯示列表中。函數
4、銷燬顯示列表
銷燬顯示列表能夠回收資源。使用glDeleteLists來銷燬一串編號連續的顯示列表。
例如,使用glDeleteLists(20, 4);將銷燬20,21,22,23這四個顯示列表。
使用顯示列表將會帶來一些開銷,例如,把各類動做保存到顯示列表中會佔用必定數量的內存資源。但若是使用得當,顯示列表能夠提高程序的性能。這主要表如今如下方面:
一、明顯的減小OpenGL函數的調用次數。若是函數調用是經過網絡進行的(Linux等操做系統支持這樣的方式,即由應用程序在客戶端發出OpenGL請求,由網絡上的另外一臺服務器進行實際的繪圖操做),將顯示列表保存在服務器端,能夠大大減小網絡負擔。
二、保存中間結果,避免一些沒必要要的計算。例如前面的樣例程序中,cos、sin函數的計算結果被直接保存到顯示列表中,之後使用時就沒必要重複計算。
三、便於優化。咱們已經知道,使用glTranslate*、glRotate*、glScale*等函數時,其實是執行矩陣乘法操做,因爲這些函數常常被組合在一塊兒使用,一般會出現矩陣的連乘。這時,若是把這些操做保存到顯示列表中,則一些複雜的OpenGL版本會嘗試先計算出連乘的一部分結果,從而提升程序的運行速度。在其它方面也可能存在相似的例子。
同時,顯示列表也爲程序的設計帶來方便。咱們在設置一些屬性時,常常把一些相關的函數放在一塊兒調用,(好比,把設置光源的各類屬性的函數放到一塊兒)這時,若是把這些設置屬性的操做裝入到顯示列表中,則能夠實現屬性的成組的切換。
固然了,即便使用顯示列表在某些狀況下能夠提升性能,但這種提升極可能並不明顯。畢竟,在硬件配置和大體的軟件算法都不變的前提下,性能可提高的空間並不大。
顯示列表的內容就是這麼多了,下面咱們看一個例子。oop
假設咱們須要繪製一個旋轉的彩色正四面體,則能夠這樣考慮:設置一個全局變量angle,而後讓它的值不斷的增長(到達360後又恢復爲0,周而復始)。每次須要繪製圖形時,根據angle的值進行旋轉,而後繪製正四面體。這裏正四面體採用顯示列表來實現,即把繪製正四面體的若干OpenGL函數裝到一個顯示列表中,而後每次須要繪製時,調用這個顯示列表便可。
將正四面體的四個頂點顏色分別設置爲紅、黃、綠、藍,經過數學計算,將座標設置爲:性能
(-0.5, -5*sqrt(5)/48, sqrt(3)/6), ( 0.5, -5*sqrt(5)/48, sqrt(3)/6), ( 0, -5*sqrt(5)/48, -sqrt(3)/3), ( 0, 11*sqrt(6)/48, 0)
2007年4月24日修正:以上結果有誤,經過計算AB, AC, AD, BC, BD, CD的長度,發現AD, BD, CD的長度與1.0有較大誤差。正確的座標應該是:優化
A點:( 0.5, -sqrt(6)/12, -sqrt(3)/6) B點:( -0.5, -sqrt(6)/12, -sqrt(3)/6) C點:( 0, -sqrt(6)/12, sqrt(3)/3) D點:( 0, sqrt(6)/4, 0)
程序代碼中也作了相應的修改
下面給出程序代碼,你們能夠從中體會一下顯示列表的用法。ui
#include <gl/glut.h> #define WIDTH 400 #define HEIGHT 400 #include <math.h> #define ColoredVertex(c, v) do{ glColor3fv(c); glVertex3fv(v); }while(0) GLfloat angle = 0.0f; void myDisplay(void) { static int list = 0; if( list == 0 ) { // 若是顯示列表不存在,則建立 /* GLfloat PointA[] = {-0.5, -5*sqrt(5)/48, sqrt(3)/6}, PointB[] = { 0.5, -5*sqrt(5)/48, sqrt(3)/6}, PointC[] = { 0, -5*sqrt(5)/48, -sqrt(3)/3}, PointD[] = { 0, 11*sqrt(6)/48, 0}; */ // 2007年4月27日修改 GLfloat PointA[] = { 0.5f, -sqrt(6.0f)/12, -sqrt(3.0f)/6}, PointB[] = {-0.5f, -sqrt(6.0f)/12, -sqrt(3.0f)/6}, PointC[] = { 0.0f, -sqrt(6.0f)/12, sqrt(3.0f)/3}, PointD[] = { 0.0f, sqrt(6.0f)/4, 0}; GLfloat ColorR[] = {1, 0, 0}, ColorG[] = {0, 1, 0}, ColorB[] = {0, 0, 1}, ColorY[] = {1, 1, 0}; list = glGenLists(1); glNewList(list, GL_COMPILE); glBegin(GL_TRIANGLES); // 平面ABC ColoredVertex(ColorR, PointA); ColoredVertex(ColorG, PointB); ColoredVertex(ColorB, PointC); // 平面ACD ColoredVertex(ColorR, PointA); ColoredVertex(ColorB, PointC); ColoredVertex(ColorY, PointD); // 平面CBD ColoredVertex(ColorB, PointC); ColoredVertex(ColorG, PointB); ColoredVertex(ColorY, PointD); // 平面BAD ColoredVertex(ColorG, PointB); ColoredVertex(ColorR, PointA); ColoredVertex(ColorY, PointD); glEnd(); glEndList(); glEnable(GL_DEPTH_TEST); } // 已經建立了顯示列表,在每次繪製正四面體時將調用它 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(angle, 1, 0.5, 0); glCallList(list); glPopMatrix(); glutSwapBuffers(); } void myIdle(void) { ++angle; if( angle >= 360.0f ) angle = 0.0f; myDisplay(); } int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowPosition(200, 200); glutInitWindowSize(WIDTH, HEIGHT); glutCreateWindow("OpenGL 窗口"); glutDisplayFunc(&myDisplay); glutIdleFunc(&myIdle); glutMainLoop(); return 0; }
在程序中,咱們將繪製正四面體的OpenGL函數裝到了一個顯示列表中,可是,關於旋轉的操做卻在顯示列表以外進行。這是由於若是把旋轉的操做也裝入到顯示列表,則每次旋轉的角度都是同樣的,不會隨着angle的值的變化而變化,因而就不能表現出動態的旋轉效果了。
程序運行時,可能感受到畫面的立體感不足,這主要是由於沒有使用光照的緣故。若是將glColor3fv函數去掉,改成設置各類材質,而後開啓光照效果,則能夠產生更好的立體感。你們能夠本身試着使用光照效果,惟一須要注意的地方就是法線向量的計算。因爲這裏的正四面體四個頂點座標選取得比較特殊,使得正四面體的中心座標正好是(0, 0, 0),所以,每三個頂點座標的平均值正好就是這三個頂點所組成的平面的法線向量的值。spa
void setNormal(GLfloat* Point1, GLfloat* Point2, GLfloat* Point3) { GLfloat normal[3]; int i; for(i=0; i<3; ++i) normal[i] = (Point1[i]+Point2[i]+Point3[i]) / 3; glNormal3fv(normal); }
小結本課介紹了顯示列表的知識和簡單的應用。能夠把各類OpenGL函數調用的動做裝到顯示列表中,之後調用顯示列表,就至關於調用了其中的OpenGL函數。顯示列表中除了存放對OpenGL函數的調用外,不會存放其它內容。使用顯示列表的過程是:分配一個未使用的顯示列表編號,把OpenGL函數調用裝入顯示列表,調用顯示列表,銷燬顯示列表。使用顯示列表有可能帶來程序運行速度的提高,可是這種提高並不必定會很明顯。顯示列表自己也存在必定的開銷。把繪製固定的物體的OpenGL函數放到一個顯示列表中,是一種不錯的編程思路。本課最後的例子中使用了這種思路。