最近学习用opengl库来构建一个3D场景,以及实现场景漫游、粒子系统等效果,最终算是是做了一个3D走迷宫游戏吧。感觉最近学了好多东西,所以有必要整理整理。

一 实现效果

二 实现过程详解

   1、3d场景构建

1)光照与材质

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

[csharp] view plaincopy
  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给一个物体设置材质的方法

[csharp] view plaincopy
  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库。

[csharp] view plaincopy
  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. if(Image[0])
  36. {
  37. if(Image[0]->data)
  38. free(Image[0]->data);
  39. free(Image[0]);
  40. }
  41. if(Image[1])
  42. {
  43. if(Image[1]->data)
  44. free(Image[1]->data);
  45. free(Image[1]);
  46. }
  47. if(Image[2])
  48. {
  49. if(Image[2]->data)
  50. free(Image[2]->data);
  51. free(Image[2]);
  52. }
  53. return true;
  54. }

上面的代码生成了三种纹理。语句glGenTextures(3, &Texture[0])生成了三个纹理索引,存在了数组Texture中,以后每次要设置纹理信息或是想应用纹理,通过函数glBindTexture(GL_TEXTURE_2D, textureIndex)就可以取到对应的纹理,其中第二个参数就是纹理索引。

绑定纹理到物体表面:

[csharp] view plaincopy
  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库。根据下面的代码就可以给物体表面贴上两种纹理:

[csharp] view plaincopy
  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(列表索引)函数调用的,显然没一个显示列表都有一个对应的索引,通过这个索引去调用显示列表中的显示操作。下面的代码生成了一个画五角星的显示列表:

[csharp] view plaincopy
  1. GLuint display_list;//一个五角星的显示列表索引
[csharp] view plaincopy
  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)的值,所以当左右键事件发生时,进行以下计算:

[csharp] view plaincopy
  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. }

可以注意到照相机位置还是不变的,因为只是改变了视线。当上下键事件发生时,改变的就是照相机的位置了。

[csharp] view plaincopy
  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 粒子系统的实现

粒子系统不是什么具体的东西,而是是一个很好的编程设计思想,通常用来模拟雨、雪、雾、烟花等效果。粒子系统的实现主要问题就是如何设计粒子的行为以及如何渲染粒子以达到真实的效果。我的程序里粒子系统是最后加的,跟走迷宫没什么练习,只是觉得粒子系统挺神奇的,就试着实现各种五角星漫天飞扬的效果。这时粒子的类,定义了一个粒子的所有行为:

[csharp] view plaincopy
  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. };

另外也可以对比一下我实现下雨效果的粒子类:

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

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

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

反复调用的实现

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

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

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

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

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

五角星粒子系统效果:


雪粒子效果



OpenGL3D迷宫场景设计相关推荐

  1. UE4风格化场景设计入门指南 Stylized Station – The Environment Artist’s Survival Kit

    持续时间13h 1920X1080 .ts 包含项目文件 大小解压后:4.9G 语言:英语+中文字幕(人工校对) 标题:风格化的车站--环境艺术家的生存工具包 信息: 环境艺术很难. 尤其是作为初学者 ...

  2. Sketchup插件Vray户外场景设计渲染教程 Vray Next For Sketchup Exterior

    Sketchup户外场景设计的Vray Next 你会学到什么 渲染白天和夜晚场景 后期制作 Sketchup的Vray Next 中级sketchup用户 大小解压后:3.83G 1280X720 ...

  3. UE4场景设计学习教程

    视频:MPEG4视频(H264) 1920×1080 25fps 1400kbps |音频:AAC 44100Hz立体声128kbps 语言:西班牙语+中英文字幕(根据原英文字幕机译更准确) |时长: ...

  4. 性能测试场景设计之用户模式设置

    性能测试场景设计之参数设计 1.用户模式设置 场景执行前需要根据系统特性对场景进行配置,以便对系统进行负载测试时压力状况更加符合业务特性.相关的参数配置如下: 首先新建场景,如下: 场景新建的时候一般 ...

  5. 阿里巴巴在应用性能测试场景设计和实现上的实践

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/yunqiinsight/article ...

  6. J.R.R.托尔金笔下的中土世界与《斗破苍穹》项目实践:从世界观解构入手场景设计

    本期真经阁的特邀讲师将通过对<魔戒>与<霍比特人>这两部影视作品进行分析,为读者解构如何通过视觉上的表现设计来展现托尔金笔下的中土世界.并将其中讲述的方法应用在<斗破苍穹 ...

  7. loadrunner 场景设计-负载生成器管理

    场景设计-负载生成器管理 by:授客 QQ:1033553122 1  简介 当执行一个场景时,Controller把场景中的每个用户配到负载生成器(Load generator). 所谓的负载生成器 ...

  8. loadrunner 只能并发50_loadrunner 场景设计-(一)

    目录:手工场景和目标场景设置 混合场景设置 一.手工场景 手工场景是自行设置虚拟用户的变化,通过设计用户的添加和减少过程,来模拟真实的用户请求模型,完成负载的生成. 手工场景分为:Scenario模式 ...

  9. 游戏场景设计思维:黑白与颜色的现代构成艺术

    导语:在场景设计中,黑白构成与颜色厚涂是构图中两个分不开的重要技法.黑白构成和颜色厚涂在构图的不同阶段分别解决了什么问题,两者之间又有什么关联,在运用中有那些技巧?本文将重点给大家讲述这两种技法在动漫 ...

最新文章

  1. EST:西湖大学鞠峰组-污水厂病原菌与土著反硝化细菌是多重抗生素耐药基因的活跃表达者...
  2. 《Docker技术入门与实战》——2.3 本书环境介绍
  3. DJANGO_SETTINGS_MODULE is undefined报错的解决
  4. 设计模式学习笔记十:单例模式(Singleton Pattern)
  5. linux git diff patch,拿到git patch要怎麼用一般patch指令merge?
  6. lua——alien库实现lua调用C动态链接库(dll、so)
  7. 百度地图离线_3大主流导航地图,你用的哪个?
  8. 雷布斯被骗200w??一行代码值两百万?雷军公开小米新logo翻车了?
  9. 达梦数据库可视化工具的使用
  10. java 无法显示打印页面_使用打印机打印网页时出现的脚本错误的办法
  11. 超详细图文教程·阿里云免费学生ECS云服务器领取并使用全过程(部署Python多人聊天室程序)
  12. 苹果自带输入法怎么换行_微信个性签名怎么弄成竖的?不仅可以竖着还可以加边框效果!...
  13. 【P1889 士兵站队】(洛谷)
  14. OSChina 周三乱弹 —— 奶奶别慌,还有一计可以平天下
  15. 【推荐】选品策略——新零售篇
  16. 【秋招纪实录】一篇特别正经的【TCL】求职经验分享
  17. [!] Error installing PromiseKit
  18. 计算机的综合应用图片,计算机软件综合应用.PPT
  19. CH9121模块只有端口2有用
  20. 国内知名的数据分析软件平台

热门文章

  1. 结构体函数作用及示例
  2. esp32 s3 USB OTG Arduino可以下载固件 USB COM不可以的解决方法
  3. 使用OpenSSL生成IIS可用的SHA-256自签名证书
  4. 如何向领导汇报工作(5)
  5. 【论文笔记】CIKM2020 Star Graph Neural Networks for Session-based Recommendation
  6. 一文看懂广州数控车床G代码
  7. A. Rook, Bishop and King
  8. ADS1.2在Win10下的安装过程和初步使用
  9. 如何在PB中调用 Microsoft WEB 浏览器 控件?
  10. 为ActiveX控件创建Cab文件