目的: 1. 如何利用显示列表表示直接模式下的函数来组织数据和提高性能。
2. 通过理解使用以及何时使用显示列表来最大限度的优化性能。
显示列表是存储起来用于稍后执行的一组OPENGL 命令。激活一个显示列表后,就按照显示列表中预先排好的次序执行其存储的命令。
大部分的opengl命令即可以存储在显示列表中,也可以在直接模式中直接发送,直接发送的命令将被立即执行。
在程序中可以自由的混合使用显示列表和直接模式。
那么我们为什么要用显示列表呢,肯定有好处的吧? 那是,现在就来告诉你哦: 假如你在程序中要频繁的绘制一个几何物体,那么可以把绘制该几何物体的一系列命令放在
显示列表中,因为一些图形硬件也许会在专用的内存中存储显示列表,或者以一种优化的形式存储数据。
我们来看一个例子呀:
#include <cmath>
#include <gl\glut.h>
#include <Windows.h>
GLuint theTorus;
static void torus(int numc, int numt)
{
double TwoPI = 2 * 3.1415926;
for (int i = 0; i < numc; ++i)
{
glBegin(GL_QUAD_STRIP);
for (int j = 0; j <= numt; ++j)
{
for (int k = 1; k >= 0; --k)
{
double s = (i + k) % numc + 0.5;
double t = j%numt;
double x = (1 + 0.1*cos(s*TwoPI / numc))*cos(t*TwoPI / numt);
double y = (1 + 0.1*cos(s*TwoPI / numc))*sin(t*TwoPI / numt);
double z = 0.1 + sin(s*TwoPI / numc);
glVertex3f(x, y, z);
}
}
glEnd();
}
}
static void init()
{
theTorus = glGenLists(1);
glNewList(theTorus, GL_COMPILE);
torus(8, 25);
glEndList();
glShadeModel(GL_FLAT);
glClearColor(0, 0, 0, 0);
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, .0, .0);
glCallList(theTorus);
glFlush();
}
void reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(30, width / height, 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0, 0, 0, // eyes
0, 0, 0, // center
0, 1, 0); // up
}
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
case 'x':
case 'X':
glRotatef(30, 1.0, 0.0, 0);
glutPostRedisplay();
break;
case 'y':
case 'Y':
glRotatef(30, 0.0, 1.0, 0);
glutPostRedisplay();
break;
case 'i':
case 'I':
glLoadIdentity();
gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
glutPostRedisplay();
break;
case 27:
exit(0);
break;
}
}
int main(int argc, char* argv[])
{
glutInitWindowSize(500, 500);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("CallList");
init();
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
上面的由glNewList 和 glEndList() 函数来定义一个显示列表。 而glNewList 函数的变量是ListName 是一个整数索引,是由 glGenList函数生成,表示该显示列表独一无二。
显示列表的设计准则:
为了优化性能, OPENGL 的显示列表是对命令的缓存而不是动态数据库。
显示列表不会比直接执行其中的命令更慢, 当然显示列表会引进一些额外的开销, 如果一个列表不大,其开销可能超过其带来的优越性。
以下列出优化的可能操作:
1. 矩阵操作, 大部分的矩阵操作要求opengl进行求逆运算。
2. 光栅位图和图像。 所指定的光栅数据格式不可能像硬件那样理想。当编译显示列表时, Opengl可以将数据转变成硬件期望表示法。
3. 光照、材质属性以及光照模型。当在福州的光照条件下绘制场景时, 可能需要改变场景中的物体的材质。材质的设置涉及大量的计算,所以速度很慢。
4.纹理。在OPENGL 1.0版本中,显示列表是管理纹理的主要方法,然而在 1.1以上版本中, 那么应该在纹理对象中存储纹理。
5. 多边形点画模式。
显示列表的缺点:是显示列表内容的不变性。为了优化性能,opengl的显示列表的内容是不可修改的,而且内容也是不可读的。
上面的代码中,我们看到,命名和创建一个显示列表:
因为每一个显示列表都由一个任意的整数来索引。所以我们要保证索引的唯一性, 可以使用GLuint glGenLists (GLsizei range);该函数分配range个相邻的、未曾分配过的显示列表索引, 返回的整数是一个索引值, 如 GLuint Index = glGenLists (3);则Index , Index+1, Index+2都可以用来创建一个新的列表。 如果使用一个已用的显示列表索引, 将自动删除已存在的显示列表。
glNewList(Index, GL_COMPILE); 或者glNewList(Index+1, GL_COMPILE); .。。。。
然后以glEndList()结束。
void glCallList (GLuint list); 来调用显示列表。
可以使用glIsList(Index)函数来判定是否正在被应用。
使用void glDeleteLists (GLuint list, GLsizei range); 删除显示列表,并释放被占用的索引。 如: glDeleteLists(Index, 3);
可以在显示列表 glNewList 和 glEndList 之间多次调用其他索引的glCallList函数。
执行多显示列表:
来看一下例子:
namespace
{
const int PT = 1;
const int STROKE = 2;
const int END = 3;
}
typedef struct charpoint
{
GLfloat x, y;
int type;
}CP;
CP Adata[] = {
{ 0, 0, PT }, { 0, 9, PT }, {1, 10, PT},
{ 4, 10, PT }, { 5, 9, PT }, {5, 0, STROKE},
{ 0, 5, PT }, {5, 5, END}
};
CP Edata[] = {
{ 5, 0, PT }, { 0, 0, PT }, {0, 10, PT},
{ 5, 10, STROKE }, { 0, 5, PT }, {4, 5, END}
};
CP Pdata[] = {
{ 0, 0, PT }, { 0, 10, PT }, {4, 10, PT},
{ 5, 9, PT }, { 5, 6, PT }, { 4, 5, PT }, {0, 5, END}
};
CP Rdata[] = {
{ 0, 1, PT }, {0, 10, PT }, {4, 10, PT},
{ 5, 9, PT }, { 5, 6, PT }, { 4, 5, PT },
{ 0, 5, STROKE }, { 3, 5, PT }, { 5, 0, END }
};
static void drawLetter(CP *l)
{
glBegin(GL_LINE_STRIP);
while (l)
{
switch (l->type)
{
case PT:
glVertex2fv(&l->x);
break;
case STROKE:
glVertex2fv(&l->x);
glEnd();
glBegin(GL_LINE_STRIP);
break;
case END:
glVertex2fv(&l->x);
glEnd();
glTranslatef(8.0, 0, 0);
return;
}
++l;
}
}
static void init()
{
GLuint base;
glShadeModel(GL_FLAT);
base = glGenLists(128);
glListBase(base);
glNewList(base + 'A', GL_COMPILE); drawLetter(Adata);
glEndList();
glNewList(base + 'E', GL_COMPILE); drawLetter(Edata);
glEndList();
glNewList(base + 'P', GL_COMPILE); drawLetter(Pdata);
glEndList();
glNewList(base + 'R', GL_COMPILE); drawLetter(Rdata);
glEndList();
glNewList(base + ' ', GL_COMPILE); glTranslatef(8, 0, 0); glEndList();
}
static void printStrokedString(const std::string& str)
{
GLsizei length = str.length();
glCallLists(length, GL_BYTE, (GLbyte*)(str.data()));
}
我们可以看到调用glCallLists 第三个参数是直接把每个字符值当作索引, 但是我们看到的索引是base+ 字符值, 所以调用了函数
glListBase, 然后指定了他的索引初始值。
void glCallLists (GLsizei n, GLenum type, const GLvoid *lists);
n-------执行n个显示列表
type------指明参数lists中数值的数据类型
lists------要读取的索引值存放的数组指针。