- 实现任务目标:
- 使用纹理贴图,增强可视效果
- 应用坐标变换,实现场景中不同物体重建
- 采用双缓冲技术,实现场景实时绘制
- 具有一定的鼠标、键盘交互功能
- 先放效果
鼠标的交互功能有:右键暂停转动,左键继续转动,滚轮向前放大,向后缩小
- IDE:opengl实现需要库函数。用的编译环境是visual studio。附上一个很好的教程【1】:在vs2017下配置opengl。(vs2019也可以用)
- 一个很好的入门教程【2】:OpenGL入门教程(精)。讲得很仔细,通俗易懂。前几课用到的库都没有超过glut的范围。
- 事实上,对于opengl的实现主要是对于各种库函数的调用,所以对于各种库函数的认知很重要。这里也给出一个很好的教程【3】:OpenGL库函数汇总。
- ok,在看了上面的教程以后肯定对于opengl有了一定认识,尤其是第二个教程中讲解得非常仔细。所以本文接下来的内容是建立在对那个教程的学习基础之上,对一些我在实践中遇到的问题作出补充。
- 下面就进入正文。
- 所包含的头文件目录
1 #include <GL/glut.h> 2 #include <stdlib.h> 3 #include <stdio.h>
- 最基本的功能是当然是创建自己的图形并显示出来,如上图我创建的是日地月系统。需要的函数为display()和main()。
- 这其中很重要的一个知识点就是图像的视图变换/模型变换、投影变换和视口变换。有关这块的内容个人觉得教程【2】中讲得不够清楚,可以参考一些别的教程。比如:OpenGL(六) gluLookAt和gluPerspective函数解析;Opengl---gluLookAt函数详解。
- 这里要介绍一下opengl中的坐标轴。x轴水平向右为正,y轴竖直向上为正,z轴垂直屏幕向外为正。符合右手定则。
- 2020/5/15 13:06:37 对于图像的各种变换做一个小的补充
- 视图变换即设置/改变观察点的位置,可以这么理解,相当于选择一个位置和方向设置一台照相机。针对glLookAt()函数而言,它一共有九个参数,3对坐标值。第一对三维坐标是观察点(照相机)在世界坐标中的位置,第二对三维坐标是被观察点(物体)的位置。从第一对坐标到第二对坐标的向量其实就指定了照相机的方向。比如说人站在台阶上,这是人在世界坐标的位置,然后人可以朝天空看,也可以朝地上看,可以朝北方看,也可以朝南方看。这个方向就是由两对坐标所造成的向量来决定的。第三对坐标是人头部的正向,可以指定人站着看,也可以倒立着看,类似于这样。
- 模型变换则是改变物体本身的位置与方向。用到的函数有glTranslate,glRotate,glScale。这些都是对物体的坐标做变换的,相当于乘以一个变换矩阵。那么这里面有两个需要注意的点。
- 1 变换的顺序是逆向的。也就是说,如果我们写的顺序是先平移再旋转,那么实际得到的结果应该是先旋转再平移。所以我们可以用堆栈来实现。对于堆栈的概念不多作解释了,如果不清楚可以去查数据结构。堆栈用到的函数是glPushMatrix()和glPopMatrix()。用法呢其实就是先声明push,然后按照想要的顺序写好矩阵函数,最后Pop一下,就能得到想要的结果。
- 2 比如连续使用偏移函数,则第二个偏移的结果其实是在第一次作偏移的基础上再做偏移的。那么如果我不想这样算怎么办呢?清空矩阵。用到的函数就是glLoadIdentity()。它的作用是把当前矩阵设置为单位矩阵。一般在开始做变换前都是需要调用一次这个函数的。
- 那么事实上,在OpenGL中,因为视图变换和模型变换的效果是类似的,所以这两个变换放在一个模式里面。在进行这两种变换前,需要声明glMatrixMode(GL_MODEVIEW)。看到这个'Matrix'是不是很眼熟呢?没错,上面讲堆栈函数的时候用到了。相信你们也会有个疑问,如果直接调用堆栈函数,这个堆栈是在哪里的呢?这个堆栈段不需要自己声明了,但它其实是属于这个模式的堆栈段。因为在接下来要将的投影变换的模式下也有自己的堆栈段。所以我也是从这个角度理解为什么要分为这两个模式的原因。
- 投影变换事实上时指定了一个可视空间。相当于你在外部架好了照相机,但你仍然可以在照相机的镜头里设置要不要放大看到的景象。在教程【2】里有有关于这个的图。所以首先我们要声明模式glMatrixMode(GL_PROJECTION),并单位化矩阵glLoadIdentity()。
- 在这个模式里有透视投影和正投影(我们做3D一般用的都是透视投影)。正投影的函数有glOrtho()和gluOrtho2D(),透视投影的函数有glFrustum()和gluPerspective()。我们最常用的当然就是gluPerspective()啦。
- gluPerspective()中的第一个参数是角度,它相当于人的眼皮要睁开多大,也就是2*仰角(仰角=俯角=1/2这个角度)。第二个参数是比例,应该是跟显示的屏幕的宽高比例有关(但是这点我不是很确定,只是暂时这么理解,如果有更好的解释,欢迎在评论区提出)。第三、四个参数则是表示截取的范围,相当于两堵墙,两墙之间的东西能看,墙外的都忽略。这就是透视的意义。
- 那么最后就是视口变换,用到的函数是glViewport()。这个就不多做介绍了。
1 void display(void) 2 { 3 glEnable(GL_DEPTH_TEST); //3、5行代码中跟DEPTH有关的函数是为了在同一个窗口内创建多个图像而不会被后创建的图像覆盖。 4 glClearColor(0, 0, 0, 1); //设置“空”色。之前看到一个很好的解释,白纸是白色的,所以白纸上的“空”色为白色。那么信封上的“空”色就是信封的颜色。 5 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //COLOR的那个参数是清除颜色缓存,设为“空”色 6 7 glMatrixMode(GL_PROJECTION); //投影变换 8 glLoadIdentity(); 9 gluPerspective(60.0, 1, 1.0, 100.0); 10 11 glMatrixMode(GL_MODELVIEW); //视图变换/模型变换 12 glLoadIdentity(); //加载单位矩阵 13 gluLookAt(0.0, 0.0, 60.0, 0, 0, 0, 0.0, 1.0, 0); 15 //太阳 16 glColor3f(1.0, 0, 0); 17 glutSolidSphere(6, 20, 20); 18 //地球 19 glColor3f(0.0, 0, 1.0); 20 glTranslatef(-20.0, 0, 0); //偏移矩阵 21 glutSolidSphere(3, 20, 20); 22 //月球 23 glColor3f(1.0, 1.0, 0); 24 glTranslatef(-6.0, 0, 0); //这里的偏移量是在上面已经偏移的基础上再进行偏移 25 glutSolidSphere(1, 20, 20); 26 27 glutSwapBuffers(); //双缓冲函数用到,相关内容看上面的教程【2】里 28 }
- main()中的函数就不具体解释了,应该都懂
1 int main(int argc, char** argv) 2 { 3 glutInit(&argc, argv); 4 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 5 glutInitWindowSize(500, 500); 6 glutInitWindowPosition(100, 100); 7 glutCreateWindow("name"); 8 glutDisplayFunc(&display); 9 glutMainLoop(); 10 return 0; 11 }
- 现在在现有程序的基础上加入动画需要4步
- 1 加入全局变量
1 static GLfloat angle = 0.0f;
- 2 在display()里面加入旋转的函数。由于效果是让整个画面都转,这句话我选择加在gluLookAt()后面。需要加入的语句已标红。
1 void display(void) 2 { 3 …… …… 4 glMatrixMode(GL_MODELVIEW); 5 glLoadIdentity(); //加载单位矩阵 6 gluLookAt(place_x, 0.0, place_z, 0, 0, 0, 0.0, 1.0, 0); 7 glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋转,改变的是x轴分量 8 9 glColor3f(1.0, 0, 0); 10 …… …… 11 }
- 3 编写myIdle()函数
1 void myIdle(void) 2 { 3 angle += 1.8f; 4 if (angle >= 360.0f) 5 angle = 0.0f; 6 display(); 7 }
- 4 在主函数加入glutIdleFunc(&myIdle);可以加在刚刚的display语句下面。
1 int main(int argc, char** argv) 2 { 3 …… …… 4 glutDisplayFunc(&display); 5 glutIdleFunc(&myIdle); 6 …… …… 7 glutMainLoop(); 8 return 0; 9 }
- ok。接下来就要为我们的程序加上纹理了。首先在网上找了两张星空的网图。而且,为了方便起见,我把它们的格式改成了24位色的bmp图片,尺寸为258*258。
- 至于怎么改格式:1 24位色可以对图片另存为时在下拉菜单里选择。2 修改尺寸可以用win自带的图片编辑器。
- 我的两张照片分别命名为“wall.bmp”,"ground.bmp"。放在源程序的同一个子目录里面
- 有关纹理贴图的详细内容继续参考教程【2】。这里附上我写的程序和说明。
- 一共分为3步。
- 1 搭建矩形框架【对我的程序来说相当于有一个支架,然后把按照点对点的方式纹理图贴上去】
- 在这一步中先只写上矩形各个点的坐标,为后面建立矩形做准备。
1 //全局变量 2 static const GLfloat vertex_list[][3] = { 3 - 15.0f, -20.0f, -10.0f, //事实上6、7两个点是用不到的,作为完整性就一起写了。贴图只在背面和底面贴了图,为了更好的演示效果。 4 40.0f, -20.0f, -10.0f, 5 40.0f, 20.0f, -10.0f, 6 -15.0f, 20.0f, -10.0f, 7 -15.0f, -20.0f, 10.0f, 8 40.0f, -20.0f, 10.0f, 9 -15.0f, 20.0f, 10.0f, 10 40.0f, 20.0f, 10.0f, 11 };
- 2 将纹理图读入。写了一个读文件的函数,还是参考之前的教程【2】,不多作解释了。以及一个参考教程:OpenGL(十二) 纹理映射(贴图)
1 //全局变量 2 #define BMP_Header_Length 54 3 //函数 4 // 函数power_of_two用于判断一个整数是不是2的整数次幂 5 int power_of_two(int n) 6 { 7 if (n <= 0) 8 return 0; 9 return (n & (n - 1)) == 0; 10 } 11 /* 函数load_texture 12 * 读取一个BMP文件作为纹理 13 * 如果失败,返回0,如果成功,返回纹理编号 14 */ 15 GLuint load_texture(const char* file_name) 16 { 17 GLint width, height, total_bytes; 18 GLubyte* pixels = 0; 19 GLuint last_texture_ID = 0, texture_ID = 0; 20 21 // 打开文件,如果失败,返回 22 FILE* pFile; 23 errno_t err; 24 err = fopen_s(&pFile, file_name, "rb"); //在vs中使用fopen_s()函数的示例。 25 if (!pFile) exit(0); 26 27 // 读取文件中图象的宽度和高度 28 fseek(pFile, 0x0012, SEEK_SET); 29 fread(&width, sizeof(width), 1, pFile); 30 fread(&height, sizeof(height), 1, pFile); 31 fseek(pFile, BMP_Header_Length, SEEK_SET); 32 33 // 计算每行像素所占字节数,并根据此数据计算总像素字节数 34 { 35 GLint line_bytes = width * 3; 36 while (line_bytes % 4 != 0) 37 ++line_bytes; 38 total_bytes = line_bytes * height; 39 } 40 41 // 根据总像素字节数分配内存 42 pixels = (GLubyte*)malloc(total_bytes); 43 if (pixels == 0) 44 { 45 fclose(pFile); 46 return 0; 47 } 48 49 // 读取像素数据 50 if (fread(pixels, total_bytes, 1, pFile) <= 0) 51 { 52 free(pixels); 53 fclose(pFile); 54 return 0; 55 } 56 57 // 对就旧版本的兼容,如果图象的宽度和高度不是的整数次方,则需要进行缩放 58 // 若图像宽高超过了OpenGL规定的最大值,也缩放 59 { 60 GLint max; 61 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); 62 if (!power_of_two(width) 63 || !power_of_two(height) 64 || width > max 65 || height > max) 66 { 67 const GLint new_width = 256; 68 const GLint new_height = 256; // 规定缩放后新的大小为边长的正方形 69 GLint new_line_bytes, new_total_bytes; 70 GLubyte* new_pixels = 0; 71 72 // 计算每行需要的字节数和总字节数 73 new_line_bytes = new_width * 3; 74 while (new_line_bytes % 4 != 0) 75 ++new_line_bytes; 76 new_total_bytes = new_line_bytes * new_height; 77 78 // 分配内存 79 new_pixels = (GLubyte*)malloc(new_total_bytes); 80 if (new_pixels == 0) 81 { 82 free(pixels); 83 fclose(pFile); 84 return 0; 85 } 86 87 // 进行像素缩放 88 gluScaleImage(GL_RGB, 89 width, height, GL_UNSIGNED_BYTE, pixels, 90 new_width, new_height, GL_UNSIGNED_BYTE, new_pixels); 91 92 // 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height 93 free(pixels); 94 pixels = new_pixels; 95 width = new_width; 96 height = new_height; 97 } 98 } 99 100 // 分配一个新的纹理编号 101 glGenTextures(1, &texture_ID); 102 if (texture_ID == 0) 103 { 104 free(pixels); 105 fclose(pFile); 106 return 0; 107 } 108 109 // 绑定新的纹理,载入纹理并设置纹理参数 110 // 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复 111 GLint lastTextureID = last_texture_ID; 112 glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID); 113 glBindTexture(GL_TEXTURE_2D, texture_ID); 114 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 115 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 116 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 117 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 118 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 119 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, 120 GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels); 121 glBindTexture(GL_TEXTURE_2D, lastTextureID); //恢复之前的纹理绑定 122 free(pixels); 123 return texture_ID; 124 }
- 3 在display()中打开状态机->读取纹理图片->搭起矩形框架->贴图->关闭状态机。
- 这里踩过的坑就是关于状态机的开闭问题。如果没有关闭状态机,显示的图像中之前画的几个球都是全黑的。这是因为纹理贴图会干扰别的颜色。
- 其中用到的glTexCoord2f()函数可以参考百度的这个示例。
1 //全局变量 2 GLuint texGround; 3 GLuint texWall; 4 //函数补充 5 void display(void) 6 { 7 …… ……//之前内容的后面加入一下内容 8 glEnable(GL_TEXTURE_2D); //开启状态机 9 texGround = load_texture("ground.bmp"); 10 texWall = load_texture("wall.bmp"); 11 12 //绘制底面 13 glBindTexture(GL_TEXTURE_2D, texGround); 14 glBegin(GL_QUADS); 15 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[4]); //点对点 16 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[5]); 17 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[1]); 18 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[0]); 19 glEnd(); 20 //绘制立面 21 glBindTexture(GL_TEXTURE_2D, texWall); 22 glBegin(GL_QUADS); 23 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[0]); 24 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[1]); 25 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[2]); 26 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[3]); 27 glEnd(); 28 glDisable(GL_TEXTURE_2D);//关闭状态机 29 glutSwapBuffers(); 30 }
- ok。那么到这里我们已经完成了纹理贴图、双缓冲绘制和场景重建的任务啦。接下来还有鼠标交互的任务。那么在这里先插入一个新的函数讲解:reshape()。
- 关于reshape()的原理呢可以去查查资料。我说说我的理解吧。简单来说呢就是在你显示窗口时,如果你拉动边框,窗口内的图像不会随着你拉动而改变。
- 附上一个简单的图片示例。
可以看到在右边的图中,我拉动了窗口的边框,则图像的形状也改变了。
- reshape()就能在窗体大小被改变时,窗口大小不变,图像比例也不变。
- 那么同样的,完成这个功能需要2步。
- 1 写一个reshape()函数
1 void reshape(int w, int h) 2 { 3 glViewport(0, 0, 500, 500); 4 glMatrixMode(GL_PROJECTION); 5 glLoadIdentity(); 6 gluPerspective(60.0, 1, 1, 100.0); 7 glMatrixMode(GL_MODELVIEW); 8 glLoadIdentity(); 9 gluLookAt(0, 0.0, 60.0, 0, 0, 0, 0.0, 1.0, 0); 10 }
- 2 在main函数中加入一句
1 int main(int argc, char** argv) 2 { 3 …… …… 4 glutDisplayFunc(&display); 5 glutReshapeFunc(&reshape); 6 glutIdleFunc(&myIdle); 7 …… …… 8 }
- ok。最后的最后,要完成鼠标的交互了。
- 我所设置的鼠标的功能包括:右键暂停、左键继续;滚轮向上放大,滚轮向下缩小。
- 前两个改变的是转过的角度angle,后两个则跟我们所建立的视图模型,也就是之前用过glLookAt()函数的参数有关。
- 对于鼠标交互用到的函数及参量是void myMouse(int button, int state, int x, int y);关于这个更多的信息可以自行查找。
- 那么同样的,完成这个需要2 / 3步。但是我分为两个部分来讲。首先是对于右键暂停和左键继续的部分。
- 1 之前的显示函数里已经有了一个angle变量用来控制角度,所以我们要做的就是停掉这个angle变量的自增,所以我们要停用myIdle函数。
1 void myMouse(int button, int state, int x, int y) 2 { 3 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) 4 { 5 glutIdleFunc(&myIdle); 6 } 7 if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) 8 { 9 glutIdleFunc(NULL); 10 } 11 }
- 2 在主函数中加入语句。
1 int main(int argc, char** argv) 2 { 3 …… …… 4 glutCreateWindow("name"); 5 glutMouseFunc(&myMouse); 6 glutDisplayFunc(&display); 7 …… …… 8 }
- 对于缩放.
- 1 因为要涉及到之前显示函数display()中的glLookAt()的改变,所以我们将其中的值设为全局变量。
1 //全局变量 2 static float place_z = 60.0f; 3 static float place_x = 0.0f; 4 //修改函数参数 5 void display(void) 6 { 7 …… …… 8 gluLookAt(place_x, 0.0, place_z, 0, 0, 0, 0.0, 1.0, 0); 9 glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋转 10 …… …… 11 }
- 2 在之前的鼠标的函数中加入对滚轮的控制语句
1 //全局变量 2 #define GLUT_WHEEL_UP 3 3 #define GLUT_WHEEL_DOWN 4 4 //函数中 5 void myMouse(int button, int state, int x, int y) 6 { 7 …… …… 8 if (state == GLUT_UP && button == GLUT_WHEEL_UP) 9 { 10 glutReshapeFunc(NULL); 11 place_z -= 5.0; 12 display(); 13 } 14 if (state == GLUT_UP && button == GLUT_WHEEL_DOWN) 15 { 16 glutReshapeFunc(NULL); 17 place_z += 5.0; 18 display(); 19 } 20 }
- 这样就ok啦。到这里就完成了一开始提出四个目标以及一个reshape()函数。效果就如最开始的gif动画一样。
- 这里还需要提到的一点是,动画播放的速度在不同的cpu里是不一样的,如果太快或太慢可以通过myIdle函数的angle自增的大小来控制。
- 为了避免混乱,最后附上完整的源代码。
1 #include <GL/glut.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 5 static const GLfloat vertex_list[][3] = { 6 - 15.0f, -20.0f, -10.0f, 7 40.0f, -20.0f, -10.0f, 8 40.0f, 20.0f, -10.0f, 9 -15.0f, 20.0f, -10.0f, 10 -15.0f, -20.0f, 10.0f, 11 40.0f, -20.0f, 10.0f, 12 -15.0f, 20.0f, 10.0f, 13 40.0f, 20.0f, 10.0f, 14 }; 15 GLuint texGround; 16 GLuint texWall; 17 18 #define BMP_Header_Length 54 19 static GLfloat angle = 0.0f; 20 static float place_z = 60.0f; 21 static float place_x = 0.0f; 22 #define GLUT_WHEEL_UP 3 23 #define GLUT_WHEEL_DOWN 4 24 25 // 函数power_of_two用于判断一个整数是不是2的整数次幂 26 int power_of_two(int n) 27 { 28 if (n <= 0) 29 return 0; 30 return (n & (n - 1)) == 0; 31 } 32 33 /* 函数load_texture 34 * 读取一个BMP文件作为纹理 35 * 如果失败,返回0,如果成功,返回纹理编号 36 */ 37 GLuint load_texture(const char* file_name) 38 { 39 GLint width, height, total_bytes; 40 GLubyte* pixels = 0; 41 GLuint last_texture_ID = 0, texture_ID = 0; 42 43 // 打开文件,如果失败,返回 44 FILE* pFile; 45 errno_t err; 46 err = fopen_s(&pFile, file_name, "rb"); 47 if (!pFile) exit(0); 48 49 // 读取文件中图象的宽度和高度 50 fseek(pFile, 0x0012, SEEK_SET); 51 fread(&width, sizeof(width), 1, pFile); 52 fread(&height, sizeof(height), 1, pFile); 53 fseek(pFile, BMP_Header_Length, SEEK_SET); 54 55 // 计算每行像素所占字节数,并根据此数据计算总像素字节数 56 { 57 GLint line_bytes = width * 3; 58 while (line_bytes % 4 != 0) 59 ++line_bytes; 60 total_bytes = line_bytes * height; 61 } 62 63 // 根据总像素字节数分配内存 64 pixels = (GLubyte*)malloc(total_bytes); 65 if (pixels == 0) 66 { 67 fclose(pFile); 68 return 0; 69 } 70 71 // 读取像素数据 72 if (fread(pixels, total_bytes, 1, pFile) <= 0) 73 { 74 free(pixels); 75 fclose(pFile); 76 return 0; 77 } 78 79 // 对就旧版本的兼容,如果图象的宽度和高度不是的整数次方,则需要进行缩放 80 // 若图像宽高超过了OpenGL规定的最大值,也缩放 81 { 82 GLint max; 83 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); 84 if (!power_of_two(width) 85 || !power_of_two(height) 86 || width > max 87 || height > max) 88 { 89 const GLint new_width = 256; 90 const GLint new_height = 256; // 规定缩放后新的大小为边长的正方形 91 GLint new_line_bytes, new_total_bytes; 92 GLubyte* new_pixels = 0; 93 94 // 计算每行需要的字节数和总字节数 95 new_line_bytes = new_width * 3; 96 while (new_line_bytes % 4 != 0) 97 ++new_line_bytes; 98 new_total_bytes = new_line_bytes * new_height; 99 100 // 分配内存 101 new_pixels = (GLubyte*)malloc(new_total_bytes); 102 if (new_pixels == 0) 103 { 104 free(pixels); 105 fclose(pFile); 106 return 0; 107 } 108 109 // 进行像素缩放 110 gluScaleImage(GL_RGB, 111 width, height, GL_UNSIGNED_BYTE, pixels, 112 new_width, new_height, GL_UNSIGNED_BYTE, new_pixels); 113 114 // 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height 115 free(pixels); 116 pixels = new_pixels; 117 width = new_width; 118 height = new_height; 119 } 120 } 121 122 // 分配一个新的纹理编号 123 glGenTextures(1, &texture_ID); 124 if (texture_ID == 0) 125 { 126 free(pixels); 127 fclose(pFile); 128 return 0; 129 } 130 131 // 绑定新的纹理,载入纹理并设置纹理参数 132 // 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复 133 GLint lastTextureID = last_texture_ID; 134 glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID); 135 glBindTexture(GL_TEXTURE_2D, texture_ID); 136 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 137 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 138 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 139 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 140 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 141 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, 142 GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels); 143 glBindTexture(GL_TEXTURE_2D, lastTextureID); //恢复之前的纹理绑定 144 free(pixels); 145 return texture_ID; 146 } 147 void display(void) 148 { 149 glEnable(GL_DEPTH_TEST); 150 glClearColor(0, 0, 0, 1); 151 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 152 153 glMatrixMode(GL_PROJECTION); 154 glLoadIdentity(); 155 gluPerspective(60.0, 1, 1.0, 100.0); 156 157 glMatrixMode(GL_MODELVIEW); 158 glLoadIdentity(); //加载单位矩阵 159 gluLookAt(place_x, 0.0, place_z, 0, 0, 0, 0.0, 1.0, 0); 160 glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋转 161 162 glColor3f(1.0, 0, 0); 163 glutSolidSphere(6, 20, 20); 164 165 glColor3f(0.0, 0, 1.0); 166 glTranslatef(-20.0, 0, 0); 167 glutSolidSphere(3, 20, 20); 168 169 glColor3f(1.0, 1.0, 0); 170 glTranslatef(-6.0, 0, 0); 171 glutSolidSphere(1, 20, 20); 172 173 glEnable(GL_TEXTURE_2D); 174 texGround = load_texture("ground.bmp"); 175 texWall = load_texture("wall.bmp"); 176 177 //绘制底面 178 glBindTexture(GL_TEXTURE_2D, texGround); 179 glBegin(GL_QUADS); 180 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[4]); 181 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[5]); 182 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[1]); 183 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[0]); 184 glEnd(); 185 //绘制立面 186 glBindTexture(GL_TEXTURE_2D, texWall); 187 glBegin(GL_QUADS); 188 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[0]); 189 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[1]); 190 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[2]); 191 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[3]); 192 glEnd(); 193 glDisable(GL_TEXTURE_2D); 194 glutSwapBuffers(); 195 } 196 void myIdle(void) 197 { 198 angle += 1.8f; 199 if (angle >= 360.0f) 200 angle = 0.0f; 201 display(); 202 } 203 void myMouse(int button, int state, int x, int y) 204 { 205 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) 206 { 207 glutIdleFunc(&myIdle); 208 } 209 if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) 210 { 211 glutIdleFunc(NULL); 212 } 213 if (state == GLUT_UP && button == GLUT_WHEEL_UP) 214 { 215 glutReshapeFunc(NULL); 216 place_z -= 5.0; 217 display(); 218 } 219 if (state == GLUT_UP && button == GLUT_WHEEL_DOWN) 220 { 221 glutReshapeFunc(NULL); 222 place_z += 5.0; 223 display(); 224 } 225 } 226 227 void reshape(int w, int h) 228 { 229 glViewport(0, 0, 500, 500); 230 glMatrixMode(GL_PROJECTION); 231 glLoadIdentity(); 232 gluPerspective(60.0, 1, 1, 100.0); 233 glMatrixMode(GL_MODELVIEW); 234 glLoadIdentity(); 235 gluLookAt(0, 0.0, 60.0, 0, 0, 0, 0.0, 1.0, 0); 236 } 237 int main(int argc, char** argv) 238 { 239 glutInit(&argc, argv); 240 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 241 glutInitWindowSize(500, 500); 242 glutInitWindowPosition(100, 100); 243 glutCreateWindow("name"); 244 glutMouseFunc(&myMouse); 245 glutDisplayFunc(&display); 246 glutReshapeFunc(&reshape); 247 glutIdleFunc(&myIdle); 248 glutMainLoop(); 249 return 0; 250 }