您的位置:首页 > 其它

Nehe第19课 粒子系统

2012-10-07 22:33 281 查看

源代码链接
#include<windows.h>		// Windows的头文件
#include <gl/glew.h>		// 包含最新的gl.h,glu.h库
#include <gl/glut.h>		// 包含OpenGL实用库
#include <gl/glaux.h>		// GLaux库的头文件
#include <stdio.h>			// 标准输入/输出库的头文件
/*****************************************************************************/

GLuint texture[1];			//存储纹理,如果纹理大于1个改为相应数值
HGLRC hRC=NULL;						//窗口着色描述表句柄
HDC hDC=NULL;						//设备渲染描述表句柄
HWND hWND=NULL;						//窗口句柄
HINSTANCE hInstance;				//保存程序的实例

bool keys[256];
bool active=TRUE;					//窗口的活动状态,缺省为true
bool fullscreen=TRUE;				//全屏的缺省状态=true

LRESULT CALLBACK WndProc(HWND, UINT,WPARAM, LPARAM);
AUX_RGBImageRec *LoadBMP(char*Filename);
/************************************************************************/
/* Nehe第19课粒子系统                                                   */
/********************19课新增代码************************************************/
#define  MAX_PARTICLES 1000
bool rainbow=true;			//是否为彩虹模式
bool sp;					//space空格键是否按下
bool rp;					//回车键是否按下
/*变量slowdown控制粒子移动的快慢.数值愈高,移动越慢.数值越底,移动越快.
如果数值降低,粒子将快速的移动!粒子的速度影响它们在荧屏中移动的距离.
记住速度慢的粒子不会射很远的.
变量xspeed和yspeed控制尾部的方向.xspeed将会增加粒子在x轴上速度.
如果xspeed是正值粒子将会向右边移动多.
如果xspeed负价值,粒子将会向左边移动多.那个值越高,就向那个方向移动比较多.
yspeed工作相同的方法,但是在y轴上.
因为有其它的因素影响粒子的运动,所以我要说"多".
xspeed和yspeed有助于在我们想要的方向上移动粒子.
最后是变量zoom,我们用该变量移入或移出我们的屏幕.在粒子引擎里,有时可看见更多的图象,而且当接近你时很酷
*/
float	slowdown=2.0f;		// 减速粒子
float	xspeed;				// X方向的速度
float	yspeed;				// Y方向的速度
float	zoom=-40.0f;			// 沿Z轴缩放
/************************************************************************/
/*我们定义了一个复杂的循环变量叫做Loop.我们用这变量预先定义粒子并在屏幕中画粒子.
color用来给予粒子不同的颜色.delay用来控制在彩虹模式中圆的颜色变化.
最后,我们设定一个存储空间(粒子纹理).我用纹理而不用点的重要原因是,
点的速度慢,而且挺麻烦的.其次纹理很酷:)你用一个正方形的粒子,一张你脸的小图片,
一张星星的图片等等.很好控制!
*/
/************************************************************************/

GLuint	loop;				// 循环变量
GLuint	col;				// 当前的颜色索引0-11
GLuint	delay;				// 彩虹效果延迟好!现在是有趣的东西.下段程序描述单一粒子结构,这是我们给予粒子的属性.我们用布尔型变量active开始,如果为true,我们的粒子为活跃的.如果为false则粒子为死的,此时我们就删除它.在程序中我没有使用活跃的,因为它很好的实现.变量life和fade来控制粒子显示多久以及显示时候的亮度.随着life数值的降低fade的数值也相应降低.这将导致一些粒子比其他粒子燃烧的时间长.

typedef struct						// 创建粒子数据结构
{
bool	active;					// 是否激活
float	life;					// 粒子生命
float	fade;					// fade 衰退 衰减速度

/************************************************************************/
// 变量r,g和b用来表示粒子的红色强度,绿色强度和蓝色强度.
float	r;					// 红色值
float	g;					// 绿色值
float	b;					// 蓝色值

//	变量x,y和z控制粒子在屏幕上显示的位置
float	x;					// X 位置
float	y;					// Y 位置
float	z;					// Z 位置

//	下面三个变量控制粒子在每个轴上移动的快慢和方向.
//	如果xi是负值粒子将会向左移动,正值将会向右移动如果yi是负值粒子将会向下移动,正值将向上,如果zi负值粒子将会向荧屏内部移动,正植将移向观察者.
float	xi;					// X 方向
float	yi;					// Y 方向
float	zi;					// Z 方向

//	最后,另外3个变量!每一个变量可被看成加速度.
float	xg;					// X 方向重力加速度
float	yg;					// Y 方向重力加速度
float	zg;					// Z 方向重力加速度

//	结构的名字为particles.

}particles;
particles particle[MAX_PARTICLES];// 保存1000个粒子的数组

//在颜色数组上我们减少一些代码来存储12种不同的颜色.对每一个颜色从0到11我们存储亮红,亮绿,和亮蓝.
//下面的颜色表里包含12个渐变颜色从红色到紫罗兰色

static GLfloat colors[12][3]=				// 彩虹颜色
{
{1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
{0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
{0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
};

//载入一符名为Particle.bmp的位图

//if (TextureImage[0]=LoadBMP("Data/Particle.bmp"))	// 载入粒子纹理

/* 在 ReSizeGLScene() 之前,我们增加了下面这一段代码。这段代码用来加载位图文件。
* 如果文件不存在,返回 NULL 告知程序无法加载位图。
* 关于用作纹理的图像。图像的宽和高必须是2的n次方;宽度和高度最小必须是64象素;
* 并且出于兼容性的原因,图像的宽度和高度不应超过256象素。如果您的原始素材的宽度
* 和高度不是64,128,256象素的话,使用图像处理软件重新改变图像的大小。*/
AUX_RGBImageRec *LoadBMP(char*Filename)
{
// 首先,我们创建一个文件句柄。句柄是个用来鉴别资源的数值,它使程序能够
//访问此资源。我们开始先将句柄设为 NULL 。
FILE *File=NULL;
if (!Filename)		//确保文件名已提供
{
return NULL;
}
File=fopen(Filename,"r");
if (File)// 文件存在么?
{
fclose(File);
return auxDIBImageLoad(Filename);	// 载入位图并返回指针
}
return NULL;
}
int LoadGLTextures()//载入位图(调用上面的代码)并转换成纹理
{
//然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。
//Status 缺省设为 FALSE (表示没有载入或创建任何东东)。
int Status=FALSE;
//创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。
AUX_RGBImageRec *TextureImage[1];	// 创建纹理的存储空间
//清除图像记录,确保其内容为空
memset(TextureImage,0,sizeof(void *)*1);				// 将指针设为 NULL
if (TextureImage[0]=LoadBMP("Data/Particle.bmp"))	// 载入粒子纹理
{
Status=true;

/*
使用TextureImage[0] 的数据创建纹理。第一行 glGenTextures(1, &texture[0]) 告诉OpenGL
我们想生成一个纹理名字(如果您想载入多个纹理,加大数字)。
第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告诉OpenGL将纹理名字 texture[0] 绑定
到纹理目标上。2D纹理只有高度(在 Y 轴上)和宽度(在 X 轴上)。主函数将纹理名字指派给纹理
数据。本例中我们告知OpenGL,&texture[0] 处的内存已经可用。我们创建的纹理将存储在
&texture[0] 的 指向的内存区域。
*/
glGenTextures(1,&texture[0]);
glBindTexture(GL_TEXTURE_2D,texture[0]);
/*
下面一行告诉OpenGL此纹理是一个2D纹理 ( GL_TEXTURE_2D )。参数“0”代表图像的详细程度,
通常就由它为零去了。参数三是数据的成分数。因为图像是由红色数据,绿色数据,蓝色数据
三种组分组成。 TextureImage[0]->sizeX 是纹理的宽度。如果您知道宽度,您可以在这里填入,
但计算机可以很容易的为您指出此值。 TextureImage[0]->sizey 是纹理的高度。参数零是边框
的值,一般就是“0”。 GL_RGB 告诉OpenGL图像数据由红、绿、蓝三色数据组成。
GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。最后... TextureImage[0]->data
告诉OpenGL纹理数据的来源。此例中指向存放在 TextureImage[0] 记录中的数据。
*/
glTexImage2D(GL_TEXTURE_2D,0,3,TextureImage[0]->sizeX,TextureImage[0]->sizeY,0,
GL_RGB,GL_UNSIGNED_BYTE,TextureImage[0]->data);
/*
下面的两行告诉OpenGL在显示图像时,当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或
缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时OpenGL采用的滤波方式。通常这两种情况下
我都采用 GL_LINEAR 。这使得纹理从很远处到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要
CPU和显卡做更多的运算。如果您的机器很慢,您也许应该采用 GL_NEAREST 。过滤的纹理在放大
的时候,看起来斑驳的很『译者注:马赛克啦』。您也可以结合这两种滤波方式。在近处时使用
GL_LINEAR ,远处时 GL_NEAREST 。
*/
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//线性滤波

}
/*
现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在此处。如果是的话,再查看
数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有
的内存都能释放。
*/
if (TextureImage[0])//纹理是否存在
{
if (TextureImage[0]->data)//纹理图像是否存在
{
free(TextureImage[0]->data);//释放纹理图像占用的内存
}
free(TextureImage[0]);//释放图像结构
}
return Status;
}

GLvoid ReSizeGLScene(GLsizei width,GLsizei height)
{
if (height==0)
{
height=1;
}
glViewport(0,0,width, height);	//重置当前视口

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//设置视口的大小,设置透视投影矩阵
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);//y轴倾斜角,宽高比,z轴近距>0,z轴远距》近距》0

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();				//重置模型视图矩阵
}

int InitGL(GLvoid)					//开始对GL进行所有设置
{
if (!LoadGLTextures())
{
return FALSE;
}

glEnable(GL_TEXTURE_2D);//启用纹理映射
//启用阴影平滑
glShadeModel(GL_SMOOTH);
//黑色背景
glClearColor(0.0f,0.0f,0.0f,0.5f);

//设置深度缓存
glClearDepth(1.0f);
//启用深度缓存
//glEnable(GL_DEPTH_TEST);
/**********19课******************************************************/
/* 我们使用光滑的阴影,清除背景为黑色,关闭深度测试,绑定并映射纹理.启用映射位图后我们选择粒子纹理。
唯一的改变就是禁用深度测试和初始化粒子
*/
/************************************************************************/

glDisable(GL_DEPTH_TEST);						//禁止深度测试
//	下面代码初始化每个粒子.我们从活跃的粒子开始.如果粒子不活跃,它在荧屏上将不出现,
//	无论它有多少life.当我们使粒子活跃之後,我们给它life.我怀疑给粒子生命和颜色渐变
//	是否是最好的方法,但当它运行一次后,效果很好!life满值是1.0f.这也给粒子完整的光亮.

//初始化所有的粒子
for (loop=0;loop<MAX_PARTICLES;loop++)
{
particle[loop].active=true;					// 使所有的粒子为激活状态
particle[loop].life=1.0f;					// 所有的粒子生命值为最大

//我们通过给定的值来设定粒子退色快慢.每次粒子被拉的时候life随着fade而减小.
//结束的数值将是0~99中的任意一个,然后平分1000份来得到一个很小的浮点数0.001-0.1.
//最后我们把结果加上0.003f来使fade速度值不为0

particle[loop].fade=float(rand()%100)/1000.0f+0.003f;		// 随机生成衰减速率

//既然粒子是活跃的,而且我们又给它生命,下面将给它颜色数值.一开始,我们就想每个粒子有不同的颜色.
//我怎么做才能使每个粒子与前面颜色箱里的颜色一一对应那?
//数学很简单,我们用loop变量乘以箱子中颜色的数目与粒子最大值(MAX_PARTICLES)的余数.
//这样防止最后的颜色数值大于最大的颜色数值(12).举例:900*(12/900)=12.1000*(12/1000)=12,等等
//archie怀疑 12/MAX_PARTICLES不是为0???

particle[loop].r=colors[loop*12/MAX_PARTICLES][0];		// 粒子的红色颜色
particle[loop].g=colors[loop*12/MAX_PARTICLES][1];		// 粒子的绿色颜色
particle[loop].b=colors[loop*12/MAX_PARTICLES][2];		// 粒子的蓝色颜色

//现在设定每个粒子移动的方向和速度.我们通过将结果乘于10.0f来创造开始时的爆炸效果.
//我们将会以任意一个正或负值结束.这个数值将以任意速度,任意方向移动粒子.

particle[loop].xi=float((rand()%50)-26.0f)*10.0f;		// 随机生成X轴方向速度
particle[loop].yi=float((rand()%50)-25.0f)*10.0f;		// 随机生成Y轴方向速度
particle[loop].zi=float((rand()%50)-25.0f)*10.0f;		// 随机生成Z轴方向速度

//最后,我们设定加速度的数值.不像一般的加速度仅仅把事物拉下,我们的加速度能拉出,拉下,拉左,拉右,拉前和拉后粒子.
//开始我们需要强大的向下加速度.为了达到这样的效果我们将xg设为0.0f.在x方向没有拉力.
//我们设yg为-0.8f来产生一个向下的拉力.如果值为正则拉向上.我们不希望粒子拉近或远离我们,所以将zg设为0.0f

particle[loop].xg=0.0f;						// 设置X轴方向加速度为0
particle[loop].yg=-0.8f;						//  设置Y轴方向加速度为-0.8
particle[loop].zg=0.0f;						//  设置Z轴方向加速度为0
}

//所作深度测试的类型
glDepthFunc(GL_LEQUAL);

//最好的透视修正
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);

return TRUE;

}

//绘图工作现在为有趣的部分.下面的部分是我们从哪里拉粒子,检查加速度等等.
//你要明白它是怎么实现的,因此仔细的看:)我们重置Modelview巨阵.
//在画粒子位置的时候用glVertex3f()命令来代替tranlations,这样在我们画粒子的时候不会改变modelview巨阵

int DrawGLScene(GLvoid)
{
//清除屏幕和深度缓存
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//重置模型视图矩阵
glLoadIdentity();
/************************************************************************/
/* 下一行代码选择我们使用的纹理。如果您在您的场景中使用多个纹理,您应该使用
glBindTexture(GL_TEXTURE_2D, texture[ 所使用纹理对应的数字 ]) 选择要绑定
的纹理。当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在
glBegin() 和 glEnd() 之间绑定纹理,必须在 glBegin() 之前或 glEnd() 之后
绑定。注意我们在后面是如何使用 glBindTexture 来指定和绑定纹理的。                                                                     */
/************************************************************************/
glBindTexture(GL_TEXTURE_2D,texture[0]);//选择纹理
/************************************************************************/
/* 为了将纹理正确的映射到四边形上,您必须将纹理的右上角映射到四边形的右上角,
纹理的左上角映射到四边形的左上角,纹理的右下角映射到四边形的右下角,纹理的
左下角映射到四边形的左下角。如果映射错误的话,图像显示时可能上下颠倒,侧向
一边或者什么都不是。
glTexCoord2f 的第一个参数是X坐标。0.0f 是纹理的左侧. 0.5f 是纹理的中点,
1.0f 是纹理的右侧。 glTexCoord2f 的第二个参数是Y坐标。 0.0f 是纹理的底部。
0.5f 是纹理的中点, 1.0f 是纹理的顶部。
所以纹理的左上坐标是 X:0.0f,Y:1.0f ,四边形的左上顶点是 X: -1.0f,Y:1.0f 。
其余三点依此类推。
*/
/************************************************************************/
for (loop=0;loop<MAX_PARTICLES;loop++)
{
if (particle[loop].active==TRUE)// 如果粒子为激活的
{
//下面三个变量是我们确定x,y和z位置的暂时变量.
//注意:在z的位置上我们加上zoom以便我们的场景在以前的基础上再移入zoom个位置.
//particle[loop].x告诉我们要画的x的位置particle[loop].y告诉我们要画的y的位置particle[loop].z告诉我们要画的z的位置
float x=particle[loop].x;				// 返回X轴的位置
float y=particle[loop].y;				// 返回Y轴的位置
float z=particle[loop].z+zoom;			// 返回Z轴的位置
//既然知道粒子位置,就能给粒子上色
//particle[loop].r保存粒子的亮红,particle[loop].g保存粒子的亮绿,particle[loop].b保存粒子的亮蓝
//注意我给alpha赋值为粒子生命.当粒子要燃尽时,它会越来越透明直到它最后消失.这就是为什么粒子的生命不应该超过1.0f.
//如果你想粒子燃烧时间长,可降低fade减小的速度

// 设置粒子颜色
glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);
//我们有粒子的位置,并设置颜色了.所以现在我们来画我们的粒子.
//我们用一个三角形带来代替一个四边形这样使程序运行快一点.
//很多3D card画三角形带比画四边形要快的多.有些3D card将四边形分成两个三角形,而有些不.所以我们按照我们自己的想法来,所以我们来画一个生动的三角形带
//画三角形带
/************************************************************************/
/*	1		0

3		2
逆时针为正。从红宝书引述:三角形带就是画一连续的三角形(三个边的多角形)使用vertices V0,V1,V2,
然后V2,V1,V3(注意顺序),然后V2,V3,V4等等.
画三角形的顺序一样才能保证三角形带为相同的表面.
要求方向是很重要的,例如:第一个三角形使用vertices0,1和2画.
第二个三角形用点vertices2,1和3构造.).
注意:两个三角形画点顺序相同.
OpenGL从新整理顶点来保证所有的三角形为同一方向!
注意:你在屏幕上看见的三角形个数是你叙述的顶点的个数减2.
在程序中在我们有4个顶点,所以我们看见二个三角形
*/
/************************************************************************/
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2d(1,1);glVertex3f(x+0.5f,y+0.5f,z);
glTexCoord2d(1,0);glVertex3f(x+0.5f,y-0.5f,z);
glTexCoord2d(0,1);glVertex3f(x-0.5f,y+0.5f,z);
glTexCoord2d(0,0);glVertex3f(x-0.5f,y-0.5f,z);
glEnd();
//现在我们能移动粒子.下面公式可能看起来很奇怪,其实很简单.
//首先我们取得当前粒子的x位置.然后把x运动速度加上粒子被减速1000倍后的值.slowdown=2.0f
//所以如果粒子在x轴(0)上屏幕中心的位置,运动值(xi)为x轴方向+10(移动我们为右),而slowdown等于1,
//我们移向右边以10/(1*1000)或 0.01f速度.如果增加slowdown值到2我们只移动0.005f.
//希望能帮助你了解slowdown如何工作.那也是为什么用10.0f乘开始值来叫象素移动快速,创造一个爆发效果
//y和z轴用相同的公式来计算附近移动粒子
particle[loop].x+=particle[loop].xi/(slowdown*1000);	// 更新X坐标的位置
particle[loop].y+=particle[loop].yi/(slowdown*1000);	// 更新Y坐标的位置
particle[loop].z+=particle[loop].zi/(slowdown*1000);	// 更新Z坐标的位置
//在计算出下一步粒子移到那里,开始考虑重力和阻力.
//在下面的第一行,将阻力(xg)和移动速度(xi)相加.
//我们的移动速度是10和阻力是1.每时每刻粒子都在抵抗阻力.
//第二次画粒子时,阻力开始作用,移动速度将会从10掉到9.第三次画粒子时,阻力再一次作用,移动速度降低到8.
//如果粒子燃烧为超过10次重画,它将会最后结束,并向相反方向移动.因为移动速度会变成负值.
//阻力同样使用于y和z移动速度

particle[loop].xi+=particle[loop].xg;			// 更新X轴方向速度大小
particle[loop].yi+=particle[loop].yg;			// 更新Y轴方向速度大小
particle[loop].zi+=particle[loop].zg;			// 更新Z轴方向速度大小

//下行将粒子的生命减少.如果我们不这么做,粒子无法烧尽.
//我们用粒子当前的life减去当前的fade值.每粒子都有不同的fade值,因此他们全部将会以不同的速度烧尽
particle[loop].life-=particle[loop].fade;		// 减少粒子的生命值,如果life小于0怎么办??
//现在我们检查当生命为零的话粒子是否活着
if (particle[loop].life<0.0f)					// 如果粒子生命值小于0
{

//	如果粒子消失(烧尽),我们将会使它复原.我们给它全值生命和新的衰弱速度.
particle[loop].life=1.0f;				// 产生一个新的粒子
particle[loop].fade=float(rand()%100)/1000.0f+0.003f;	// 随机生成衰减速率
//我们也重新设定粒子在屏幕中心放置.我们重新设定粒子的x,y和z位置为零
particle[loop].x=0.0f;					// 新粒子出现在屏幕的中央
particle[loop].y=0.0f;
particle[loop].z=0.0f;

//在粒子从新设置之后,将给它新的移动速度/方向.
//注意:我增加最大和最小值,粒子移动速度为从50到60的任意值,但是这次我们没将移动速度乘10.
//我们这次不想要一个爆发的效果,而要比较慢地移动粒子.
//也注意我把xspeed和x轴移动速度相加,y轴移动速度和yspeed相加.这个控制粒子的移动方向.
particle[loop].xi=xspeed+float((rand()%60)-32.0f);	// 随机生成粒子速度
particle[loop].yi=yspeed+float((rand()%60)-30.0f);
particle[loop].zi=float((rand()%60)-30.0f);
//最后我们分配粒子一种新的颜色.变量col保存一个数字从1到11(12种颜色),
//我们用这个变量去找红,绿,蓝亮度在颜色箱里面.
//如果col等于0,我们要看第一个组
particle[loop].r=colors[col][0];			// 设置粒子颜色
particle[loop].g=colors[col][1];
particle[loop].b=colors[col][2];
}

//下行描述加速度的数值是多少.通过小键盘8号键,我们增加yg(y 地心引力)值.这引起向上的力.
//如果这个程序在循环外面,那么我们必须生成另一个循环做相同的工作,因此我们最好放在这里

// 如果小键盘8被按住,增加Y轴方向的加速度
if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;
// 如果小键盘2被按住,减少Y轴方向的加速度 ,减小yg值,引起向下的力
if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;
// 如果小键盘6被按住,增加X轴方向的加速度
if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;
//	最后如果4号键被按下则增加向左的拉力.这些按键给了我们很酷的结果.
//举例来说:你可以用粒子造一条向上的水流.通过增加向下的引力可以形成泉水
// 如果小键盘4被按住,减少X轴方向的加速度
if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;

//	通过按住tab键所有粒子都回到屏幕中心.所有的粒子在从新开始运动,再产生一个大的爆发.在粒子变弱之后,你最初的效果会再一次出现

if (keys[VK_TAB])						// 按Tab键,使粒子回到原点
{
particle[loop].x=0.0f;
particle[loop].y=0.0f;
particle[loop].z=0.0f;
particle[loop].xi=float((rand()%50)-26.0f)*10.0f;	// 随机生成速度
particle[loop].yi=float((rand()%50)-25.0f)*10.0f;
particle[loop].zi=float((rand()%50)-25.0f)*10.0f;
}
}
}

return TRUE;

}

//程序退出之前调用 依次释放着色描述表RC,设备描述表DC和窗口句柄
GLvoid KillGLWindow(GLvoid)
{

if (fullscreen)
{
ChangeDisplaySettings(NULL,0);  //Windows API 把缺省显示设备的设置改变为由第一个参数设定的图形模式
ShowCursor(TRUE);				//Window32API 显示鼠标

}
if (hRC)		//是否拥有渲染描述表
{

if (!wglMakeCurrent(NULL,NULL))
{

MessageBox(NULL,"释放DC或RC失败","Shutdown Error",MB_OK|MB_ICONINFORMATION);
}

if (!wglDeleteContext(hRC))
{
MessageBox(NULL,"释放RC失败","Shutdown Error",MB_OK|MB_ICONINFORMATION);
}
hRC=NULL;					//将hRC设为NULL

}
if (hDC&&!ReleaseDC(hWND,hDC))
{
MessageBox(NULL,"不能释放DC失败","Shutdown Error",MB_OK|MB_ICONINFORMATION);
hDC=NULL;
}

if (hWND&&!DestroyWindow(hWND))
{
MessageBox(NULL,"释放窗口句柄失败","Shutdown Error",MB_OK|MB_ICONINFORMATION);
hWND=NULL;
}

if (!UnregisterClass("OpenGL",hInstance))
{
MessageBox(NULL,"不能注销窗口类","Shutdown Error",MB_OK|MB_ICONINFORMATION);
hInstance=NULL;
}
}

//创建OpenGL窗口  窗口标题 宽 高 颜色位 全屏标志
BOOL CreateGLWindow(char* title,int width,int height,int bits,bool fullscreenflag)
{
//保存Windows相匹配的像素格式值变量
GLuint PixelFormat;
//窗口类结构
WNDCLASS wc;

DWORD dwStyle;//窗口风格
DWORD dwExStyle;//扩展窗口风格

RECT WindowRect;
WindowRect.left=(long)0;
WindowRect.right=(long)width;
WindowRect.top=(long)0;
WindowRect.bottom=(long)height;

fullscreen=fullscreenflag;

hInstance=GetModuleHandle(NULL);//获取一个应用程序或动态链接库的模块句柄 ,NULL返回自身应用程序句柄
wc.style=CS_VREDRAW|CS_HREDRAW|CS_OWNDC;//移动时重画,并为窗口取得DC
wc.lpfnWndProc=(WNDPROC)WndProc;		//WndProc处理消息
wc.cbClsExtra		= 0;						// 无额外窗口数据
wc.cbWndExtra		= 0;						// 无额外窗口数据
wc.hInstance=hInstance;							//设置实例
wc.hIcon=LoadIcon(NULL,IDI_WINLOGO);			//装入缺省图标
wc.hCursor=LoadCursor(NULL,IDC_ARROW);			//装入鼠标指针
wc.hbrBackground=NULL;							//GL不需要背景
wc.lpszMenuName=NULL;							//不需要菜单
wc.lpszClassName="OpenGL";						//设定类名字

//注册窗口类
if (!RegisterClass(&wc))
{
MessageBox(NULL,"注册窗口失败"," Error",MB_OK|MB_ICONEXCLAMATION);//EXCLAMATION感叹号
return FALSE;
}

if (fullscreen)//全屏模式
{
DEVMODE dmScreenSettings;					//设备模式
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));//确保内存清空为0
dmScreenSettings.dmSize=sizeof(dmScreenSettings);	//DEVMODE结构的大小
dmScreenSettings.dmPelsWidth=width;
dmScreenSettings.dmPelsHeight=height;				//所选屏幕高度
dmScreenSettings.dmBitsPerPel=bits;					//每像素颜色数
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSHEIGHT|DM_PELSWIDTH;

if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
// 若模式失败,提供两个选项:退出或在窗口内运行。
if (MessageBox(NULL,"全屏模式在当前显示卡上设置失败!\n使用窗口模式","Nehe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=false;
}
else//用户选择退出
{
MessageBox(NULL,"程序将被关闭","Error",MB_OK|MB_ICONSTOP);
return FALSE;
}

}
}

if (fullscreen)
{
dwExStyle=WS_EX_APPWINDOW;
dwStyle=WS_POPUP;
ShowCursor(FALSE);		//隐藏鼠标指针
}
else//不是全屏
{
dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;
dwStyle=WS_OVERLAPPEDWINDOW;
}
AdjustWindowRectEx(&WindowRect,dwStyle,FALSE,dwExStyle);	//Windows API真正适合窗口

if (!(hWND=CreateWindowEx(dwExStyle,
"OpenGL",
title,
WS_CLIPSIBLINGS|WS_CLIPCHILDREN|dwStyle,//必须的窗体风格属性
0,0,
WindowRect.right-WindowRect.left,
WindowRect.bottom-WindowRect.top,//计算调整好的窗口高度
NULL,//无父窗口
NULL,//无子菜单
hInstance,
NULL)))//不向WM_CREATE传递消息
{
KillGLWindow();
MessageBox(NULL,"不能创建窗口","Error",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}

static	PIXELFORMATDESCRIPTOR pfd=					// /pfd 告诉窗口我们所希望的东东,即窗口使用的像素格式
{
sizeof(PIXELFORMATDESCRIPTOR),					// 上述格式描述符的大小
1,								// 版本号
PFD_DRAW_TO_WINDOW |						// 格式支持窗口
PFD_SUPPORT_OPENGL |						// 格式必须支持OpenGL
PFD_DOUBLEBUFFER,						// 必须支持双缓冲
PFD_TYPE_RGBA,							// 申请 RGBA 格式
bits,								// 选定色彩深度
0, 0, 0, 0, 0, 0,						// 忽略的色彩位
0,								// 无Alpha缓存
0,								// 忽略Shift Bit
0,								// 无累加缓存
0, 0, 0, 0,							// 忽略聚集位
16,								// 16位 Z-缓存 (深度缓存)
0,								// 无蒙板缓存
0,								// 无辅助缓存
PFD_MAIN_PLANE,							// 主绘图层
0,								// Reserved
0, 0, 0								// 忽略层遮罩
};

if (!(hDC=GetDC(hWND)))
{
KillGLWindow();							// 重置显示区
MessageBox(NULL,"不能创建一种相匹配的像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
{
KillGLWindow();
MessageBox(NULL,"不能创建像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd))				// 能够设置象素格式么?
{
KillGLWindow();							// 重置显示区
MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE;							// 返回 FALSE
}

if (!(hRC=wglCreateContext(hDC)))
{
KillGLWindow();							// 重置显示区
MessageBox(NULL,"不能创建OpenGL渲染描述表","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (!wglMakeCurrent(hDC,hRC))
{

KillGLWindow();							// 重置显示区
MessageBox(NULL,"不能激活当前的OpenGL渲然描述表","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}

ShowWindow(hWND,SW_SHOW);
SetForegroundWindow(hWND);					//提高优先级
SetFocus(hWND);								//设置焦点
ReSizeGLScene(width,height);

if (!InitGL())								// 初始化新建的GL窗口
{
KillGLWindow();							// 重置显示区
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;							// 返回 FALSE
}

return TRUE;

}

LRESULT CALLBACK WndProc(	HWND	hWnd,					// 窗口的句柄
UINT	uMsg,					// 窗口的消息
WPARAM	wParam,					// 附加的消息内容
LPARAM	lParam)					// 附加的消息内容
{

switch (uMsg)								// 检查Windows消息
{
case WM_ACTIVATE:						// 监视窗口激活消息
{
if (!HIWORD(wParam))					// 检查最小化状态
{
active=TRUE;					// 程序处于激活状态
}
else
{
active=FALSE;					// 程序不再激活
}

return 0;						// 返回消息循环
}

case WM_SYSCOMMAND:						// 系统中断命令
{
switch (wParam)						// 检查系统调用
{
case SC_SCREENSAVE:				// 屏保要运行?
case SC_MONITORPOWER:				// 显示器要进入节电模式?
return 0;					// 阻止发生
}
break;							// 退出
}

case WM_CLOSE:							// 收到Close消息?
{
PostQuitMessage(0);					// 发出退出消息
return 0;						// 返回
}

case WM_KEYDOWN:						// 有键按下么?
{
keys[wParam] = TRUE;					// 如果是,设为TRUE
return 0;						// 返回
}

case WM_KEYUP:							// 有键放开么?
{
keys[wParam] = FALSE;					// 如果是,设为FALSE
return 0;						// 返回
}

case WM_SIZE:							// 调整OpenGL窗口大小
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));		// LoWord=Width,HiWord=Height
return 0;						// 返回
}
}

return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

int WINAPI WinMain(	HINSTANCE	hInstance,				// 当前窗口实例
HINSTANCE	hPrevInstance,				// 前一个窗口实例
LPSTR		lpCmdLine,				// 命令行参数
int		nCmdShow)				// 窗口显示状态
{

MSG	msg;								// Windowsx消息结构
BOOL	done=FALSE;							// 用来退出循环的Bool 变量

if (MessageBox(NULL,"你想在全屏模式下运行么?", "设置全屏模式",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE;						// FALSE为窗口模式
}

if (!CreateGLWindow("NeHe's 3D空间",640,480,16,fullscreen))
{
return 0;							// 失败退出
}

while(!done)								// 保持循环直到 done=TRUE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))			// 有消息在等待吗?
{

if (msg.message==WM_QUIT)				// 收到退出消息?
{
done=TRUE;					// 是,则done=TRUE
}
else							// 不是,处理窗口消息
{

TranslateMessage(&msg);				// 翻译消息
DispatchMessage(&msg);				// 发送消息
}
}
else								// 如果没有消息
{

if (active)						// 程序激活的么?
{
if (keys[VK_ESCAPE])				// ESC 按下了么?
{
done=TRUE;				// ESC 发出退出信号
}

else						// 不是退出的时候,刷新屏幕
{

DrawGLScene();				// 绘制场景
SwapBuffers(hDC);			// 交换缓存 (双缓存)
}
}

if (keys[VK_F1])					// F1键按下了么?
{
keys[VK_F1]=FALSE;				// 若是,使对应的Key数组中的值为 FALSE
KillGLWindow();					// 销毁当前的窗口
fullscreen=!fullscreen;				// 切换 全屏 / 窗口 模式
// 重建 OpenGL 窗口
if (!CreateGLWindow("NeHe's 3D空间",640,480,16,fullscreen))
{
return 0;				// 如果窗口未能创建,程序退出
}
}
//下面的代码检查"+"是否被按下.如果它和slowdown一起实现则slowdown减少0.01f.粒子就可以较快速地移动.
if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f;		// 按+号,加速粒子
if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f;	// 按-号,减速粒子
//下面的代码检测Page Up是否被按下.如果是,则zoom增加.从而导致粒子靠近我们
if (keys[VK_PRIOR]) zoom+=0.1f;		// 按Page Up键,让粒子靠近视点
//	下行代码检测Page Down是否别按下,如果是,则zoom减小.从而导师粒子离开我们
if (keys[VK_NEXT]) zoom-=0.1f;		// 按Page Down,让粒子远离视点
if (keys[VK_RETURN] && !rp)		// 按住回车键,切换彩虹模式
{
rp=true;
rainbow=!rainbow;
}
if (!keys[VK_RETURN]) rp=false;
//第一行检查space键是否被按下并没有被一直按着.并检查彩虹模式是否开始运行,如果是,检查delay是否大于25
//delay是我创建的显示彩虹效果的数值.如果你曾经改变颜色结构,粒子将显示不同颜色.
//通过创建一个delay,在颜色改变之前,一组粒子将是一种颜色.
//如果space按下,彩虹运行,delay值大于25则颜色改变
if ((keys[' '] && !sp) || (rainbow && (delay>25)))	// 空格键,变换颜色
{
//下面行是为了当space按下则彩虹关掉而设置的.如果我们不关掉彩虹模式,颜色会继续变化直到enter再被按下
//也就是说人们按下space来代替enter是想叫粒子颜色自己变化
if (keys[' ']) rainbow=false;
//如果space键被按下,或者彩虹模式已开始,并且delay大于25,我们叫计算机知道space键被按下
//通过叫sp为true.然后我们将delay设定回0以便它能再到25.
//最后我们增加col的值以便它通过颜色箱改变成另一个颜色.
sp=true;
delay=0;
col++;
//如果颜色值大于11,我们把它重新设为零
if (col>11) col=0;
}
//最后如果space键不被按下,我们将sp设为false。
if (!keys[' '])	sp=false;		// 如果释放空格键,记录这个状态
//现在对粒子增加一些控制.还记得我们从开始定义的2变量么?一个xspeed,一个yspeed.
//在粒子燃尽之后,我们给它新的移动速度且把新的速度加入到xspeed和yspeed中.
//这样当粒子被创建时将影响粒子的速度.
//举例来说:粒子在x轴上的速度为5在y轴上的速度为0.
//当我们减少xspeed到-10,我们将以-10(xspeed)+5(最初的移动速度)的速度移动.
//这样我们将以5的速度向左移动.明白了么??
//无论如何,下面的代码检测UP是否被按下.如果yspeed增加这将引起粒子向上运动.
//最大速度不超过200.速度再快就不好看了

// 按上增加粒子Y轴正方向的速度
if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;
// 按下减少粒子Y轴正方向的速度
if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;
// 按右增加粒子X轴正方向的速度
if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;
// 按左减少粒子X轴正方向的速度
if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;
delay++;			// 增加彩虹模式的颜色切换延迟

}
}

KillGLWindow();								// 销毁窗口
return (msg.wParam);							// 退出程序
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息