普通模式 顶点数组模式 显示列表模式
顶点数组
观察bunny的绘制,发现它是由法线向量和顶点信息构成的,这两个信息分别由normals数组和vertexs数组维护,它们存储了bunny的具体信息。
此外,还有一个数组face_indices,它是作为索引数组存在的,里面存储的是下标信息,用于指向normals数组和vertex数组的信息。
在顶点数组中,我们只需告诉OpenGL这三个数组分别是什么,就可以完成快速绘制,减少函数调用消耗,让数据一次性传输,而不是一个个传输,而且数据可以是非静态的。
使用顶点数组的时候,我们需要先开启特定的顶点数组,然后再指定具体的顶点坐标与下标索引。
开启顶点数组:
glEnableClientState(GL_VERTEX_ARRAY);//启用顶点坐标数组
glEnableClientState(GL_NORMAL_ARRAY);//启用法线向量数组
指定顶点数组:
glVertexPointer(3,GL_FLOAT, 0, vertices);//指定顶点
glNormalPointer(GL_FLOAT, 0, normals);//指定法向量
指定下标数组:
glDrawElements(shape_type,count , array_type, array);
显示列表
显示列表的使用比较简单,生成显示列表并分配后,把需要加载到显示列表的物体代码写上即可。
lid = glGenLists(1);
glNewList(lid, GL_COMPILE);
……
glEndList();
拾取
拾取也就是判断是否点中某个物体的方法。它和鼠标的操作相关,所以它是基于鼠标回调事件的,在此之前,我们需要先指定我们需要拾取哪些物体,以及它们的编号。
指定拾取物体在绘制物体时完成,首先,需要进行初始化:
glInitNames(); // 初始化名字
具体指定的代码如下:
glPushName(Name1);
draw1();
glPopName();
glPushName(Name2);
draw2();
glPopName();
也可以用glLoadName(Name),它等价于glPopName(),glLoadName(Name)。
glPushName和glPushMatrix一样,是可以嵌套调用的,在这次实验我们没有使用这个功能。
在渲染模式下,以上函数调用都会被忽略,所以可以实现两种模式共用一个函数而不发生冲突。
然后,我们关注回调事件,当我们鼠标左键点击了屏幕时,触发鼠标回调事件,开始我们需要指定选中的结果存储到哪个数组,并且需要给出数组大小:
glSelectBuffer(BUFSIZE, selectBuf);
之后,开启选择模式
glRenderMode(GL_SELECT);
在选择模式下,我们先把矩阵模式切换为投影,然后指定投影方式,设置选择矩阵,这时候再进行重绘,重绘过程中又要把矩阵模式切换为模型,之后再切换为投影。这时,OpenGL会返回选中物体的下标以及深度信息等。
gluPickMatrix((GLdouble)x,(GLdouble)(viewport[3] -y), 1, 1, viewport);//设置选择矩阵
由于为3D图形,所以一次点击可能点中多个物体,我们需要按照深度排序,然后得到深度最小的物体作为当前选中物体,对于选中物体,把物体的颜色数组进行修改,这时就完成了拾取物体并修改颜色的功能。
对于颜色,我们采取了一开始产生随机数生成。
实验数据记录和处理
模式 |
FPS |
Naive |
45 |
Vertex array |
190 |
Display list |
65 |
可以看到,显示列表略胜于普通模式,顶点数组明显优于前两者。
// glutEx1.cpp : 定义控制台应用程序的入口点。 // #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #include "glut.h" #define BUFSIZE 100 float whRatio; float fTranslate; float fRotate = 156.5f; float fScale = 1.0f; // set inital scale value to 1.0f bool bPersp = true; bool bAnim = false; bool bWire = false; bool isTableWhite = true; bool isBunnyWhite[19]; int wHeight = 0; int wWidth = 0; GLfloat bunny_diffuse[19][3]; GLfloat table_diffuse[3]; GLint dl = 0; GLenum mode = GL_RENDER; GLuint selectBuf[BUFSIZE]; void Draw_Leg(); int drawMode = 0; extern void drawNaive(); extern void drawVA(); extern GLint Gen3DObjectList(); void drawDL() { glCallList(dl); } void drawBunny() { glRotatef(90, 1, 0, 0); glScalef(3, 3, 3); if (drawMode == 0) drawNaive(); else if (drawMode == 1) drawVA(); else drawDL(); } void Draw_Desk(); void setColor(int index) { if (isBunnyWhite[index] == false) { glMaterialfv(GL_FRONT, GL_DIFFUSE, bunny_diffuse[index]);//设置多边形正面漫反射属性 } else { GLfloat mat_diffuse[] = { 1.0f,1.0f,1.0f }; glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);//设置多边形正面漫反射属性 } } void Draw_Triangle() // This function draws a triangle with RGB colors { glInitNames();//初始化名字 glPushName(0);//初始化名字栈 glLoadName(1);//加载名字 setColor(1);//设置颜色 glPushMatrix(); glTranslatef(-1, -1, 5.5); drawBunny(); glPopMatrix(); glLoadName(2);//加载名字 setColor(2);//设置颜色 glPushMatrix(); glTranslatef(0, -1, 5.5); drawBunny(); glPopMatrix(); glLoadName(3);//加载名字 setColor(3);//设置颜色 glPushMatrix(); glTranslatef(1, -1, 5.5); drawBunny(); glPopMatrix(); glLoadName(4);//加载名字 setColor(4);//设置颜色 glPushMatrix(); glTranslatef(-1, 1, 5.5); drawBunny(); glPopMatrix(); glLoadName(5);//加载名字 setColor(5);//设置颜色 glPushMatrix(); glTranslatef(0, 1, 5.5); drawBunny(); glPopMatrix(); glLoadName(6);//加载名字 setColor(6);//设置颜色 glPushMatrix(); glTranslatef(1, 1, 5.5); drawBunny(); glPopMatrix(); glLoadName(7);//加载名字 setColor(7);//设置颜色 glPushMatrix(); glTranslatef(-1, 0, 5.5); drawBunny(); glPopMatrix(); glLoadName(8);//加载名字 setColor(8);//设置颜色 glPushMatrix(); glTranslatef(0, 0, 5.5); drawBunny(); glPopMatrix(); glLoadName(9);//加载名字 setColor(9);//设置颜色 glPushMatrix(); glTranslatef(1, 0, 5.5); drawBunny(); glPopMatrix(); glLoadName(10);//加载名字 setColor(10);//设置颜色 glPushMatrix(); glTranslatef(-1, -1, 7.5); drawBunny(); glPopMatrix(); glLoadName(11);//加载名字 setColor(11);//设置颜色 glPushMatrix(); glTranslatef(0, -1, 7.5); drawBunny(); glPopMatrix(); glLoadName(12);//加载名字 setColor(12);//设置颜色 glPushMatrix(); glTranslatef(1, -1, 7.5); drawBunny(); glPopMatrix(); glLoadName(13);//加载名字 setColor(13);//设置颜色 glPushMatrix(); glTranslatef(-1, 1, 7.5); drawBunny(); glPopMatrix(); glLoadName(14);//加载名字 setColor(14);//设置颜色 glPushMatrix(); glTranslatef(0, 1, 7.5); drawBunny(); glPopMatrix(); glLoadName(15);//加载名字 setColor(15);//设置颜色 glPushMatrix(); glTranslatef(1, 1, 7.5); drawBunny(); glPopMatrix(); glLoadName(16);//加载名字 setColor(16);//设置颜色 glPushMatrix(); glTranslatef(-1, 0, 7.5); drawBunny(); glPopMatrix(); glLoadName(17);//加载名字 setColor(17);//设置颜色 glPushMatrix(); glTranslatef(0, 0, 7.5); drawBunny(); glPopMatrix(); glLoadName(18);//加载名字 setColor(18);//设置颜色 glPushMatrix(); glTranslatef(1, 0, 7.5); drawBunny(); glPopMatrix(); glLoadName(19);//加载名字 Draw_Desk(); glPopName(); } void Draw_Desk() { if (isTableWhite == true) { GLfloat mat_diffuse[] = { 1.0f,1.0f,1.0f }; glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);//设置多边形正面漫反射属性 } else { glMaterialfv(GL_FRONT, GL_DIFFUSE, table_diffuse);//设置多边形正面漫反射属性 } glPushMatrix(); glTranslatef(0, 0, 3.5); glScalef(5, 4, 1); glutSolidCube(1.0); glPopMatrix(); glPushMatrix(); glTranslatef(1.5, 1, 1.5); Draw_Leg(); glPopMatrix(); glPushMatrix(); glTranslatef(-1.5, 1, 1.5); Draw_Leg(); glPopMatrix(); glPushMatrix(); glTranslatef(1.5, -1, 1.5); Draw_Leg(); glPopMatrix(); glPushMatrix(); glTranslatef(-1.5, -1, 1.5); Draw_Leg(); glPopMatrix(); } void Draw_Leg() { glScalef(1, 1, 3); glutSolidCube(1.0); } void updateView(int width, int height) { glViewport(0, 0, width, height);//设置视窗大小 glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影 glLoadIdentity(); //初始化矩阵为单位矩阵 whRatio = (GLfloat)width / (GLfloat)height; //设置显示比例 if (bPersp) { gluPerspective(45.0f, whRatio, 0.1f, 100.0f); //透视投影 //glFrustum(-3, 3, -3, 3, 3,100); } else { glOrtho(-3, 3, -3, 3, -100, 100); //正投影 } glMatrixMode(GL_MODELVIEW); //设置矩阵模式为模型 } void reshape(int width, int height) { if (height == 0) //如果高度为0 { height = 1; //让高度为1(避免出现分母为0的现象) } wHeight = height; wWidth = width; updateView(wHeight, wWidth); //更新视角 } void idle() { glutPostRedisplay(); } float center[] = {0, -0.8, -6}; float eye[] = {0, 1.2, 2}; void redraw(); //鼠标点击事件 void mouse(int mouse, int status, int x, int y) { //左键点击 鼠标按下状态时 if (mouse == GLUT_LEFT_BUTTON && status == GLUT_DOWN) { //存储视角 GLint viewport[4]; glSelectBuffer(BUFSIZE, selectBuf);//选择返回数据的数组 glGetIntegerv(GL_VIEWPORT, viewport);//得到视角 glRenderMode(GL_SELECT);//进入选择模式 glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影 glPushMatrix();//矩阵入栈 glLoadIdentity();//初始化为单位矩阵 gluPickMatrix((GLdouble)x, (GLdouble)(viewport[3] - y), 1, 1, viewport);//设置选择矩阵 if (bPersp) { gluPerspective(45.0f, whRatio, 0.1f, 100.0f); //透视投影 //glFrustum(-3, 3, -3, 3, 3,100); } else { glOrtho(-3, 3, -3, 3, -100, 100); //正投影 } redraw(); //重绘 int hits = glRenderMode(GL_RENDER); //进入渲染模式,得到选中物体个数 if (hits >= 1) //如果选中物体个数大于1 { int choose = selectBuf[3]; //得到选中物体名字 int depth = selectBuf[1]; //得到选中物体深度 printf("2 = %d\n", selectBuf[2]); printf("4 = %d\n", selectBuf[4]); for (int i = 0; i < hits; i++) { if (selectBuf[i * 4 + 1] < (GLuint)depth)//获取深度最小的物体(selectBuff是按照ID从小到大排列的) { choose = selectBuf[i * 4 + 3]; depth = selectBuf[i * 4 + 1]; } } //更改选中物体的颜色 if (choose >= 1 && choose <= 18) { isBunnyWhite[choose] = !isBunnyWhite[choose]; } else if (choose == 19) { isTableWhite = !isTableWhite; } } //设置矩阵模式为投影 glMatrixMode(GL_PROJECTION); glPopMatrix();//抛出矩阵 //设置矩阵模式为模型 glMatrixMode(GL_MODELVIEW); } } void key(unsigned char k, int x, int y) { switch(k) { case 27: case 'q': {exit(0); break; } //退出 case 'p': {bPersp = !bPersp; break; } //切换投影模式 case ' ': {bAnim = !bAnim; break;} //旋转 case 'o': {bWire = !bWire; break;} //切换线、面显示 case '0': {drawMode++; drawMode %= 3; break;}//切换渲染方式 case 'a': { //左移 eye[0] += 0.2f; center[0] += 0.2f; break; } case 'd': { //右移 eye[0] -= 0.2f; center[0] -= 0.2f; break; } case 'w': { //上移 eye[1] -= 0.2f; center[1] -= 0.2f; break; } case 's': { //下移 eye[1] += 0.2f; center[1] += 0.2f; break; } case 'z': { //前移 eye[2] -= 0.2f; center[2] -= 0.2f; break; } case 'c': { //后移 eye[2] += 0.2f; center[2] += 0.2f; break; } } updateView(wHeight, wWidth); //更新视角 } void getFPS() { static int frame = 0, time, timebase = 0; static char buffer[256];//字符串缓冲区 char mode[64];//模式 if (drawMode == 0) //普通 strcpy(mode, "naive"); else if (drawMode == 1) //顶点数组 strcpy(mode, "vertex array"); else //显示列表 strcpy(mode, "display list"); frame++; time=glutGet(GLUT_ELAPSED_TIME); //返回两次调用glutGet(GLUT_ELAPSED_TIME)的时间间隔,单位为毫秒 if (time - timebase > 1000) {//时间间隔差大于1000ms时 sprintf(buffer,"FPS:%4.2f %s", frame*1000.0/(time-timebase), mode);//写入buffer中 timebase = time; //上一次的时间间隔 frame = 0; } glutSetWindowTitle(buffer);//设置窗口标题 } void redraw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓存 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //初始化矩阵为单位矩阵 gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0); // 场景(0,0,0)的视点中心 (0,5,50),Y轴向上 if (bWire) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //设置多边形绘制模式:正反面,线型 } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //设置多边形绘制模式:正反面,填充 } glEnable(GL_DEPTH_TEST);//开启深度测试 glEnable(GL_LIGHTING); //开启光照模式 GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };//白色 GLfloat light_pos[] = {5,5,5,1};//光源位置 glLightfv(GL_LIGHT0, GL_POSITION, light_pos);//光源位置 glLightfv(GL_LIGHT0, GL_AMBIENT, white);//环境光白色 glEnable(GL_LIGHT0);//开启第0号光源 glRotatef(fRotate, 0, 1.0f, 0); //旋转 glRotatef(-90, 1, 0, 0); glScalef(0.2, 0.2, 0.2);//缩放 Draw_Triangle();//绘制场景 if (bAnim) fRotate += 0.5f;//旋转因子改变 getFPS();//得到fps glutSwapBuffers();//交换缓冲区 } void generateColor() { for (int i = 1; i <= 18; i++) { //随机生成兔子颜色 GLfloat x = (float)(rand() % 1001) * 0.001f; GLfloat y = (float)(rand() % 1001) * 0.001f; GLfloat z = (float)(rand() % 1001) * 0.001f; bunny_diffuse[i][0] = x; bunny_diffuse[i][1] = y; bunny_diffuse[i][2] = z; } //随机生成桌子颜色 GLfloat x = (float)(rand() % 1001) * 0.001f; GLfloat y = (float)(rand() % 1001) * 0.001f; GLfloat z = (float)(rand() % 1001) * 0.001f; table_diffuse[0] = x; table_diffuse[1] = y; table_diffuse[2] = z; } int main(int argc, char *argv[]) { srand(unsigned(time(nullptr))); glutInit(&argc, argv);//对glut的初始化 glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); //初始化显示模式:RGB颜色模型,深度测试,双缓冲 glutInitWindowSize(480, 480);//设置窗口大小 int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题 glutDisplayFunc(redraw); //注册绘制回调函数 glutReshapeFunc(reshape); //注册重绘回调函数 glutKeyboardFunc(key); //注册按键回调函数 glutIdleFunc(idle);//注册全局回调函数:空闲时调用 glutMouseFunc(mouse); //注册鼠标回调函数 memset(isBunnyWhite, true, sizeof(isBunnyWhite));//初始化兔子颜色数组 generateColor();//随机生成兔子和桌子的颜色 dl = Gen3DObjectList();//生成显示列表 glutMainLoop(); // glut事件处理循环 return 0; }
#ifdef _WIN32 #include <windows.h> #endif #include <GL/gl.h> #include <GL/glu.h> #include <stdio.h> #pragma warning(disable: 4305) // 8146 Verticies // 8146 Normals // 16301 Triangles //数组里的数据非常害怕,它们吓得躲了起来 short face_indicies[16301][3]; GLfloat normals [8146][3]; GLfloat vertices [8146][3]; #define STR2(x) #x #define STR(x) STR2(x) #define MSG(desc) message(__FILE__ "(" STR(__LINE__) ") : ------- " desc " -------") void drawVA() { glEnableClientState(GL_VERTEX_ARRAY);//启用顶点坐标数组 glEnableClientState(GL_NORMAL_ARRAY);//启用法线向量数组 glVertexPointer(3, GL_FLOAT, 0, vertices); glNormalPointer(GL_FLOAT, 0, normals); glDrawElements(GL_TRIANGLES, sizeof(face_indicies) / sizeof(face_indicies[0])*3, GL_UNSIGNED_SHORT, face_indicies); } void drawNaive() { glBegin (GL_TRIANGLES); for(int i=0;i<(sizeof(face_indicies)/sizeof(face_indicies[0]));i++) { for(int j=0;j<3;j++) { int idx=face_indicies[i][j]; glNormal3fv(&normals[idx][0]); glVertex3fv(&vertices[idx][0]); } } glEnd (); } GLint Gen3DObjectList() { GLint lid = glGenLists(1); //生成一个空的显示列表 glNewList(lid, GL_COMPILE); // 用于创建和替换一个显示列表函数原型 // 指定显示列表的名称,编译模式:只编译 glBegin(GL_TRIANGLES); for (int i = 0; i<(sizeof(face_indicies) / sizeof(face_indicies[0])); i++) { for (int j = 0; j<3; j++) { int idx = face_indicies[i][j]; glNormal3fv(&normals[idx][0]); glVertex3fv(&vertices[idx][0]); } } glEnd(); glEndList(); return lid; //返回显示列表编号 };