近期学习用opengl库来构建一个3D场景,以及实现场景漫游、粒子系统等效果。终于算是是做了一个3D走迷宫游戏吧。

感觉近期学了好多东西,所以有必要整理整理。

一 实现效果

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjk5OTQyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjk5OTQyNA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

二 实现过程具体解释

   1、3d场景构建

1)光照与材质

通过设置光照与材质。使得场景的显示效果更真实。opengl加光源的方法:

  1. GLfloat light_position[] = {0.0, 80.0, 0.0};
  2. GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0};
  3. glLightfv(GL_LIGHT0, GL_POSITION, light_position);
  4. glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, light_diffuse);
  5. glEnable(GL_LIGHTING);
  6. glEnable(GL_LIGHT0);

加一个光源至少要上面这些代码。通过glLightfv()函数给光源设置位置以及颜色,能够加环境光G_AMBIENT、漫射光GL_DIFFUSE或镜面光GLSPECULAR,能够同一时候加8个光源,上面的光源时GL_LIGHT0,其它的就是GL_LIGHT1、2等。

场景中一旦加了光源,物体就会依据自己的材质对RGB光成分反射程度而显示不同的颜色。OpenGL给一个物体设置材质的方法

  1. GLfloat diffuse[] = {1.0, 0.9, 0.9};
  2. glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse);

设置材质參数后接下来画的物体就具备了这样的材质。通常使用參数GL_AMBIENT_AND_DIFFUSE给环境光和漫射光设置同样的反射程度。

2)纹理映射与多重纹理映射

给模型加纹理是为了在表面形成复杂图案,由于设置材质仅仅是控制表面的显示颜色。实际上物体的表面信息要更复杂些。opengl提供了给物体贴纹理图的方法,实际上有多种方法。但我用的是 glaux库。

  1. struct IMAGE
  2. {
  3. GLuint sizeX;
  4. GLuint sizeY;
  5. signed char* data;
  6. };
  7. IMAGE *Image[3];
  8. GLuint Texture[3];
  9. bool loadTexture()//设置各种纹理。从bmp图像读取
  10. {
  11. FILE* myFile;
  12. if(!(myFile = fopen("wall.bmp", "r")))
  13. return false;
  14. Image[0] = (IMAGE*)auxDIBImageLoad("wall.bmp");
  15. glGenTextures(3, &Texture[0]);
  16. glBindTexture(GL_TEXTURE_2D, Texture[0]);
  17. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  18. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  19. glTexImage2D(GL_TEXTURE_2D, 0, 3, Image[0]->sizeX, Image[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image[0]->data);
  20. if(!(myFile = fopen("floor.bmp", "r")))
  21. return false;
  22. Image[1] = (IMAGE*)auxDIBImageLoad("floor.bmp");
  23. glBindTexture(GL_TEXTURE_2D, Texture[1]);
  24. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  25. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  26. glTexImage2D(GL_TEXTURE_2D, 0, 3, Image[1]->sizeX, Image[1]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image[1]->data);
  27. if(!(myFile = fopen("water.bmp", "r")))
  28. return false;
  29. Image[2] = (IMAGE*)auxDIBImageLoad("water.bmp");
  30. glBindTexture(GL_TEXTURE_2D, Texture[2]);
  31. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  32. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  33. glTexImage2D(GL_TEXTURE_2D, 0, 3, Image[2]->sizeX, Image[2]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image[2]->data);
  34.  
  35. //释放内存
  36. if(Image[0])
  37. {
  38. if(Image[0]->data)
  39. free(Image[0]->data);
  40. free(Image[0]);
  41. }
  42. if(Image[1])
  43. {
  44. if(Image[1]->data)
  45. free(Image[1]->data);
  46. free(Image[1]);
  47. }
  48. if(Image[2])
  49. {
  50. if(Image[2]->data)
  51. free(Image[2]->data);
  52. free(Image[2]);
  53. }
  54. return true;
  55. }

上面的代码生成了三种纹理。

语句glGenTextures(3, &Texture[0])生成了三个纹理索引,存在了数组Texture中。以后每次要设置纹理信息或是想应用纹理,通过函数glBindTexture(GL_TEXTURE_2D, textureIndex)就能够取到相应的纹理。当中第二个參数就是纹理索引。

绑定纹理到物体表面:

  1. void drawPolygon(GLfloat a[3], GLfloat b[3], GLfloat c[3], GLfloat d[3])//依据四个点画一个面
  2. {
  3. glEnable(GL_TEXTURE_2D);
  4. glBindTexture(GL_TEXTURE_2D, Texture[0]);
  5. glBegin(GL_POLYGON);
  6. glTexCoord2f(0.0f, 0.0f);
  7. glVertex3fv(a);
  8. glTexCoord2f(1.0f, 0.0f);
  9. glVertex3fv(b);
  10. glTexCoord2f(1.0f, 1.0f);
  11. glVertex3fv(c);
  12. glTexCoord2f(0.0f, 1.0f);
  13. glVertex3fv(d);
  14. glEnd();
  15. }

多重纹理就是在物体表面贴上多个纹理的方法,要使用多重纹理须要用到还有一个库glext库。依据以下的代码就能够给物体表面贴上两种纹理:

  1. PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB=NULL;
  2. PFNGLACTIVETEXTUREARBPROC glActiveTextureARB=NULL;
  3. bool canMultiTexture = true;
  4. void multiTextureInit()//多重纹理的初始化
  5. {
  6. glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
  7. glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
  8. if(glActiveTextureARB == NULL)
  9. canMultiTexture = false;
  10. }
  11. void multiTextureBegin()//多重纹理绑定
  12. {
  13. glEnable(GL_TEXTURE_2D);
  14. glActiveTextureARB(GL_TEXTURE0_ARB);
  15. glBindTexture(GL_TEXTURE_2D, Texture[0]);//纹理1
  16. glActiveTextureARB(GL_TEXTURE1_ARB);
  17. glEnable(GL_TEXTURE_2D);
  18. glBindTexture(GL_TEXTURE_2D, Texture[1]);//纹理2
  19. }

值得注意的是,多重纹理对电脑设备有要求,主要是显示器要支持,所以一旦不支持,上面初始化的时候glActivetextureARB就会为null,这时候要做另外处理,不然后面使用这个为null的变量程序就会出错。

3)显示列表

显示列表是OpenGL提供的一种方便重复调用同样的显示函数的方法,比方你的程序中须要重复的描绘一个物体。你就最好用显示列表来调用,这样做可以大大优化性能。

调用显示列表是通过glCallList(列表索引)函数调用的,显然没一个显示列表都有一个相应的索引,通过这个索引去调用显示列表中的显示操作。以下的代码生成了一个画五角星的显示列表:

  1. GLuint display_list;//一个五角星的显示列表索引
  1. GLuint createDL()//创建一个五角星显示列表
  2. {
  3. GLuint DL;
  4. DL = glGenLists(1);
  5. glNewList(DL,GL_COMPILE);
  6. drawFive();//画一个五角星
  7. glEndList();
  8. return DL;
  9. }

当须要画一个五角星的时候调用glCallList(display_list);就可以。

2 场景漫游

我实现的是模拟人在迷宫中走动寻找出口的情形,通过键盘的上下左右键控制视线的改变以及位置的移动。先理解一下gluLookAt函数,我的程序里參数是这种gluLookAt(x, y, z, x + lx,y + ly,z + lz,0.0f,1.0f,0.0f) 总共同拥有9个參数。前三个參数代表了照相机的位置,所以这里照相机的位置是(x,y。z),接下来三个參数是目标的中心位置。即(x+lx, y+ly,z+lz),后面三个參数一般设为0, 1, 0,表示的是照相机头部的方向,假设把照相机看错人眼。那照相机头部的方向也就是我们头的方向(所以一般向上)。由于要控制向前/后移动。所以须要知道此时视线的方向向量。实际上就是(lx,
ly。 lz)。当改变视角是事实上就是改变(lx。 ly。 lz)的值。所以当左右键事件发生时,进行下面计算:

  1. void orientMe(float ang) //计算由于左右键盘操作而改变视点方向。使用左右方向键旋转照相机
  2. {
  3. lx = sin(ang);
  4. lz = -cos(ang);
  5. glLoadIdentity();
  6. gluLookAt(x, y, z, x + lx,y + ly,z + lz, 0.0f,1.0f,0.0f);
  7. }

能够注意到照相机位置还是不变的,由于仅仅是改变了视线。

当上下键事件发生时,改变的就是照相机的位置了。

  1. void moveMeFlat(int direction) //计算视点因为上下键盘操作而移动的量,上下方向键使照相机沿视线前后移动
  2. {
  3. int prev_x = x, prev_z = z;
  4. x = x + direction*(lx)*0.1;
  5. z = z + direction*(lz)*0.1;
  6. glLoadIdentity();
  7. if(isWall[(int)(x + 93)][(int)(z + 93)])
  8. {
  9. x = prev_x;
  10. z = prev_z;
  11. }
  12. gluLookAt(x, y, z, x + lx,y + ly,z + lz,0.0f,1.0f,0.0f);
  13. }

3 粒子系统的实现

粒子系统不是什么详细的东西。而是是一个非常好的编程设计思想,通经常使用来模拟雨、雪、雾、烟花等效果。

粒子系统的实现主要问题就是怎样设计粒子的行为以及怎样渲染粒子以达到真实的效果。

我的程序里粒子系统是最后加的,跟走迷宫没什么练习。仅仅是认为粒子系统挺奇妙的,就试着实现各种五角星漫天飞扬的效果。

这时粒子的类。定义了一个粒子的全部行为:

  1. //次类是粒子类。实现粒子的一系列行为
  2. #include<stdlib.h>
  3. #include<GL\glut.h>
  4. #include<time.h>
  5. #define PI 3.1415
  6. class particle
  7. {
  8. private:
  9. GLfloat x;//位置x坐标
  10. GLfloat y;//y坐标
  11. GLfloat z;//z坐标
  12. GLfloat v[3];//控制速度
  13. GLfloat rotate[3];//控制旋转方向
  14. GLfloat angle;//旋转的角度
  15. GLfloat color[3];//五角星显示的颜色
  16. GLuint display_list;//一个五角星的显示列表索引
  17. public:
  18. GLuint createDL()//创建一个五角星显示列表
  19. {
  20. GLuint DL;
  21. DL = glGenLists(1);
  22. glNewList(DL,GL_COMPILE);
  23. drawFive();//画一个五角星
  24. glEndList();
  25. return DL;
  26. }
  27. void init()//随机初始化位置以及方向等信息
  28. {
  29. display_list = createDL();
  30. angle = 0;
  31. y = rand() % 40;
  32. x = rand() % 181 - 90;
  33. z = rand() % 181 - 90;
  34. v[0] = (float)(rand() % 8) / (float)10 - 0.4;
  35. v[1] = (float)(rand() % 8) / (float)10 - 0.4;
  36. v[2] = (float)(rand() % 8) / (float)10 - 0.4;
  37. rotate[0] = (float)(rand() % 7) / (float)7 + 5;
  38. rotate[1] = (float)(rand() % 7) / (float)7 + 5;
  39. rotate[2] = (float)(rand() % 7) / (float)7 + 5;
  40. color[0] = (float)(rand() % 5) / (float)5 + 0.2;
  41. color[1] = (float)(rand() % 5) / (float)5 + 0.2;
  42. color[2] = (float)(rand() % 5) / (float)5 + 0.2;
  43. }
  44. void drawFive()//画五角星
  45. {
  46. GLfloat out_length = sqrt(1.0 / (2 - 2 * cos(72 * PI / 180))),
  47. bx = out_length * cos(18 * PI / 180),
  48. by = out_length * sin(18 * PI / 180),
  49. cx = out_length * sin(36 * PI / 180),
  50. cy = -out_length * cos(36 * PI / 180);
  51. GLfloat fx = cx * (by - out_length) / (cy - out_length), fy = by,
  52. in_length = sqrt(fx * fx + fy * fy),
  53. gx = in_length * cos(18 * PI / 180),
  54. gy = -in_length * sin(18 * PI / 180);
  55. GLfloat point_a[2] = {0, out_length},
  56. point_b[2] = {bx, by},
  57. point_c[2] = {cx, cy},
  58. point_d[2] = {-cx, cy},
  59. point_e[2] = {-bx, by},
  60. point_f[2] = {fx, fy},
  61. point_g[2] = {gx, gy},
  62. point_h[2] = {0, -in_length},
  63. point_i[2] = {-gx, gy},
  64. point_j[2] = {-fx, fy};
  65. glBegin(GL_TRIANGLE_FAN);
  66. glVertex2f(0.0f, 0.0f);
  67. glVertex2f(point_a[0], point_a[1]);
  68. glVertex2f(point_f[0], point_f[1]);
  69. glVertex2f(point_b[0], point_b[1]);
  70. glVertex2f(point_g[0], point_g[1]);
  71. glVertex2f(point_c[0], point_c[1]);
  72. glVertex2f(point_h[0], point_h[1]);
  73. glVertex2f(point_d[0], point_d[1]);
  74. glVertex2f(point_i[0], point_i[1]);
  75. glVertex2f(point_e[0], point_e[1]);
  76. glVertex2f(point_j[0], point_j[1]);
  77. glVertex2f(point_a[0], point_a[1]);
  78. glEnd();
  79. }
  80. void draw()//在(x, y, z)显示五角星
  81. {
  82. GLfloat diffuse[] = {color[0], color[1], color[2]};
  83. glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse);
  84. glPushMatrix();
  85. glTranslatef(x, y, z);
  86. glRotatef(angle, rotate[0], rotate[1], rotate[2]);
  87. glCallList(display_list);
  88. glPopMatrix();
  89. }
  90. void move(float slowdown)//改变粒子位置及角度等信息
  91. {
  92. x += v[0] / slowdown;
  93. y += v[1] / slowdown;
  94. z += v[2] / slowdown;
  95. angle += 10 / slowdown;
  96. if(!(x >= -90 && x <= 90))
  97. die();
  98. else if(!(z >= -90 && z <= 90))
  99. die();
  100. else if(!(y >= 0 && y <= 50))
  101. die();
  102. }
  103. void die()//粒子死亡,消失,又一次初始化
  104. {//能够加其它操作
  105. init();
  106. }
  107. };

另外也能够对照一下我实现下雨效果的粒子类:

  1. #include<stdlib.h>
  2. #include<GL\glut.h>
  3. #include<time.h>
  4.  
  5. class rain
  6. {
  7. private:
  8. GLfloat position[3];//粒子的位置
  9. GLfloat v0;//粒子的初速度
  10. GLfloat g;//重力加速度
  11. GLfloat size;//雨滴的大小
  12. GLfloat sizeSet[4];
  13. GLfloat gSet[4];
  14. GLuint display_list;
  15. public:
  16. rain()
  17. {
  18. sizeSet[0] = 0.40;
  19. sizeSet[1] = 0.45;
  20. sizeSet[2] = 0.50;
  21. sizeSet[3] = 0.55;
  22. gSet[0] = 0.5;
  23. gSet[1] = 0.52;
  24. gSet[2] = 0.54;
  25. gSet[3] = 0.56;
  26. }
  27. GLuint createDL()
  28. {
  29. GLuint DL;
  30. DL = glGenLists(1);
  31. glNewList(DL,GL_COMPILE);
  32. GLUquadricObj *qobj = gluNewQuadric();
  33. gluQuadricTexture(qobj,GL_TRUE);
  34. gluSphere(qobj, size, 20, 20);//画一个小球
  35. glEndList();
  36. return DL;
  37. }
  38. void init()//粒子初始化
  39. {
  40. display_list = createDL();
  41. position[0] = rand() % 181 - 90;
  42. position[1] = 50;
  43. position[2] = rand() % 181 - 90;
  44. int sizeIndex = rand() % 4;
  45. size = sizeSet[sizeIndex];
  46. g = gSet[sizeIndex];//随机加速度
  47. v0 = (float)(rand() % 6) / (float)20;//随机初始化初速度
  48. }
  49. void draw()
  50. {
  51. GLfloat diffuse[3] = {1.0, 1.0, 1.0};
  52. glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse);
  53. glPushMatrix();
  54. glTranslatef(position[0], position[1], position[2]);
  55. glCallList(display_list);
  56. glPopMatrix();
  57. }
  58. void move()
  59. {
  60. position[1] -= v0;
  61. v0 += g;
  62. if(position[1] <= 0)
  63. die();
  64. }
  65. void die()
  66. {
  67. init();
  68. }
  69. };

雨水粒子我设计得比較简单,初始化的时候分配它随机一个初速度、一个初位置、加速度、大小等,每次显示过后依据速度和加速度改变位置以实现“加速落下”的效果,还有渲染的时候须要用到雨水的纹理图。

设计好了粒子类之后,就能够再写一个类实现对粒子数的控制,以及对全部粒子进行初始化和显示。

重复调用的实现

通过理解粒子系统,我知道它是重复地调用显示全部粒子的函数。由于每次粒子的位置都会改变。所以就形成了粒子的运动。那怎么重复调用显示函数呢?看一下glut库里的函数(当然假设用windows库的话也能实现重复调用,这里仅仅是glut库的):

glutDisplayFunc(renderScene);每次窗体重绘时指定调用函数

glutReshapeFunc(changeSize);     每次窗体大小改变时制定调用函数

一開始想通过这两个函数想重复调用renderScene函数,可是没办法,它们指定的函数仅仅能在特定情况下被调用;

然后我就找到了glutIdleFunc(renderScene)函数。作用是设置全局的默认调用函数。当函数glutMainLoop()进行了无限等待时间循环时。假设没有窗体事件发生,就默认调用glutIdelFunc指定的函数,这样就能够重复调用renderScene函数了。

五角星粒子系统效果:



雪粒子效果



OpenGL3D迷宫场景设计的更多相关文章

  1. LoadRunner脚本设计、场景设计和结果分析

    本次笔记主要记录LoadRunner脚本设计.场景设计和结果分析   1. 脚本设计       录制模式            手工模式:插入步骤.手动编写       1.1  脚本增强:     ...

  2. 【Loadrunner】初学Loadrunner——场景设计

    在使用Loadrunner的时候,常常需要使用到场景设计.但是怎么设计一个满意的场景?如何开展? 首先可以点击tools > Create Controller Scenario > OK ...

  3. lr_场景设计之知识点-集合点、loadgenerator

    1.controller原理 通过场景设计来模拟用户的真实操作并调用bugen中的脚本,再通过设置的压力机产生压力,在场景运行中实时监控用户的执行情况,tps,响应时间,吞吐量,服务器资源使用情况: ...

  4. vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图

    vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图 Mand Mobile是面向金融场景设计的移动端组件库,基于Vue.js实现.目前已实际应用于滴滴四大金融业务板块的1 ...

  5. loadrunner 场景设计-学习笔记之性能误区

    场景设计-学习笔记之性能误区 by:授客 QQ:1033553122 场景假设: 每个事务仅包含一次请求,执行10000个并发用户数 性能误区: 每秒并发用户数=每秒向服务器提交请求数 详细解答: 每 ...

  6. loadrunner 场景设计-设计与实践

    场景设计-设计与实践 by:授客 QQ:1033553122 以lr 11.0 自带Web Tours为例,进行以下测试 说明:以下测试仅供演示,学习设计思路 A.确定系统组件 简单B/S架构:Cli ...

  7. loadrunner 场景设计-设置结果文件保存路径

    场景设计-设置结果文件保存路径 by:授客 QQ:1033553122 Results->Results settings Results Name 结果文件夹名称 Directory 指定结果 ...

  8. loadrunner 场景设计-制定负载测试计划

    by:授客 QQ:1033553122 场景设计-制定负载测试计划 步骤1.分析应用程序 你应该对硬件和软件组建,系统配置和典型的使用场景很熟悉.这些应用程序的分析保证你在使用loadrunner进行 ...

  9. loadrunner 场景设计-添加Unix、Linux Resources计数器

    场景设计-添加Unix.Linux Resources计数器 by:授客 QQ:1033553122 A.   目的 监控要测试的Unix.Linux服务器的资源使用情况 Linux CentOS为例 ...

随机推荐

  1. Mysql RR隔离更新列没有索引 会锁全表

    <pre name="code" class="html">mysql> show variables like '%tx_isolation ...

  2. OpenCV在矩阵上的卷积

    转载请注明出处!!!http://blog.csdn.net/zhonghuan1992 OpenCV在矩阵上的卷积 在openCV官网上说是戴面具,事实上就是又一次计算一下矩阵中的每个value,那 ...

  3. 手把手教你_android自己主动化实践方案选型

    接到一个android自己主动化的任务,看了看手中的家伙:ranorex,appium,uiautomator 当然先捡商用的试试,简单呀,能够录制回放,只是不是抱特别大的期望,这个爷比較娇气,要是a ...

  4. Bootstrapping (compilers) - Wikipedia, the free encyclopedia

    Bootstrapping (compilers) - Wikipedia, the free encyclopedia Bootstrapping (compilers)

  5. SQL中如何将一个表中的某一列的数据复制到另一个表中的某一列里

    表一: SPRD PRD_NO      SPC 001                NULL 002               NULL 003               NULL ...   ...

  6. poj1639 Picnic Planning 最小度数限制生成树

    题意:若干个人开车要去park聚会,可是park能停的车是有限的,为k.所以这些人要通过先开车到其它人家中,停车,然后拼车去聚会.另外,车的容量是无限的,他们家停车位也是无限的. 求开车总行程最短. ...

  7. POJ 3040 Allowance 贪心

    这题目的贪心思路还是有一点细节问题的. 还没有证明,据说是因为题目给的条件是每个价格是比它小的价格的倍数才能这么贪心的. 思路如下: 假设要给奶牛的钱为C 1)从大面值到小面值一次拿钱,能拿多少拿多少 ...

  8. hdu-4418-Time travel-高斯+概率dp

    把N个点先转化为2*N-2个点. 比方说把012345转化成0123454321. 这样,就能够找出随意两两个点之间的关系. 然后依据关系能够得出来一个一元多项式的矩阵. 然后就用高斯消元求出矩阵就可 ...

  9. haskell,lisp,erlang你们更喜欢哪个?

    haskell,lisp,erlang你们更喜欢哪个? haskell,lisp,erlang你们更喜欢哪个?

  10. IO流的总结

    流操作的基本规律: 最痛苦的就是流对象有非常多.不知道该用那一个. 通过明白来完毕. (1).明白源和目的 源:输入流InputStream Reader 目的:输出流OutputStream Wri ...