OpenGL3D迷宫场景设计

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

一 实现效果

二 实现过程详解

   1、3d场景构建

1)光照与材质

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

GLfloat light_position[] = {0.0, 80.0, 0.0};
	GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0};
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_AMBIENT_AND_DIFFUSE, light_diffuse);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

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

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

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

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

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

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

struct IMAGE
{
	GLuint sizeX;
	GLuint sizeY;
	signed char* data;
};
IMAGE *Image[3];
GLuint Texture[3];
bool loadTexture()//设置各种纹理,从bmp图像读取
{
	FILE* myFile;
	if(!(myFile = fopen("wall.bmp", "r")))
	return false;
	Image[0] = (IMAGE*)auxDIBImageLoad("wall.bmp");
	glGenTextures(3, &Texture[0]);
	glBindTexture(GL_TEXTURE_2D, Texture[0]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, Image[0]->sizeX, Image[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image[0]->data);
	if(!(myFile = fopen("floor.bmp", "r")))
		return false;
	Image[1] = (IMAGE*)auxDIBImageLoad("floor.bmp");
	glBindTexture(GL_TEXTURE_2D, Texture[1]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, Image[1]->sizeX, Image[1]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image[1]->data);
	if(!(myFile = fopen("water.bmp", "r")))
		return false;
	Image[2] = (IMAGE*)auxDIBImageLoad("water.bmp");
	glBindTexture(GL_TEXTURE_2D, Texture[2]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, Image[2]->sizeX, Image[2]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image[2]->data);

	//释放内存
	if(Image[0])
	{
		if(Image[0]->data)
			free(Image[0]->data);
			free(Image[0]);
	}
	if(Image[1])
	{
		if(Image[1]->data)
			free(Image[1]->data);
		free(Image[1]);
	}
	if(Image[2])
	{
		if(Image[2]->data)
			free(Image[2]->data);
		free(Image[2]);
	}
	return true;
}

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

绑定纹理到物体表面:

void drawPolygon(GLfloat a[3], GLfloat b[3], GLfloat c[3], GLfloat d[3])//根据四个点画一个面
{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, Texture[0]);
	glBegin(GL_POLYGON);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3fv(a);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3fv(b);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3fv(c);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3fv(d);
	glEnd();
}

多重纹理就是在物体表面贴上多个纹理的方法,要使用多重纹理需要用到另一个库glext库。根据下面的代码就可以给物体表面贴上两种纹理:

PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB=NULL;
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB=NULL;
bool canMultiTexture = true;
void multiTextureInit()//多重纹理的初始化
{
	glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
	glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
	if(glActiveTextureARB == NULL)
		canMultiTexture = false;
}
void multiTextureBegin()//多重纹理绑定
{
	 glEnable(GL_TEXTURE_2D);
	 glActiveTextureARB(GL_TEXTURE0_ARB);
	 glBindTexture(GL_TEXTURE_2D, Texture[0]);//纹理1
	 glActiveTextureARB(GL_TEXTURE1_ARB);
	 glEnable(GL_TEXTURE_2D);
	 glBindTexture(GL_TEXTURE_2D, Texture[1]);//纹理2
}

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

3)显示列表

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

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

GLuint display_list;//一个五角星的显示列表索引
GLuint createDL()//创建一个五角星显示列表
	{
		GLuint DL;
		DL = glGenLists(1);
		glNewList(DL,GL_COMPILE);
		drawFive();//画一个五角星
		glEndList();
		return DL;
	}

当需要画一个五角星的时候调用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)的值,所以当左右键事件发生时,进行以下计算:

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

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

void moveMeFlat(int direction)  //计算视点由于上下键盘操作而移动的量,上下方向键使照相机沿视线前后移动
{
	int prev_x = x, prev_z = z;
	x = x + direction*(lx)*0.1;
	z = z + direction*(lz)*0.1;
	glLoadIdentity();
	if(isWall[(int)(x + 93)][(int)(z + 93)])
	{
		x = prev_x;
		z = prev_z;
	}
	gluLookAt(x, y, z, x + lx,y + ly,z + lz,0.0f,1.0f,0.0f);
}

3 粒子系统的实现

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

//次类是粒子类,实现粒子的一系列行为
#include<stdlib.h>
#include<GL\glut.h>
#include<time.h>
#define PI 3.1415
class particle
{
private:
	GLfloat x;//位置x坐标
	GLfloat y;//y坐标
	GLfloat z;//z坐标
	GLfloat v[3];//控制速度
	GLfloat rotate[3];//控制旋转方向
	GLfloat angle;//旋转的角度
	GLfloat color[3];//五角星显示的颜色
	GLuint display_list;//一个五角星的显示列表索引
public:
	GLuint createDL()//创建一个五角星显示列表
	{
		GLuint DL;
		DL = glGenLists(1);
		glNewList(DL,GL_COMPILE);
		drawFive();//画一个五角星
		glEndList();
		return DL;
	}
	void init()//随机初始化位置以及方向等信息
	{
		display_list = createDL();
		angle = 0;
		y = rand() % 40;
		x = rand() % 181 - 90;
		z = rand() % 181 - 90;
		v[0] = (float)(rand() % 8) / (float)10 - 0.4;
		v[1] = (float)(rand() % 8) / (float)10 - 0.4;
		v[2] = (float)(rand() % 8) / (float)10 - 0.4;
		rotate[0] = (float)(rand() % 7) / (float)7 + 5;
		rotate[1] = (float)(rand() % 7) / (float)7 + 5;
		rotate[2] = (float)(rand() % 7) / (float)7 + 5;
		color[0] = (float)(rand() % 5) / (float)5 + 0.2;
		color[1] = (float)(rand() % 5) / (float)5 + 0.2;
		color[2] = (float)(rand() % 5) / (float)5 + 0.2;
	}
	void drawFive()//画五角星
	{
		GLfloat out_length = sqrt(1.0 / (2 - 2 * cos(72 * PI / 180))),
		bx = out_length * cos(18 * PI / 180),
		by = out_length * sin(18 * PI / 180),
		cx = out_length * sin(36 * PI / 180),
		cy = -out_length * cos(36 * PI / 180);
		GLfloat fx = cx * (by - out_length) / (cy - out_length), fy = by,
		in_length = sqrt(fx * fx + fy * fy),
		gx = in_length * cos(18 * PI / 180),
		gy = -in_length * sin(18 * PI / 180);
		GLfloat point_a[2] = {0, out_length},
		point_b[2] = {bx, by},
		point_c[2] = {cx, cy},
		point_d[2] = {-cx, cy},
		point_e[2] = {-bx, by},
		point_f[2] = {fx, fy},
		point_g[2] = {gx, gy},
		point_h[2] = {0, -in_length},
		point_i[2] = {-gx, gy},
		point_j[2] = {-fx, fy};
		glBegin(GL_TRIANGLE_FAN);
		glVertex2f(0.0f, 0.0f);
		glVertex2f(point_a[0], point_a[1]);
		glVertex2f(point_f[0], point_f[1]);
		glVertex2f(point_b[0], point_b[1]);
		glVertex2f(point_g[0], point_g[1]);
		glVertex2f(point_c[0], point_c[1]);
		glVertex2f(point_h[0], point_h[1]);
		glVertex2f(point_d[0], point_d[1]);
		glVertex2f(point_i[0], point_i[1]);
		glVertex2f(point_e[0], point_e[1]);
		glVertex2f(point_j[0], point_j[1]);
		glVertex2f(point_a[0], point_a[1]);
		glEnd();
	}
	void draw()//在(x, y, z)显示五角星
	{
		GLfloat diffuse[] = {color[0], color[1], color[2]};
		glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse);
		glPushMatrix();
		glTranslatef(x, y, z);
		glRotatef(angle, rotate[0], rotate[1], rotate[2]);
		glCallList(display_list);
		glPopMatrix();
	}
	void move(float slowdown)//改变粒子位置及角度等信息
	{
		x += v[0] / slowdown;
		y += v[1] / slowdown;
		z += v[2] / slowdown;
		angle += 10 / slowdown;
		if(!(x >= -90 && x <= 90))
			die();
		else if(!(z >= -90 && z <= 90))
			die();
		else if(!(y >= 0 && y <= 50))
			die();
	}
	void die()//粒子死亡,消失,重新初始化
	{//可以加其他操作
		init();
	}
};

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

#include<stdlib.h>
#include<GL\glut.h>
#include<time.h>

class rain
{
private:
	GLfloat position[3];//粒子的位置
	GLfloat v0;//粒子的初速度
	GLfloat g;//重力加速度
	GLfloat size;//雨滴的大小
	GLfloat sizeSet[4];
	GLfloat gSet[4];
	GLuint display_list;
public:
	rain()
	{
		sizeSet[0] = 0.40;
		sizeSet[1] = 0.45;
		sizeSet[2] = 0.50;
		sizeSet[3] = 0.55;
		gSet[0] = 0.5;
		gSet[1] = 0.52;
		gSet[2] = 0.54;
		gSet[3] = 0.56;
	}
	GLuint createDL()
	{
		GLuint DL;
		DL = glGenLists(1);
		glNewList(DL,GL_COMPILE);
		GLUquadricObj *qobj = gluNewQuadric();
		gluQuadricTexture(qobj,GL_TRUE);
		 gluSphere(qobj, size, 20, 20);//画一个小球
		glEndList();
		return DL;
	}
	void init()//粒子初始化
	{
		display_list = createDL();
		position[0] = rand() % 181 - 90;
		position[1] = 50;
		position[2] = rand() % 181 - 90;
		int sizeIndex = rand() % 4;
		size = sizeSet[sizeIndex];
		g = gSet[sizeIndex];//随机加速度
		v0 = (float)(rand() % 6) / (float)20;//随机初始化初速度
	}
	void draw()
	{
		GLfloat diffuse[3] = {1.0, 1.0, 1.0};
		glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse);
		glPushMatrix();
		glTranslatef(position[0], position[1], position[2]);
		glCallList(display_list);
		glPopMatrix();
	}
	void move()
	{
		position[1] -= v0;
		v0 += g;
		if(position[1] <= 0)
			die();
	}
	void die()
	{
		init();
	}
};

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

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

反复调用的实现

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

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

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

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

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

五角星粒子系统效果:

OpenGL3D迷宫场景设计

时间: 2024-11-14 00:04:09

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

loadrunner11的移动端性能测试之场景设计

测试步骤之场景设计(Controller) 进入手工场景 准备好脚本后就可以进行场景设计和执行场景了,从VuGen中进入,见下图: 进入后第一个为目标场景,选择第二个更灵活的手工场景,我的目标人数200,第三个为结果保存地址,最好放到空间比较大的盘中,其他默认即可. 确认后进入场景设计页,也可以在[HP LoadRunner]-[Applications]-[Controller]单独进入.选择第一个手工场景,选择左边你需要运行的脚本,点击[Add]添加至场景脚本中,点击确定就ok了,后面再设置

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

在使用Loadrunner的时候,常常需要使用到场景设计.但是怎么设计一个满意的场景?如何开展? 首先可以点击tools > Create Controller Scenario > OK(单个脚本的场景设计到这里就可以结束了) > File > New (第一次使用的时候在点工具 > 创建的时候就会出现新场景设计页面,这里的步骤是非第一次使用场景设计页面创建时的操作步骤).场景设置有手动设置和基于目标的场景设置两种,下面就逐一介绍. 一.手动设置场景Manual Scena

jmeter场景设计

场景设计是根据收集分析用户的实际操作而定义的Jmeter脚本的执行策略. 性能测试中涉及的基本场景有两种, 即单一业务场景和混合业务场景,这 两种业务场景缺一不可,缺少任何一种都不能准确评估系统性能,定位系统瓶 颈.如果只做单一业务场景,得到的结果与实际生产环境差距较大,没有实际 指导意义:如果只做混合业务场景, 不能快速定位系统性能快速降低的原因, 起不到定位瓶颈.系统调优的作用.只有两种场景互为补充,才可以获取 最符 合客户要求的测试结果. 在Jmeter场景主要通过线程组设置来完成.通过组

LR实战之Discuz开源论坛——登录场景设计

以下是根据个人项目经验,对登录场景的设计,如下步骤: 一.打开Controller,添加登录脚本,选择"手动场景",一般我们项目中经常使用的是"手动场景"类型设计,如图 二.在"设计"部分,设置场景的并发用户数及场景计划 1.在场景计划页面,计划的"运行模式"默认为"实际计划",选择"基本计划",就可以修改"场景组"列表下该场景组名的并发用户数量,这里并发40个用户,

loadrunner&#160;场景设计-手工场景方案(Schedule)设计&#160;Part&#160;1

参考:http://blog.sina.com.cn/s/articlelist_5314188213_1_1.html loadrunner 场景设计-手工场景方案(Schedule)设计 Part 1 A.   定义方案schedule 在 Scenario Schedule面板中,选择一个方案schedule,或通过点击New Schedule定义一个新的方案 定义schedule: a.新建schedule:点击新建按钮(可选) b.重命名schedule:在Schedule Name输

LR-Controller场景设计与场景监控笔记

笔记要点: 概念 场景设计 场景执行 场景监视 一.概念 1.新建场景中包括两种场景方法:手动场景(更灵活,更接近真实用户操作) 和面向目标场景. 二.场景设计 主要包括Schedule .View Script.Generator参数设置.手动与面向目标场景的后两个参数是一致的.只有Schedule参数不同. 1.手动场景Schedule的配置 Schedule主要设置用户的行为方式 .包括按场景计划和按用户组计划. (1)场景名称 场景名称要反映场景动作. (2)按场景计划 1)Initia

场景设计方法

一.方法简介 现在的软件几乎都是用事件触发来控制流程的,事件触发时的情景便形成了场景,而同一事件不同的触发顺序和处理结果就形成事件流.这种在软件设计方面的思想也可以引入到软件测试中,可以比较生动地描绘出事件触发时的情景,有利于测试设计者设计测试用例,同时使测试用例更容易理解和执行. 基本流和备选流:如下图所示,图中经过用例的每条路径都用基本流和备选流来表示,直黑线表示基本流,是经过用例的最简单的路径.备选流用不同的色彩表示,一个备选流可能从基本流开始,在某个特定条件下执行,然后重新加入基本流中(

性能测试之场景设计

负载测试 需求举例: 系统支持200个并发,用户信息查询的响应时间小于5秒 场景设计: 200个并发持续运行20分钟,通过测试结果验证用户信息查询的响应时间是否小于5秒. 压力测试 需求举例: 系统在50,100,150,200并发下的运行情况 场景设计: 50个并发开始,每隔10分钟增加50个并发,目标并发数为200,到达目标并发数之后再运行10分钟,然后每隔20秒停止50个并发.通过测试工具监控响应时间.事务处理速率,主机资源使用情况,中间件资源使用情况,数据库运行情况. 强度测试 需求举例

测试用例设计方法(八)场景设计方法

现在的软件几乎都是用事件触发来控制流程的,事件触发时的情景便形成了场景,而同一事件不同的触发顺序和处理结果就形成事件流.这种在软件设计方面的思想也可以引入到软件测试中,可以比较生动地描绘出事件触发时的情景,有利于测试设计者设计测试用例,同时使测试用例更容易理解和执行. 基本流和备选流:如下图所示,图中经过用例的每条路径都用基本流和备选流来表示,直黑线表示基本流,是经过用例的最简单的路径.备选流用不同的色彩表示,一个备选流可能从基本流开始,在某个特定条件下执行,然后重新加入基本流中(如备选流1和3