這次我將教你如何使用顯示列表,顯示列表將加快程序的速度,而且可以減少代碼的長度。 當你在制作 游戲里的小行星場景時,每一層上至少需要兩個行星,你可以用 OpenGL中的多邊形來構造每一個行星。聰明點的做法是做一個循環,每個循環畫出行星的一個面,最終你用幾十條語句畫出了一個行星。每次把行星畫到屏幕上都是很困難的。當你面臨更復雜的物體時你就會明白了。
那么,解決的辦法是什么呢?用現實列表,你只需要一次性建立物體,你可以貼圖,用顏色,想怎么弄就怎么弄。給現實列表一個名字,比如給小行星的顯示列表命
名為“asteroid”。現在,任何時候我想在屏幕上畫出行星,我只需要調用glCallList(asteroid)。之前做好的小行星就會立刻顯示
在屏幕上了。因為小行星已經在顯示列表里建造好了,OpenGL不會再計算如何構造它。它已經在內存中建造好了。這將大大降低CPU的使用,讓你的程序跑
的更快。 那么,開始學習咯。我稱這個DEMO為Q-Bert顯示列表。最終這個DEMO將在屏幕上畫出15個立方體。每個立方體都由一個盒子和一個頂構成,頂部是一個單獨的顯示列表,盒子沒有頂。 這一課是建立在第六課的基礎上的,我將重寫大部分的代碼,這樣容易看懂。下面的這些代碼在所有的課程中差不多都用到了。 #include <windows.h> // Header File For Windows #include <stdio.h> // Header File For Standard Input/Output #include <gl\gl.h> // Header File For The OpenGL32 Library #include <gl\glu.h> // Header File For The GLu32 Library #include <gl\glaux.h> ? ? // Header File For The GLaux Library HDC hDC=NULL; // Private GDI Device Context HGLRC hRC=NULL; // Permanent Rendering Context HWND hWnd=NULL; // Holds Our Window Handle HINSTANCE hInstance; // Holds The Instance Of The Application bool keys[256]; // Array Used For The Keyboard Routine bool active=TRUE; // Window Active Flag Set To TRUE By Default bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default 下面設置變量。首先是存儲紋理的變量,然后兩個新的變量用于顯示列表。這些變量是指向內存中顯示列表的指針。命名為box和top。 然后用兩個變量xloop,yloop表示屏幕上立方體的位置,兩個變量xrot,yrot表示立方體的旋轉。 GLuint texture[1]; // Storage For One Texture GLuint box; // Storage For The Display List GLuint top; // Storage For The Second Display List GLuint xloop; // Loop For X Axis GLuint yloop; // Loop For Y Axis GLfloat xrot; // Rotates Cube On The X Axis GLfloat yrot; // Rotates Cube On The Y Axis 接下來建立兩個顏色數組。 static GLfloat boxcol[5][3]= // Array For Box Colors { // Bright: Red, Orange, Yellow, Green, Blue {1.0f,0.0f,0.0f},{1.0f,0.5f,0.0f},{1.0f,1.0f,0.0f},{0.0f,1.0f,0.0f},{0.0f,1.0f,1.0f} };
static GLfloat topcol[5][3]= // Array For Top Colors { // Dark: Red, Orange, Yellow, Green, Blue {.5f,0.0f,0.0f},{0.5f,0.25f,0.0f},{0.5f,0.5f,0.0f},{0.0f,0.5f,0.0f},{0.0f,0.5f,0.5f} };
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc 現在正式開始建立顯示列表。你可能注意到了,所有創造盒子的代碼都在第一個顯示列表里,所有創造頂部的代碼都在另一個列表里。我會努力解釋這些細節。 GLvoid BuildLists() // Build Box Display List { 開始的時候我們告訴OpenGL我們要建立兩個顯示列表。glGenLists(2)建立了兩個顯示列表的空間,并返回第一個顯示列表的指針。“box”指向第一個顯示列表,任何時候調用“box”第一個顯示列表就會顯示出來。 box=glGenLists(2); // Building Two Lists 現在開始構造第一個顯示列表。我們已經申請了兩個顯示列表的空間了,并且有box指針指向第一個顯示列表。所以現在我們應該告訴OpenGL要建立什么類型的顯示列表。
我們用glNewList()命令來做這個事情。你一定注意到了box是第一個參數,這表示OpenGL將把列表存儲到box所指向的內存空間。第二個參
數GL_COMPILE告訴OpenGL我們想預先在內存中構造這個列表,這樣每次畫的時候就不必重新計算怎么構造物體了。
GL_COMPILE類似于編程。在你寫程序的時候,把它裝載到編譯器里,你每次運行程序都需要重新編譯。而如果他已經編譯成了.exe文件,那么每次你
只需要點擊那個.exe文件就可以運行它了,不需要編譯。當OpenGL編譯過顯示列表后,就不需要再每次顯示的時候重新編譯它了。這就是為什么用顯示列
表可以加快速度。 glNewList(box,GL_COMPILE); // New Compiled box Display List 下面這部分的代碼畫出一個沒有頂部的盒子,它不會出現在屏幕上,只會存儲在顯示列表里。 你可以在glNewList()和glEngList()中間加上任何你想加上的代碼。可以設置顏色,貼圖等等。唯一不能加進去的代碼就是會改變顯示列表的代碼。顯示列表一旦建立,你就不能改變它。
比如你想加上glColor3ub(rand()%255,rand()%255,rand()%255),使得每一次畫物體時都會有不同的顏色。但因為
顯示列表只會建立一次,所以每次畫物體的時候顏色都不會改變。物體將會保持第一次建立顯示列表時的顏色。
如果你想改變顯示列表的顏色,你只有在調用顯示列表之前改變顏色。后面將詳細解釋這一點。 glBegin(GL_QUADS); // Start Drawing Quads
// Bottom Face glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Right Of The Texture and Quad
// Front Face glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Right Of The Texture and Quad glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Top Left Of The Texture and Quad
// Back Face glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Right Of The Texture and Quad glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Left Of The Texture and Quad
// Right face glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Right Of The Texture and Quad glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom Left Of The Texture and Quad
// Left Face glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Bottom Right Of The Texture and Quad glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Top Right Of The Texture and Quad glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Top Left Of The Texture and Quad glEnd(); // Done Drawing Quads 用glEngList()命令,我們告訴OpenGL我們已經完成了一個顯示列表。在glNewList()和glEngList()之間的任何東西就是顯示列表的一部分。 glEndList(); // Done Building The box List 現在我們來建立第二個顯示列表。在上一個顯示列表的指針上加1,就得到了第二個顯示列表的指針。第二個顯示列表的指針命名為“top”。 top=box+1; // top List Value Is box List Value +1 現在我們知道了第二個顯示列表的指針,我們可以建立它了。 glNewList(top,GL_COMPILE); // New Compiled top Display List 下面的代碼畫出盒子的頂部。 glBegin(GL_QUADS); // Start Drawing Quad
// Top Face glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Top Left Of The Texture and Quad glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Bottom Left Of The Texture and Quad glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Bottom Right Of The Texture and Quad glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Top Right Of The Texture and Quad glEnd(); // Done Drawing Quad 然后告訴OpenGL第二個顯示列表建立完畢。 glEndList(); // Done Building The top Display List } 貼圖紋理的代碼和之前 教程里的代碼是一樣的。我們需要一個可以貼在立方體上的紋理。我決定使用mipmapping處理讓紋理看上去光滑,因為我討厭看見像素點。紋理的文件名是“cube.bmp”,存放在data目錄下。 if (TextureImage[0]=LoadBMP("Data/Cube.bmp")) // Load The Bitmap 改變窗口大小的代碼和第六課是一樣的。 初始化的代碼只有一點改變,加入了一行BuildList()。請注意代碼的順序,先讀入紋理,然后建立顯示列表,這樣當我們建立顯示列表的時候就可以將紋理貼到立方體上了。 int InitGL(GLvoid) // All Setup For OpenGL Goes Here { if (!LoadGLTextures()) // Jump To Texture Loading Routine { return FALSE; // If Texture Didn’t Load Return FALSE } BuildLists(); // Jump To The Code That Creates Our Display Lists glEnable(GL_TEXTURE_2D); // Enable Texture Mapping glShadeModel(GL_SMOOTH); // Enable Smooth Shading glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background glClearDepth(1.0f); // Depth Buffer Setup glEnable(GL_DEPTH_TEST); // Enables Depth Testing glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do 接下來的三行使燈光有效。Light0一般來說是在顯卡中預先定義過的,如果Light0不工作,把下面那行注釋掉好了。 最后一行的GL_COLOR_MATERIAL使我們可以用顏色來貼紋理。如果沒有這行代碼,紋理將始終保持原來的顏色,glColor3f(r,g,b)就沒有用了。總之這行代碼是很有用的。 glEnable(GL_LIGHT0); // Quick And Dirty Lighting (Assumes Light0 Is Set Up) glEnable(GL_LIGHTING); // Enable Lighting glEnable(GL_COLOR_MATERIAL); // Enable Material Coloring 最后,設置投影校正,返回TURE。 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Nice Perspective Correction return TRUE; // Initialization Went OK 現在來看繪畫?拇搿6允掖永炊際嗆芡反蟮模揮衧in,沒有cos,但仍然看起來很奇怪(相信讀者不會覺得頭大)。首先,按慣例,清除屏幕和深度緩沖。 然后捆綁紋理到立方體上(我知道捆綁這個詞不太專業,但是……)。可以將這行放在顯示列表里,但放在外邊,就可以在任何時候修改它。 int DrawGLScene(GLvoid) // Here’s Where We Do All The Drawing { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer glBindTexture(GL_TEXTURE_2D, texture[0]); // Select The Texture 現在到了真正有趣的地方了。用一個循環,循環變量用于改變Y軸位置,在Y軸上畫5個立方體,所以用從1到5的循環。 for (yloop=1;yloop<6;yloop++) // Loop Through The Y Plane {
另外用一個循環,循環變量用于改變X軸位置。每行上的立方體數目取決于行數,所以循環方式如下。 for (xloop=0;xloop< yloop;xloop++) // Loop Through The X Plane { glLoadIdentity(); // Reset The View 下邊的代碼是移動和旋轉當前坐標系到需要畫出立方體的位置。(原文有很 羅嗦的一大段,相信大家的數學功底都不錯,就不翻譯了) glLoadIdentity(); // Reset The View // Position The Cubes On The Screen glTranslatef(1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f),((6.0f-float(yloop))*2.4f)-7.0f,-20.0f);
glRotatef(45.0f-(2.0f*yloop)+xrot,1.0f,0.0f,0.0f); // Tilt The Cubes Up And Down glRotatef(45.0f+yrot,0.0f,1.0f,0.0f); // Spin Cubes Left And Right 然后在正式畫盒子之前設置顏色。每個盒子用不同的顏色。 glColor3fv(boxcol[yloop-1]); // Select A Box Color 好了,顏色設置好了。現在需要做的就是畫出盒子。不用寫出畫多邊形的代碼,只需要用glCallList(box)命令調用顯示列表。盒子將會用glColor3fv()所設置的顏色畫出來。 glCallList(box); // Draw The Box 然后用另外的顏色畫頂部。搞定。 glColor3fv(topcol[yloop-1]); // Select The Top Color glCallList(top); // Draw The Top } } return TRUE; // Jump Back } 下面的代碼是鍵盤控制的一些東西。 SwapBuffers(hDC); // Swap Buffers (Double Buffering) if (keys[VK_LEFT]) // Left Arrow Being Pressed? { ?? yrot-=0.2f; // If So Spin Cubes Left } if (keys[VK_RIGHT]) // Right Arrow Being Pressed? { yrot+=0.2f; // If So Spin Cubes Right } if (keys[VK_UP]) // Up Arrow Being Pressed? { xrot-=0.2f; // If So Tilt Cubes Up } if (keys[VK_DOWN]) // Down Arrow Being Pressed? { xrot+=0.2f; // If So Tilt Cubes Down } 與以前的指南一樣,我們要確認窗口頂部標題的正確。 if (keys[VK_F1]) // Is F1 Being Pressed? { keys[VK_F1]=FALSE; // If So Make Key FALSE KillGLWindow(); // Kill Our Current Window fullscreen=!fullscreen;// Toggle Fullscreen / Windowed Mode // Recreate Our OpenGL Window if (!CreateGLWindow("NeHe’s Display List Tutorial", 640,480,16,fullscreen)) { return 0; // Quit If Window Was Not Created } } } } }
|