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); // 退出程序 }
相关文章推荐
- 着色:Jeff Molofee(NeHe) 的 OPENGL 教程-第三课
- NeHe的opengl教程delphi版(7)----滤波
- 位图波浪:Jeff Molofee(NeHe) 的 OPENGL 教程-第十一课
- NeHe OpenGL指南-基于江超宇版的修正(一)
- 使用OpenGL VBO扩展 --Nehe教程
- 用OpenInventor实现的NeHe OpenGL教程-第十课
- NeHE中文学习网址
- NeHe的opengl教程delphi版(9)----星星
- 用OpenInventor实现的NeHe OpenGL教程-第四十二课
- [NEHE Couse] 06.Texture Map
- NeHe OpenGL教程 (十五)
- JeffMolofee(NeHe)的OPENGL教程-第一课[6]-WndProc
- 关于NeHe OpenGL网站代码运行问题
- 用OpenInventor实现的NeHe OpenGL教程-第十一课
- 用OpenInventor实现的NeHe OpenGL教程-第二十八课
- 用OpenInventor实现的NeHe OpenGL教程-第四十四课
- NeHe OpenGL第十一课:飘动的旗帜
- NeHe OpenGL第二十七课:影子
- NeHe OpenGL第四十五课:顶点缓存
- NeHe OpenGL教程 13 位图文字