烟花粒子系统是一种基于MFC对话框下的操作,主要是运用了OpenGL库函数。OpenGL是一个功能强大的开放图元库,用户可以很方便地开发所需要地有多种特殊视觉(如光照,纹理,透明,阴影)的三维图形,烟花粒子系统就是在该库函数下的产物,要想运行烟花粒子系统必须在VC6.0环境下安装OpenGL压缩包以及fmod压缩包。它主要通过运用粒子纹理映射,使烟花产生不同的爆炸效果,给看者一种视觉的享受,在该系统中还添加了爆炸的音乐,使烟花更加像真实存在的。为了方便用户操作,我们还设计了是否全屏显示的提示,便于用户自己选择怎样显示。
我们知道了烟花粒子系统是通过纹理映射来实现烟花的爆炸,在爆炸过程中,我们还添加了七种烟花的爆炸效果,使得烟花更加的逼真好看。就烟花爆炸时的形状我们又载入了四种不同形状的位图,使得烟花爆炸时,姿态更完美绚丽。在烟花爆炸显示的时候,我们也做了一个小小的提示,用户可以根据自己的喜好选择所要播放烟花屏幕的形态,是全屏还是小屏。基于以上思路我们就一步步完成了烟花粒子系统。
在以上讨论之后,我们对OpenGL烟花例子系统的具体功能有了一些认识。OpenGL 是一个功能强大的开放图元库,用户可以很方便地开发所需要地有多种特殊视觉(如光照,纹理,透明,阴影)的三维图形,与软件平台无关的三维图形软件包,可以运行多种窗口系统之上。
OpenGL库:
l 包括115个基本函数
l 函数以gl开头
l 例:glColor3f(), glTranslate3f(). 完成图元的定义、几何变换、投影等功能
在VC6.0上配置OpenGL:
1.几个.dll文件 放入 C:\WINDOWS\system32
2.把GL文件夹 放入 X:\Program Files\Microsoft Visual Studio\VC98\Include
3.几个 .lib 文件 放入 X:\Program Files\Microsoft Visual Studio\VC98\Lib
4.ProjectàSettings LINK标签
在Object/Library Modules选项 (在kernel.32前)增加
OpenGL32.lib GLu32.lib GLaux.lib Glut32.lib
然后单击”OK”按钮
5.在程序前,需添加头文件:
#include
在VC6.0平台编写OpenGL程序:
本课题是基于GLUT来编写OpenGL程序
利用如下步骤新建一个程序:
点击菜单“ File” à “New” à Projects à Win32 Console Application àempty project
在Project | Add to Project | New ,然后选择“C++ Source File”
在出现的页面中编写编辑代码
OpenGL包含图元生成、投影、光照、光栅化等图形显示功能所需的过程。就该系统做出如下的分析。框架界面:因为人们的喜好不同,为了满足不同人的喜好,烟花粒子系统在显示于主界面时,是否全屏显示因人而异,我们就对显示界面做了一个是否是全屏的提示,以方便用户的选择。
加载音乐:加载音乐是为了使烟花爆炸时的效果更形象,通过所加的音乐让人们看了后有种身临其境的感觉。加载音乐的方法就是在粒子系统中载入fmod声音库函数,通过函数CreateSound()、BindSound()、InitSound()来实现音乐的播放。
在粒子设计方面,每个粒子的生命是整个系统的关键,为了保证烟花爆炸会一直发生,就必须对粒子的设计做详细的描述。
尽管我们知道一个完美的粒子设计对一个烟花系统的成功有多大的意义,但是以现在我们的水平来看,想要做到美观、流畅是很不容易的,因为我们现在学的知识、水平有限,像纹理映射啊、监粒子系统啊什么的我们根本就一无所知,也不知道那是什么东西,不知道什么条件下才去用什么样的东西,但是我们并没有因此放弃,我们相互鼓励,不到最后一刻决不放弃。有了这样的干劲,我们开始去网上、图书馆查资料,尽我们所知道的各种途径去获取与OpenGL粒子有关的各种知识,我们的努力没有白费,终于在几天之后,对于做出一个什么样的烟花有了大致的想法,有了一个不太清楚的轮廓,我们就大致把做成什么样的烟花、烟花爆炸时有什么样的效果设想了一下。
通过粒子数据结构给粒子在三维空间中的位置、速度、上一个节点、下一个节点、尾巴的加速度进行了描述。还对粒子的颜色、寿命、衰减速度、粒子大小、是否有尾巴、尾巴增加速度,清晰的概括出来,指引着下面程序的添加,使整个烟花系统很明朗。
粒子链表的构建使得粒子在燃放过程中,源源不断的出现,当粒子的生命减少到小于0时,粒子死亡,然后通过粒子链表的构建使用void AddParticle(Particle ex) 函数,增加新的粒子,并对增加的粒子进行一切属性的赋值。
虽然在上面OpenGL功能分析以及烟花粒子设计中,把一些问题都说了,但是似乎还是有点不清楚,所以又对粒子做了一些操作以便使我们的思路更清晰。
如果只是单纯的粒子,而不加对它进行修饰,它也无非就是一些散乱的粒子,没有什么价值所在,更不会像烟花那样出现绚烂多姿的形态和五彩的色彩,吸引人们的眼球,所以我们就对这些纯粹的粒子做了一些修饰。在粒子的程序中加入了一些爆炸效果,使得烟花粒子看起来更加形象,在粒子爆炸效果中粒子的运动方向都是随机的,粒子的大小也需要自己添加,粒子是否是爆炸粒子,粒子爆炸时有没有产生爆炸效果、以及粒子爆炸时有没有尾巴,都在爆炸效果中有清晰的代码。这个程序系统中总共添加了七种爆炸效果,每种爆炸效果中粒子的属性多多少少都有些改变,这就使得烟花爆炸时看起来更加真实。
在以上讨论之后,我们对OpenGL烟花例子系统的具体功能做出如下的分析。框架界面:因为人们的喜好不同,为了满足不同人的喜好,烟花粒子系统在显示于主界面时,是否全屏显示因人而异,我们就对显示界面做了一个是否是全屏的提示,以方
便用户的选择。音乐的加入主要是想给人们一种更加真实的感觉,如身临其境。
按照我们之前的思路已经把整个框架给描绘出来了,就差实际去动手做了。可以先创建一个框架。我们使用的是MFC。MFC是Win API与C++的结合。API,即微软提供的Windows下应用程序的编程语言接口,是一种软件编程的规范,但不是一种程序开发语言本身,可以允许用户使用各种各样的第三方的编程语言来进行对Windows下应用程序的开发,使这些被开发出来的应用程序能在Windows下运行,编程语言函数本质上全部源于API,因此用它们开发出来的应用程序都能工作在Windows的消息机制和绘图里,遵守Windows作为一个操作系统的内部实现,这其实也是一种必要。框架界面:因为人们的喜好不同,为了满足不同人的喜好,烟花粒子系统在显示于主界面时,是否全屏显示因人而异,我们就对显示界面做了一个是否是全屏的提示,以方便用户选择。
包含头文件:写下代码的前4行,它们包括了我们使用的每个库文件的头文件。如下所示:
#include
#include
#include
#include
4、写下一些全局变量:将这些变量设置为全局变量是因为我们需要在程序中的多个地方访问这些变量。
HGLRC hRC=NULL; // 永久着色描述表
HDC hDC=NULL; // GDI设备描述表
HWND hWnd=NULL; // 保存我们的窗口句柄
HINSTANCE hInstance; // 保存程序的实例
boolkeys[256]; // 用于键盘按键的数组,用于保存键盘的状态
boolactive=TRUE; // 窗口的活动标志,缺省为TRUE
boolfullscreen=TRUE; // 全屏标志缺省设定成全屏模式,提示用户选择窗口模式
5、定义ReSizeGLScene函数:下面的函数发生在窗口大小变化时,其函数的作用是为了在窗口大小改变时,我们的三维场景也能作相应的调整
6、GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
// 重新调整OpenGL 窗口,初始化时窗口大小也会变化一次
{
if (height==0) //防止被0除,看后面的(GLfloat)width/(GLfloat)height
{
height=1; // 让高度为1
}
glViewport(0,0,width,height); // 重置视口,完成程序后试着改变参数
glMatrixMode(GL_PROJECTION); // 选择投影矩阵,下面的改变都针对投影矩阵
glLoadIdentity(); // 将投影矩阵重置为单位矩阵
// 计算窗口的宽高比,用于设置透视投影
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // 选择模型矩阵,后面的操作都针对模型视图矩阵
glLoadIdentity(); //将模型视图矩阵重置为单位矩阵
}
Ok,上面的代码比较难懂,这里解释一下OpenGL的状态机概念,OpenGL的设置都像一个开关,开启了之后或者设置了之后,后面的程序都要受到该设置的影响,glMatrixMode(GL_PROJECTION);是用于设置透视效果的,如果后面没有glMatrixMode(GL_MODELVIEW);那么有可能图形就画不出来了。另一个概念是透视投影(远处的物体小,近处的物体大),其实还有一种是正交投影(远近一个样),看下面的图:
在我们的代码中fovy被设置为45,aspect被设置(GLfloat)width/(GLfloat)height,透视图跟我们拍照的原理相似,远处的物体成像小,近处的成像大,而在这个锥体外的物体是不能成像的。
6、定义初始化函数InitGL:这个函数主要用于初始化的工作。
int InitGL(GLvoid) // 所有OpenGL的初始设置
{
glShadeModel(GL_SMOOTH); // 启动平滑着色,如果设置为GL_FLAT,则不平滑过渡
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 清除颜色缓存的颜色为黑色,也就是背景色
glClearDepth(1.0f);//使用1.0来清除深度缓存中的值
glEnable(GL_DEPTH_TEST); // 启动深度检测
glDepthFunc(GL_LEQUAL);// 深度检测的类型
return TRUE;
}
这里的一个主要概念是缓存,我们都知道屏幕显示的方案为双缓存机制,也就是先绘制到后台缓存,然后通过缓存拷贝一次性的显示出来,这样可以防止闪烁。帧缓存可以包括颜色缓存、深度缓存等。颜色缓存就是保存了每个像素的颜色R、G、B三个值,而深度缓存就是保存了每个像素的Z值。这里要说明一下深度检测的作用了:假如我们有两个物体A和B,从你的眼光看去,加入A在某个像素上能够遮挡住B的,也就是A在某像素点的Z值比B的Z值小(Z轴是从屏幕朝外的)。但是你如果不启动深度检测,哪么如果先画A后画B,那就变成B遮挡A了,而深度检测启动后,不管先画A还是先画B,都能保证正确的遮挡关系。
7、绘图函数DrawGLScene:这个函数是图形能够绘制的关键,所有的绘图效果都在这里面了。当然现在基本上没有绘制任何画面(事实上就是绘制成一片漆黑)
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓存和深度缓存,也就是使用在InitGL中设置的背景色和深度值来清空,因此就变成黑色了
glLoadIdentity(); // 重置模型视图矩阵
return TRUE;
}
8、窗口消息处理函数WndProc:这个函数用于处理所有的Windows消息。
LRESULT CALLBACK WndProc( HWND hWnd, //窗口句柄
UINT uMsg, //窗口的消息
WPARAM wParam, //窗口消息的附带信息
LPARAM lParam) //窗口消息的附带信息
{
switch (uMsg) // 检测消息类型
{
case WM_ACTIVATE: // 窗口是否激活
{
if (!HIWORD(wParam)) // 最小化状态
{
active=TRUE; // 活动的
}
else
{
active=FALSE; // 非活动的
}
return 0;
}
case WM_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));//还记得这个函数吗?
return 0;
}
}
// 其它消息由默认消息处理函数处理
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
9、释放窗口资源函数KillGLWindow:这个函数在程序退出时被调用,由于在窗口创建时(现在还没有做呢,在下面)创建了很多资源,因此要释放掉。
GLvoid KillGLWindow(GLvoid) // Kill The Window
{
if (fullscreen) // 在全屏模式下?
{
ChangeDisplaySettings(NULL,0); // 切换到桌面模式
ShowCursor(TRUE); // 显示鼠标光标
}
if (hRC) // Rendering Context不为空?
{
if (!wglMakeCurrent(NULL,NULL)) // 不在使用DC 和 RC?
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) //能释放RC?
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // 设置RC为NULL
}
if (hDC && !ReleaseDC(hWnd,hDC)) //能释放DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // 设置DC 为 NULL
}
if (hWnd && !DestroyWindow(hWnd)) // 能摧毁窗口?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // 设置hWnd为NULL
}
if (!UnregisterClass("OpenGL",hInstance)) //卸载窗口类
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // 设置hInstance 为NULL
}
}
10、创建窗口函数CreateGLWindow:这个函数看上去很长,但它只完成一个功能,那就是OpenGL窗口的创建,只要建立适合于OpenGL绘图的窗口,我们才可以使用OpenGL绘制图形。
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
GLuint PixelFormat; // 像素格式
WNDCLASS wc; // 窗口类结构
DWORD dwExStyle; // 窗口扩展风格
DWORD dwStyle; // 窗口风格
RECT WindowRect; // 窗口对应的矩形
WindowRect.left=(long)0;
WindowRect.right=(long)width;
WindowRect.top=(long)0;
WindowRect.bottom=(long)height;
fullscreen=fullscreenflag; //是否全屏模式记录下来
hInstance = GetModuleHandle(NULL); // 窗口的Instance
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc; // 还记得这个函数吗?
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 默认图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标样式
wc.hbrBackground = NULL; // OpenGL绘图,不需要背景的
wc.lpszMenuName = NULL; // 目前没有菜单,要菜单可以在这里指定
wc.lpszClassName = "OpenGL";// 窗口类名字,卸载类的时候要用到的
if (!RegisterClass(&wc)) //注册类
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (fullscreen) // 进入全屏模式?
{
DEVMODE dmScreenSettings; // 设备模式
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // 清空
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // 大小
dmScreenSettings.dmPelsWidth = width; // 宽度
dmScreenSettings.dmPelsHeight = height; // 高度
dmScreenSettings.dmBitsPerPel = bits; // 每个像素的位数
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
// 设置窗口模式,注意:CDS_FULLSCREEN消除开始菜单
if(ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
// 模式设置失败时有两个选择
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=FALSE; // 选择是,进入窗口模式
}
else
{
// 否则,程序关闭
MessageBox(NULL,"Program Will Now Close.","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); //大小
//真正创建窗口
if (!(hWnd=CreateWindowEx( dwExStyle, // 扩展风格
"OpenGL", // 类名
title, // 窗口标题
dwStyle | // 窗口风格
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN,
0, 0, // 窗口位置
WindowRect.right-WindowRect.left, // 宽度
WindowRect.bottom-WindowRect.top, // 高度
NULL, // 父窗口为空
NULL, // 菜单
hInstance, // Instance
NULL))) //
{
KillGLWindow();
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
static PIXELFORMATDESCRIPTOR pfd= // 像素格式描述
{
sizeof(PIXELFORMATDESCRIPTOR), // Size
1, // 版本号
PFD_DRAW_TO_WINDOW |// 窗口必须要
PFD_SUPPORT_OPENGL | // 窗口支持OpenGL
PFD_DOUBLEBUFFER, // 双缓存
PFD_TYPE_RGBA, // RGBA格式
bits, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
0, // No Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
16, // 16位Z-Buffer (深度缓存,还记得吗)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
if (!(hDC=GetDC(hWnd))) // 获取DC?
{
KillGLWindow();
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) //查询像素格式是否支持?
{
KillGLWindow();
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // 像素格式能设置成功吗?
{
KillGLWindow();
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (!(hRC=wglCreateContext(hDC))) // 能获取RC?
{
KillGLWindow();
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if(!wglMakeCurrent(hDC,hRC)) // 能激活RC?
{
KillGLWindow();
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
ShowWindow(hWnd,SW_SHOW); // 显示窗口,这时候窗口才能看到
SetForegroundWindow(hWnd); // 优先级稍微高点
SetFocus(hWnd); // 焦点定位在窗口上
ReSizeGLScene(width, height); // 窗口变化一次,调整透视投影
if (!InitGL()) // 这里是OpenGL的初始化,一定是在窗口创建之后
{
KillGLWindow();
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE; // Success
}
10、主函数WinMain:这个函数是我们程序运行的起点,
int WINAPI WinMain( HINSTANCE hInstance, // 句柄
HINSTANCE hPrevInstance, // 先前句柄
LPSTR lpCmdLine, // 命令行参数
int nCmdShow) // 窗口状态
{
MSG msg; // 窗口消息
BOOL done=FALSE; // 标志变量,用于程序循环控制
// 由用户决定是否进入全屏模式
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // 窗口模式
}
// 创建我们的窗口
if (!CreateGLWindow("OpenGL Demo",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;
}
else // 否则处理窗口消息
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else // 没有消息,此时我们不断的绘制图形
{
if (active) // 程序激活?
{
if (keys[VK_ESCAPE]) // ESC按下?
{
done=TRUE;
}
else // 更新屏幕,不断的绘制
{
DrawGLScene(); //绘制到后台缓存
SwapBuffers(hDC); //双缓存机制,交换
}
}
if (keys[VK_F1]) // F1按下?
{
keys[VK_F1]=FALSE; // 设置为FALSE
KillGLWindow();// 先摧毁窗口
fullscreen=!fullscreen; // 切换模式
// 创新创建窗口,此时的模式已经在窗口和全屏之间切换
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0;
}
}
}
}
//关闭程序
KillGLWindow();// 摧毁窗口
return (msg.wParam); //退出程序
}
在烟花爆炸的过程中,如果只单纯的显示烟花爆炸时的缤纷,而没有音乐的陪衬,那就如只有鲜花没有绿叶一样,让人们产生视觉疲劳,为了避免这样的效果,我们就在烟花系统中通过导入fmod库,对烟花粒子系统加载了音乐,使得其效果更加完美。具体操作代码如下:
void CreateSound(char name[80], int num)
{
sound[num] = FSOUND_Stream_OpenFile(name, FSOUND_HW3D || FSOUND_LOOP_OFF,0);
}
void InitSound(void)
{
FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
FSOUND_SetBufferSize(50);
FSOUND_SetDriver(0);
FSOUND_Init(44100, 32, 0);
CreateSound("baozha.wav", 0);//爆炸声
CreateSound("shengkong.wav", 1);//烟花升空声音
}
void BindSound(int num)
{
FSOUND_Stream_Play(FSOUND_FREE, sound[num]);
}
首先我们定义粒子系统的数据结构,这里我们采用了双向链表。每一个粒子结点都含有向前指针和向后指针,所有这些结点就组成了一个粒子系统。其中一个粒子结点的数据结构如下,从这里我们可以看出粒子有很多属性。其中最基本的属性是粒子的位置、速度、颜色、寿命、衰减速度,大小等等。这些属性在绘制如雨雪等其它效果时也一定会用到。请把这部分代码放在全局变量的位置。代码如下:
typedef struct tag_PARTICLE
{
GLfloat xpos;//(xpos,ypos,zpos)为粒子的位置
GLfloat ypos;
GLfloat zpos;
GLfloat xspeed;//(xspeed,yspeed,zspeed)为粒子的速度
GLfloat yspeed;
GLfloat zspeed;
GLfloat r;//(r,g,b)为粒子的颜色
GLfloat g;
GLfloat b;
GLfloat life;//粒子的寿命
GLfloat fade;//粒子的衰减速度
GLfloat size;//粒子大小,可以接合粒子位置,计算边缘值
GLbyte bFire;//是否为烟花粒子,即代表烟花的粒子,这种粒子小于最大烟花数
GLbyte nExpl;//对应的爆炸效果,我们根据该值的不同来绘制多种效果
GLbyte bAddParts;//该粒子是否含有尾巴粒子
GLfloat AddSpeed;//尾巴粒子的增加速度
GLfloat AddCount;//尾巴粒子的增加量
tag_PARTICLE* pNext;//下一粒子结点
tag_PARTICLE* pPrev;//上一粒子结点
} Particle,*pParticle;
同时,我们紧跟着上面的代码,加入几个全局变量,它们用于定义每个烟花的粒子链表和粒子数目,烟花个数,当前有几个烟花。你可以适当的修改,但粒子数目可不能太多,这样机器画起来可吃力了。
pParticle Particles=NULL; //粒子链表
#define MAX_PARTICLES 1000 //最大粒子数
#define MAX_FIRES 5 //最大烟花数
UINT nOfFires=0; //当前的烟花数,这个在我们程
我们写了几个辅助函数,目的就是为了便于构造粒子链表,也就是我们那个双向链表了。下面的函数用于将一个粒子插入到现有的粒子链表中,从而使得粒子数目增加一个,代码如下:
void AddParticle(Particle ex)
{
pParticle p;
p=new Particle;//new 出一个粒子来
p->pNext=NULL;//以下开始填充粒子的各个属性
p->pPrev=NULL;
p->b=ex.b;
p->g=ex.g;
p->r=ex.r;
p->fade=ex.fade;
p->life=ex.life;
p->size=ex.size;
p->xpos=ex.xpos;
p->ypos=ex.ypos;
p->zpos=ex.zpos;
p->xspeed=ex.xspeed;
p->yspeed=ex.yspeed;
p->zspeed=ex.zspeed;
p->AddCount=ex.AddCount;
p->AddSpeed=ex.AddSpeed;
p->bAddParts=ex.bAddParts;
p->bFire=ex.bFire;
p->nExpl=ex.nExpl;
if(!Particles)//当前的粒子链表为空,也就是现在这个是第一个粒子
{
Particles=p;
return;
}
Particles->pPrev=p;//插入粒子,可以看出这是前插,你也可以改用后插的
p->pNext=Particles;
Particles=p;
}
下面的函数用于从粒子链表中删除某个指定的粒子,这也是双链表经常涉及的操作:
void DeleteParticle(pParticle* p)
{
if(!(*p))//当前结点为空,不作任何处理
return;
if(!(*p)->pNext && !(*p)->pPrev)//孤立结点,直接删除
{
delete (*p);
*p=NULL;
return;
}
pParticle tmp;
if(!(*p)->pPrev)//首节点,需要记住下一个结点并使其成为首节点,然后再删除
{
tmp=(*p);
*p=(*p)->pNext;
Particles=*p;
(*p)->pPrev=NULL;
delete tmp;
return;
}
if(!(*p)->pNext)//末节点,使其前结点成为末节点,然后删除
{
(*p)->pPrev->pNext=NULL;
delete (*p);
*p=NULL;
return;
}
//一般情况,修改指针链,再删除
tmp=(*p);
(*p)->pPrev->pNext=(*p)->pNext;
(*p)->pNext->pPrev=(*p)->pPrev;
*p=(*p)->pNext;
delete tmp;
}
由于我们的链表是new出来的,由C++的知识就知道,我们最终要把它delete掉。否则就会发生内存泄露(也就是使用的内存没有及时的释放,造成了内存的浪费,C++没有垃圾回收机制,而JAVA是有的,这是JAVA的优点,但也同时是缺点,因为这样就损失了一定的性能)。下面再定义一个函数删除全部的粒子,这会发生在我们的程序结束时(需要释放掉整个链表),代码如下:
//删除所有的粒子
void DeleteAll(pParticle* Part)
{
while((*Part))
DeleteParticle(Part);
}
有了上面的函数,我们就可以将一个粒子初始化,并加入到粒子链表中,下面函数用于初始化一颗烟花粒子,其中代码如下:
void InitParticle(Particle& ep)
{
ep.b=float(rand()%100)/60.0f;//初始颜色随机
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=1.0f;//初始生命值满
ep.fade=0.005f+float(rand()%21)/10000.0f;//衰减速度随机
ep.size=1;//大小
ep.xpos=30.0f-float(rand()%601)/10.0f;//x、z的位置在屏幕范围内,y值固定在屏幕底部
ep.ypos=-24.0f;
ep.zpos=20.0f-float(rand()%401)/10.0f;
if(!int(ep.xpos))//初始化x方向速度,保证屏幕两侧的粒子都向中间运动
{
ep.xspeed=0.0f;
}
else
{
if(ep.xpos<0)
{
ep.xspeed=(rand()%int(-ep.xpos))/1500.0f;
}
else
{
ep.xspeed=-(rand()%int(-ep.xpos))/1500.0f;
}
}
ep.yspeed=0.04f+float(rand()%11)/1000.0f;//y方向的速度为正值,这样粒子才能朝上运动
if(!int(ep.zpos))//z方向的速度和x类似
{
ep.zspeed=0.0f;
}
else
{
if(ep.zpos<0)
{
ep.zspeed=(rand()%int(-ep.zpos))/1500.0f;
}
else
{
ep.zspeed=-(rand()%int(ep.zpos))/1500.0f;
}
}
ep.bFire=1;//烟花粒子标志
ep.nExpl=1+rand()%6;//粒子效果随机,从1到6
ep.bAddParts=1;//初始烟花粒子有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.2f;
nOfFires++;//当前粒子数加1
AddParticle(ep);//加入粒子链表
}
有了这些函数,我们只要动态的更新粒子链表,并对粒子链表中的每一个粒子更新其属性,就能够处理的动画效果。
渲染时粒子的重要属性必须改变才能产生复杂的动画效果,首先我们增加几个全局变量:
UINT Tick1=0, Tick2=0; //由CPU的时钟来控制速度,防止在有些机器上粒子速度太快,效果不好
float DTick;
GLfloat grav=0.00003f;
我们编写了如下函数,这个函数首先判断一下目前有多少个烟花粒子,如果小于最大烟花数,那么就初始化产生一个烟花粒子。然后修改粒子链表中的每个粒子的生命值,再判断生命值是否接近于0:
(1)如果是,分为两种情况:如果该粒子是烟花粒子,那么启动爆炸效果(否则肯定是尾巴粒子或者爆炸产生的粒子),不管是否烟花粒子,该粒子都将消亡,因此我们要删除它。注意,此时我们的效果都被注释掉了,因为我们还没有实现这些效果。等实现后再将这些注释去掉。
(2)如果否,即此时粒子还没有消亡,这时可能是因为烟花粒子在上升过程中,或爆炸后的粒子还在展示。因此按正常的情况修改位置、速度值。如果该粒子有尾巴,我们就增加尾巴粒子。
代码如下:
void ProcessParticles()
{
Tick1 = Tick2;
Tick2 = GetTickCount();
DTick = float(Tick2 - Tick1);
DTick*=0.5f;
Particle ep;
if(nOfFires
{
InitParticle(ep);//初始化一个新的烟花粒子并加入到粒子链表中
}
pParticle par;
par=Particles;
while (par)
{
par->life-=par->fade*(float(DTick)*0.1f);//烟花的生命值衰减
if(par->life<=0.05f)//如果生命值很小了,就让其爆炸
{
if(par->nExpl)//如果是爆炸,产生爆炸效果
{
switch(par->nExpl)
{
case 1:
Explosion1(par);//爆炸效果1
BindSound(0);
break;
case 2:
Explosion2(par);//爆炸效果2
BindSound(0);
break;
case 3:
Explosion3(par);//爆炸效果3
BindSound(0);
break;
case 4:
Explosion4(par);//爆炸效果4
BindSound(0);
break;
case 5:
Explosion5(par);//爆炸效果5
BindSound(0);
break;
case 6:
Explosion6(par);//爆炸效果6
BindSound(0);
break;
case 7:
Explosion7(par);//爆炸效果7
BindSound(0);
break;
default:
break;
}
}
if(par->bFire)//如果粒子爆炸了,要减掉一个烟花数
nOfFires--;
DeleteParticle(&par);//将生命值不够的粒子从当前链表中删除
}
else//粒子生命值没有耗尽,还在运动过程中,比如烟花粒子上升,或者粒子爆炸效果正在展示中
//此时我们要更新粒子属性
{
par->xpos+=par->xspeed*DTick;//根据速度值修改粒子位置(xpos,ypos,zpos)
par->ypos+=par->yspeed*DTick;
par->zpos+=par->zspeed*DTick;
par->yspeed-=grav*DTick;//根据重力加速度修改y方向的速度值,注意垂直方向为y轴
if(par->bAddParts)//判断粒子是否有尾巴
{
par->AddCount+=0.01f*DTick;//AddCount变化越慢,尾巴粒子越小
if(par->AddCount>par->AddSpeed)//AddSpeed越大,尾巴粒子越小
{
par->AddCount=0;//下面的代码用于加一个尾巴粒子...
ep.b=par->b;//颜色和烟花粒子一样
ep.g=par->g;
ep.r=par->r;
ep.life=par->life*0.9f;//初始寿命短一些
ep.fade=par->fade*7.0f;//衰减快一些
ep.size=0.6f;//粒子尺寸小一些
ep.xpos=par->xpos;//初始位置相同
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.0f;//
ep.yspeed=0.0f;
ep.zspeed=0.0f;
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=0;//尾巴粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
par=par->pNext;//更新下一个粒子
}
}
}
在制作粒子爆炸效果时,速度的方向起了关键性的作用,因为大家都知道,在物理力运动学中,运动距离和经过的时间成正比,即关系式为:△S=V△T,其中的V为即时速度,当然我们这里的速度是三维的,即在X、Y、Z方向都有分量。因此我们只要在一颗烟花粒子上产生大量的爆炸粒子(一般选在1000以内就可以了,太多必然影响速度的),并使各个粒子的速度方向不同,每个粒子都遵从运动学原理,即可让每个粒子朝不同方向运动,产生爆炸效果。
爆炸效果1:看下面的代码:
void Explosion1(Particle* par)
{
Particle ep;
for(int i=0;i<100;i++)
{
ep.b=float(rand()%100)/60.0f;//以下初始化粒子的各个属性
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=1.0f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.02f-float(rand()%41)/1000.0f;//各个粒子的速度是随机的,因此运动方向随机
ep.yspeed=0.02f-float(rand()%41)/1000.0f;//你可以改变这些值,观察运动情况
ep.zspeed=0.02f-float(rand()%41)/1000.0f;
ep.bFire=0;//这是爆炸粒子,不是烟花粒子,因此标志为0
ep.nExpl=0;//爆炸粒子不再产生爆炸效果了,因此标志为0
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
爆炸效果2:想法和上面类似,只是产生了更多的粒子,实时上,你可以自己传递一个粒子数num参数给Explosion1函数,在调用的时候随机化粒子数量,这个步骤留给同学们自己完成了。看下面的代码:
void Explosion2(Particle* par)
{
Particle ep;
for(int i=0;i<1000;i++)
{
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=1.0f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.02f-float(rand()%41)/1000.0f;
ep.yspeed=0.02f-float(rand()%41)/1000.0f;
ep.zspeed=0.02f-float(rand()%41)/1000.0f;
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
爆炸效果3:此效果的速度不再朝任意方向运动,而是呈圆周分布,因此要速度方向较均匀的在水平圆(即XZ平面内)平均的分布,当然我们为了粒子的多样化,只让爆炸粒子在圆周上随机分布,你可以试着修改,让爆炸粒子严格成圆周分布。而且我们为每个爆炸粒子增加尾巴,你只要修改bAddParts标志为1以及AddCount和AddSpeed的值即可(这两个值你可以修改它试试),在我们处理粒子的时候(ProcessParticles函数),会增加粒子尾巴的。由于此效果每个爆炸粒子都有尾巴粒子,因此爆炸粒子的数量不应该太大,我们将它设置为30,另外这个效果里,我们设置的生命值较大一些,因为有尾巴的粒子消失的慢一些。这里需要定义一个PIAsp常量,它是π/180。同时要包含数学库头文件math.h,代码如下:
void Explosion3(Particle* par)
{
Particle ep;
float PIAsp=3.1415926/180;
for(int i=0;i<30;i++)
{
float angle=float(rand()%360)*PIAsp;//产生一个0到360度范围的角度,这里是弧度值,因为cos和sin需要弧度参数
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=1.5f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=(float)sin(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.yspeed=0.01f+float(rand()%11)/1000.0f;//y方向随机,这是为正值,你可以试着修改一下让它正负都有可能
ep.zspeed=(float)cos(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=1;//该爆炸产生的粒子有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.2f;
AddParticle(ep);
}
}
爆炸效果4:该效果和爆炸效果3类似,只是我们让爆炸粒子的颜色多样化,展现出五彩缤纷的效果,代码如下:
void Explosion4(Particle* par)
{
Particle ep;
float PIAsp=3.1415926/180;
for(int i=0;i<30;i++)
{
float angle=float(rand()%360)*PIAsp;//产生一个0到360度范围的角度,这里是弧度值,因为cos和sin需要弧度参数
ep.b=float(rand()%100)/60.0f;
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=1.5f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=(float)sin(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.yspeed=0.01f+float(rand()%11)/1000.0f;//y方向随机,这是为正值,你可以试着修改一下让它正负都有可能
ep.zspeed=(float)cos(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=1;//该爆炸产生的粒子有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.2f;
AddParticle(ep);
}
}
爆炸效果5:该效果和效果1类似,但它用来展示那种存在时间较短的烟花效果,因此我们的初始生命值较小,而且速度值相比于效果1要小,这样粒子在生命期内运行的距离也小,因此产生了稍纵即逝的效果。要注意,我们这里对每个爆炸粒子的nExpl属性设置为7,这说明了每个爆炸粒子后面还有后续的爆炸效果哦,这要看爆炸效果7是怎样实现的,我们稍后再看,看下面的代码:
void Explosion5(Particle* par)
{
Particle ep;
for(int i=0;i<30;i++)
{
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=0.8f;//生命值较小
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.01f-float(rand()%21)/1000.0f;//速度较效果1减半
ep.yspeed=0.01f-float(rand()%21)/1000.0f;
ep.zspeed=0.01f-float(rand()%21)/1000.0f;
ep.bFire=0;
ep.nExpl=7;//有后续爆炸效果
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
爆炸效果6:和爆炸效果5类似,这里只是增加对颜色的随机化,看下面代码:
void Explosion6(Particle* par)
{
Particle ep;
for(int i=0;i<100;i++)
{
ep.b=float(rand()%100)/60.0f;//颜色随机
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=0.8f;//生命值较小
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.01f-float(rand()%21)/1000.0f;//速度较效果1减半
ep.yspeed=0.01f-float(rand()%21)/1000.0f;
ep.zspeed=0.01f-float(rand()%21)/1000.0f;
ep.bFire=0;
ep.nExpl=7;//有后续爆炸效果
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
爆炸效果7:该效果主要用在爆炸效果5和爆炸效果6的后续爆炸中,它的生命值更短,粒子数量很少(因为和其他效果合起来,总共的粒子数也不少),使其产生零星点缀的效果。而且由于我们在初始化粒子(InitParticles函数)的时候随机数取在1到6之间。因此不产生独立效果,你可以修改试试,效果并不好。代码如下:
void Explosion7(Particle* par)
{
Particle ep;
for(int i=0;i<10;i++)//粒子数较少
{
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=0.5f;//生命值很短
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.6f;//粒子较小
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.02f-float(rand()%41)/1000.0f;
ep.yspeed=0.02f-float(rand()%41)/1000.0f;
ep.zspeed=0.02f-float(rand()%41)/1000.0f;
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
#define GLUT_DISABLE_ATEXIT_HACK
#include "windows.h" // Windows的头文件,因为这是Windows OS上的OpenGL
#include "time.h"
#include "stdlib.h"
#include
#include "gl\gl.h" // OpenGL32库的头文件,这是OpenGL核心
#include "gl\glu.h" // GLu32库的头文件,这是OpenGL的实用库
#include "gl\glaux.h" // GLaux库的头文件,用于管理OpenGL窗口
#define FONT_SIZE 64
#define TEXTURE_SIZE FONT_SIZE
#define MAX_CHAR 128
#include "math.h"
//
HGLRC hRC=NULL; // 永久着色描述表
HDC hDC=NULL; // GDI设备描述表
HWND hWnd=NULL; // 保存我们的窗口句柄
HINSTANCE hInstance; // 保存程序的实例
BOOL keys[256]; // 用于键盘按键的数组,用于保存键盘的状态
BOOL active=TRUE; // 窗口的活动标志,缺省为TRUE
BOOL fullscreen=TRUE; // 全屏标志缺省设定成全屏模式,提示用户选择窗口模式
typedef struct tag_PARTICLE
{
GLfloat xpos;//(xpos,ypos,zpos)为粒子的位置
GLfloat ypos;
GLfloat zpos;
GLfloat xspeed;//(xspeed,yspeed,zspeed)为粒子的速度
GLfloat yspeed;
GLfloat zspeed;
GLfloat r;//(r,g,b)为粒子的颜色
GLfloat g;
GLfloat b;
GLfloat life;//粒子的寿命
GLfloat fade;//粒子的衰减速度
GLfloat size;//粒子大小,可以接合粒子位置,计算边缘值
GLbyte bFire;//是否为烟花粒子,即代表烟花的粒子,这种粒子小于最大烟花数
GLbyte nExpl;//对应的爆炸效果,我们根据该值的不同来绘制多种效果
GLbyte bAddParts;//该粒子是否含有尾巴粒子
GLfloat AddSpeed;//尾巴粒子的增加速度
GLfloat AddCount;//尾巴粒子的增加量
tag_PARTICLE* pNext;//下一粒子结点
tag_PARTICLE* pPrev;//上一粒子结点
} Particle,*pParticle;
/
pParticle Particles=NULL; //粒子链表
#define MAX_PARTICLES 1000 //最大粒子数
#define MAX_FIRES 5 //最大烟花数
UINT nOfFires=0; //当前的烟花数,这个在我们程序中会统计的,粒子爆炸了烟花书要减掉一个,这样便于我们再放一颗烟花,从而保证还有后续的画面
//粒子处理函数
//在粒子链表Particles中增加一个粒子
void AddParticle(Particle ex)
{
pParticle p;
p=new Particle;//new 出一个粒子来
p->pNext=NULL;//以下开始填充粒子的各个属性
p->pPrev=NULL;
p->b=ex.b;
p->g=ex.g;
p->r=ex.r;
p->fade=ex.fade;
p->life=ex.life;
p->size=ex.size;
p->xpos=ex.xpos;
p->ypos=ex.ypos;
p->zpos=ex.zpos;
p->xspeed=ex.xspeed;
p->yspeed=ex.yspeed;
p->zspeed=ex.zspeed;
p->AddCount=ex.AddCount;
p->AddSpeed=ex.AddSpeed;
p->bAddParts=ex.bAddParts;
p->bFire=ex.bFire;
p->nExpl=ex.nExpl;
if(!Particles)//当前的粒子链表为空,也就是现在这个是第一个粒子
{
Particles=p;
return;
}
Particles->pPrev=p;//插入粒子,可以看出这是前插,你也可以改用后插的
p->pNext=Particles;
Particles=p;
}
//删除一个指定的粒子
void DeleteParticle(pParticle* p)
{
if(!(*p))//当前结点为空,不作任何处理
return;
if(!(*p)->pNext && !(*p)->pPrev)//孤立结点,直接删除
{
delete (*p);
*p=NULL;
return;
}
pParticle tmp;
if(!(*p)->pPrev)//首节点,需要记住下一个结点并使其成为首节点,然后再删除
{
tmp=(*p);
*p=(*p)->pNext;
Particles=*p;
(*p)->pPrev=NULL;
delete tmp;
return;
}
if(!(*p)->pNext)//末节点,使其前结点成为末节点,然后删除
{
(*p)->pPrev->pNext=NULL;
delete (*p);
*p=NULL;
return;
}
//一般情况,修改指针链,再删除
tmp=(*p);
(*p)->pPrev->pNext=(*p)->pNext;
(*p)->pNext->pPrev=(*p)->pPrev;
*p=(*p)->pNext;
delete tmp;
}
//删除所有的粒子
void DeleteAll(pParticle* Part)
{
while((*Part))
DeleteParticle(Part);
}
void InitParticle(Particle& ep)
{
ep.b=float(rand()%100)/60.0f;//初始颜色随机
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=1.0f;//初始生命值满
ep.fade=0.005f+float(rand()%21)/10000.0f;//衰减速度随机
ep.size=1;//大小
ep.xpos=30.0f-float(rand()%601)/10.0f;//x、z的位置在屏幕范围内,y值固定在屏幕底部
ep.ypos=-24.0f;
ep.zpos=20.0f-float(rand()%401)/10.0f;
if(!int(ep.xpos))//初始化x方向速度,保证屏幕两侧的粒子都向中间运动
{
ep.xspeed=0.0f;
}
else
{
if(ep.xpos<0)
{
ep.xspeed=(rand()%int(-ep.xpos))/1500.0f;
}
else
{
ep.xspeed=-(rand()%int(-ep.xpos))/1500.0f;
}
}
ep.yspeed=0.04f+float(rand()%11)/1000.0f;//y方向的速度为正值,这样粒子才能朝上运动
if(!int(ep.zpos))//z方向的速度和x类似
{
ep.zspeed=0.0f;
}
else
{
if(ep.zpos<0)
{
ep.zspeed=(rand()%int(-ep.zpos))/1500.0f;
}
else
{
ep.zspeed=-(rand()%int(ep.zpos))/1500.0f;
}
}
ep.bFire=1;//烟花粒子标志
ep.nExpl=1+rand()%6;//粒子效果随机,从1到6
ep.bAddParts=1;//初始烟花粒子有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.2f;
nOfFires++;//当前粒子数加1
AddParticle(ep);//加入粒子链表
}
UINT Tick1=0, Tick2=0; //由CPU的时钟来控制速度,防止在有些机器上粒子速度太快,效果不好
float DTick;
GLfloat grav=0.00003f; //引力大小为常量,但我们的烟花可不是物理真实的
void Explosion1(Particle* par)
{
Particle ep;
for(int i=0;i<100;i++)
{
ep.b=float(rand()%100)/60.0f;//以下初始化粒子的各个属性
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=1.0f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.02f-float(rand()%41)/1000.0f;//各个粒子的速度是随机的,因此运动方向随机
ep.yspeed=0.02f-float(rand()%41)/1000.0f;//你可以改变这些值,观察运动情况
ep.zspeed=0.02f-float(rand()%41)/1000.0f;
ep.bFire=0;//这是爆炸粒子,不是烟花粒子,因此标志为0
ep.nExpl=0;//爆炸粒子不再产生爆炸效果了,因此标志为0
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
void Explosion2(Particle* par)
{
Particle ep;
for(int i=0;i<1000;i++)
{
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=1.0f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.02f-float(rand()%41)/1000.0f;
ep.yspeed=0.02f-float(rand()%41)/1000.0f;
ep.zspeed=0.02f-float(rand()%41)/1000.0f;
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
void Explosion3(Particle* par)
{
Particle ep;
float PIAsp=3.1415926/180;
for(int i=0;i<30;i++)
{
float angle=float(rand()%360)*PIAsp;//产生一个0到360度范围的角度,这里是弧度值,因为cos和sin需要弧度参数
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=1.5f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=(float)sin(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.yspeed=0.01f+float(rand()%11)/1000.0f;//y方向随机,这是为正值,你可以试着修改一下让它正负都有可能
ep.zspeed=(float)cos(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=1;//该爆炸产生的粒子有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.2f;
AddParticle(ep);
}
}
void Explosion4(Particle* par)
{
Particle ep;
float PIAsp=3.1415926/180;
for(int i=0;i<30;i++)
{
float angle=float(rand()%360)*PIAsp;//产生一个0到360度范围的角度,这里是弧度值,因为cos和sin需要弧度参数
ep.b=float(rand()%100)/60.0f;
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=1.5f;
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=(float)sin(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.yspeed=0.01f+float(rand()%11)/1000.0f;//y方向随机,这是为正值,你可以试着修改一下让它正负都有可能
ep.zspeed=(float)cos(angle)*0.01f;//由极坐标公式,角度余弦为X方向
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=1;//该爆炸产生的粒子有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.2f;
AddParticle(ep);
}
}
void Explosion5(Particle* par)
{
Particle ep;
for(int i=0;i<30;i++)
{
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=0.8f;//生命值较小
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.01f-float(rand()%21)/1000.0f;//速度较效果1减半
ep.yspeed=0.01f-float(rand()%21)/1000.0f;
ep.zspeed=0.01f-float(rand()%21)/1000.0f;
ep.bFire=0;
ep.nExpl=7;//有后续爆炸效果
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
void Explosion6(Particle* par)
{
Particle ep;
for(int i=0;i<100;i++)
{
ep.b=float(rand()%100)/60.0f;//颜色随机
ep.g=float(rand()%100)/60.0f;
ep.r=float(rand()%100)/60.0f;
ep.life=0.8f;//生命值较小
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.8f;
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.01f-float(rand()%21)/1000.0f;//速度较效果1减半
ep.yspeed=0.01f-float(rand()%21)/1000.0f;
ep.zspeed=0.01f-float(rand()%21)/1000.0f;
ep.bFire=0;
ep.nExpl=7;//有后续爆炸效果
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
void Explosion7(Particle* par)
{
Particle ep;
for(int i=0;i<10;i++)//粒子数较少
{
ep.b=par->b;
ep.g=par->g;
ep.r=par->r;
ep.life=0.5f;//生命值很短
ep.fade=0.01f+float(rand()%31)/10000.0f;
ep.size=0.6f;//粒子较小
ep.xpos=par->xpos;
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.02f-float(rand()%41)/1000.0f;
ep.yspeed=0.02f-float(rand()%41)/1000.0f;
ep.zspeed=0.02f-float(rand()%41)/1000.0f;
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=0;//该爆炸产生的粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
void ProcessParticles()
{
Tick1 = Tick2;
Tick2 = GetTickCount();
DTick = float(Tick2 - Tick1);
DTick*=0.5f;
Particle ep;
if(nOfFireslife-=par->fade*(float(DTick)*0.1f);//烟花的生命值衰减
if(par->life<=0.05f)//如果生命值很小了,就让其爆炸
{
if(par->nExpl)//如果是爆炸,产生爆炸效果
{
switch(par->nExpl)
{
case 1:
Explosion1(par);//爆炸效果1
break;
case 2:
Explosion2(par);//爆炸效果2
break;
case 3:
Explosion3(par);//爆炸效果3
break;
case 4:
Explosion4(par);//爆炸效果4
break;
case 5:
Explosion5(par);//爆炸效果5
break;
case 6:
Explosion6(par);//爆炸效果6
break;
case 7:
Explosion7(par);//爆炸效果7
break;
default:
break;
}
}
if(par->bFire)//如果粒子爆炸了,要减掉一个烟花数
nOfFires--;
DeleteParticle(&par);//将生命值不够的粒子从当前链表中删除
}
else//粒子生命值没有耗尽,还在运动过程中,比如烟花粒子上升,或者粒子爆炸效果正在展示中
//此时我们要更新粒子属性
{
par->xpos+=par->xspeed*DTick;//根据速度值修改粒子位置(xpos,ypos,zpos)
par->ypos+=par->yspeed*DTick;
par->zpos+=par->zspeed*DTick;
par->yspeed-=grav*DTick;//根据重力加速度修改y方向的速度值,注意垂直方向为y轴
if(par->bAddParts)//判断粒子是否有尾巴
{
par->AddCount+=0.01f*DTick;//AddCount变化越慢,尾巴粒子越小
if(par->AddCount>par->AddSpeed)//AddSpeed越大,尾巴粒子越小
{
par->AddCount=0;//下面的代码用于加一个尾巴粒子...
ep.b=par->b;//颜色和烟花粒子一样
ep.g=par->g;
ep.r=par->r;
ep.life=par->life*0.9f;//初始寿命短一些
ep.fade=par->fade*7.0f;//衰减快一些
ep.size=0.6f;//粒子尺寸小一些
ep.xpos=par->xpos;//初始位置相同
ep.ypos=par->ypos;
ep.zpos=par->zpos;
ep.xspeed=0.0f;//
ep.yspeed=0.0f;
ep.zspeed=0.0f;
ep.bFire=0;
ep.nExpl=0;
ep.bAddParts=0;//尾巴粒子没有尾巴
ep.AddCount=0.0f;
ep.AddSpeed=0.0f;
AddParticle(ep);
}
}
par=par->pNext;//更新下一个粒子
}
}
}
GLuint textureID; //粒子纹理
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
AUX_RGBImageRec* TextTexture[5]; // create storage space for the texture
memset(TextTexture, 0, 5*sizeof(void*)); // set the pointer to NULL
if (!(TextTexture[0] = auxDIBImageLoad("res/particle.bmp")) ||
!(TextTexture[1] = auxDIBImageLoad("res/Text.bmp"))||
!(TextTexture[2] = auxDIBImageLoad("res/cloud.bmp"))||
!(TextTexture[3] = auxDIBImageLoad("res/star.bmp"))||
!(TextTexture[4] = auxDIBImageLoad("res/dian.bmp"))||
!(TextTexture[5] = auxDIBImageLoad("res/pearl.bmp"))) {
return FALSE;
}
glGenTextures(5, textureID);// provide unused texture names
for (int i = 0; i <5; i++) {
glBindTexture(GL_TEXTURE_2D, textureID[i]); // create a new texture object and assigned name
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D, 0, 3, TextTexture[i]->sizeX, // define the texture
TextTexture[i]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextTexture[i]->data);
if (TextTexture[i]) {
if (TextTexture[i]->data) {
free(TextTexture[i]->data);
}
free(TextTexture[i]);
}
}
return TRUE;
}
void UnloadTextures()
{
glDeleteTextures (5, &textureID);
for (int i = 0; i <5; i++) {
if (TextTexture[i])
{
if (TextTexture[i]->data)
{
free(TextTexture[i]->data);
}
free(TextTexture[i]);
}
}
}
void DrawParticles()
{
glBindTexture(GL_TEXTURE_2D, textureID); //绑定到纹理
glTranslatef(0,0,-60);
pParticle par;
par=Particles;
while (par)
{
glColor4f(par->r,par->g,par->b,par->life);
glBegin(GL_TRIANGLE_STRIP); //以三角形条带方式绘制,下面4个顶点有两个三角形
glTexCoord2d(1,1);
glVertex3f(par->xpos+par->size,par->ypos+par->size,par->zpos);
glTexCoord2d(0,1);
glVertex3f(par->xpos-par->size,par->ypos+par->size,par->zpos);
glTexCoord2d(1,0);
glVertex3f(par->xpos+par->size,par->ypos-par->size,par->zpos);
glTexCoord2d(0,0);
glVertex3f(par->xpos-par->size,par->ypos-par->size,par->zpos);
glEnd();
par=par->pNext;
}
}
///
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)// 重新调整OpenGL 窗口,初始化时窗口大小也会变化一次
{
if (height==0) //防止被0除,看后面的(GLfloat)width/(GLfloat)height
{
height=1; // 让高度为1
}
glViewport(0,0,width,height); // 重置视口,完成程序后试着改变参数
glMatrixMode(GL_PROJECTION); // 选择投影矩阵,下面的改变都针对投影矩阵
glLoadIdentity(); // 将投影矩阵重置为单位矩阵
// 计算窗口的宽高比,用于设置透视投影
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // 选择模型矩阵,后面的操作都针对模型视图矩阵
glLoadIdentity(); //将模型视图矩阵重置为单位矩阵
}
int InitGL(GLvoid) // 所有OpenGL的初始设置
{
glShadeModel(GL_SMOOTH); // 启动平滑着色,如果设置为GL_FLAT,则不平滑过渡
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 清除颜色缓存的颜色为黑色,也就是背景色
glClearDepth(1.0f); //使用1.0来清除深度缓存中的值
glEnable(GL_DEPTH_TEST); // 启动深度检测
glDepthFunc(GL_LEQUAL); // 深度检测的类型
srand( (unsigned)time( NULL ) );
Tick1 = GetTickCount ( );
Tick2 = GetTickCount ( );
if(!LoadGLTextures())
return FALSE;
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND); //启动混合
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 混合函数
return TRUE;
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓存和深度缓存,也就是使用在InitGL中设置的背景色和深度值来清空,因此就变成黑色了
glLoadIdentity(); // 重置模型视图矩阵,还记得ReSizeGLScene里的设置吗?
ProcessParticles();
DrawParticles();
return TRUE;
}
//
LRESULT CALLBACK WndProc( HWND hWnd, //窗口句柄
UINT uMsg, //窗口的消息
WPARAM wParam, //窗口消息的附带信息
LPARAM lParam) //窗口消息的附带信息
{
switch (uMsg) // 检测消息类型
{
case WM_ACTIVATE: // 窗口是否激活
{
if (!HIWORD(wParam)) // 最小化状态
{
active=TRUE; // 活动的
}
else
{
active=FALSE; // 非活动的
}
return 0;
}
case WM_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));//还记得这个函数吗?
return 0;
}
}
// 其它消息由默认消息处理函数处理
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
GLvoid KillGLWindow(GLvoid) // Kill The Window
{
if (fullscreen) // 在全屏模式下?
{
ChangeDisplaySettings(NULL,0); // 切换到桌面模式
ShowCursor(TRUE); // 显示鼠标光标
}
if (hRC) // Rendering Context不为空?
{
if (!wglMakeCurrent(NULL,NULL)) // 不在使用DC 和 RC?
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) //能释放RC?
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // 设置RC为NULL
}
if (hDC && !ReleaseDC(hWnd,hDC)) //能释放DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // 设置DC 为 NULL
}
if (hWnd && !DestroyWindow(hWnd)) // 能摧毁窗口?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // 设置hWnd为NULL
}
if (!UnregisterClass("OpenGL",hInstance)) //卸载窗口类
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // 设置hInstance 为NULL
}
UnloadTextures();
DeleteAll(&Particles);
}
BOOL CreateGLWindow(char* title, int width, int height, int bits, BOOL fullscreenflag)
{
GLuint PixelFormat; // 像素格式
WNDCLASS wc; // 窗口类结构
DWORD dwExStyle; // 窗口扩展风格
DWORD dwStyle; // 窗口风格
RECT WindowRect; // 窗口对应的矩形
WindowRect.left=(long)0;
WindowRect.right=(long)width;
WindowRect.top=(long)0;
WindowRect.bottom=(long)height;
fullscreen=fullscreenflag; //是否全屏模式记录下来
hInstance = GetModuleHandle(NULL); // 窗口的Instance
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc; // 还记得这个函数吗?
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 默认图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标样式
wc.hbrBackground = NULL; // OpenGL绘图,不需要背景的
wc.lpszMenuName = NULL; // 目前没有菜单,要菜单可以在这里指定
wc.lpszClassName = "OpenGL";// 窗口类名字,卸载类的时候要用到的
if (!RegisterClass(&wc)) //注册类
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (fullscreen) // 进入全屏模式?
{
DEVMODE dmScreenSettings; // 设备模式
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // 清空
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // 大小
dmScreenSettings.dmPelsWidth = width; // 宽度
dmScreenSettings.dmPelsHeight = height; // 高度
dmScreenSettings.dmBitsPerPel = bits; // 每个像素的位数
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
// 设置窗口模式,注意:CDS_FULLSCREEN消除开始菜单
if(ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
// 模式设置失败时有两个选择
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=FALSE; // 选择是,进入窗口模式
}
else
{
// 否则,程序关闭
MessageBox(NULL,"Program Will Now Close.","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); //大小
//真正创建窗口
if (!(hWnd=CreateWindowEx( dwExStyle, // 扩展风格
"OpenGL", // 类名
title, // 窗口标题
dwStyle | // 窗口风格
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN,
0, 0, // 窗口位置
WindowRect.right-WindowRect.left, // 宽度
WindowRect.bottom-WindowRect.top, // 高度
NULL, // 父窗口为空
NULL, // 菜单
hInstance, // Instance
NULL))) //
{
KillGLWindow();
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
static PIXELFORMATDESCRIPTOR pfd= // 像素格式描述
{ sizeof(PIXELFORMATDESCRIPTOR), // Size
1, // 版本号
PFD_DRAW_TO_WINDOW |// 窗口必须要
PFD_SUPPORT_OPENGL | // 窗口支持OpenGL
PFD_DOUBLEBUFFER, // 双缓存
PFD_TYPE_RGBA, // RGBA格式
bits, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
0, // No Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
16, // 16位Z-Buffer (深度缓存,还记得吗)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
if (!(hDC=GetDC(hWnd))) // 获取DC?
{
KillGLWindow();
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) //查询像素格式是否支持?
{
KillGLWindow();
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // 像素格式能设置成功吗?
{
KillGLWindow();
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if (!(hRC=wglCreateContext(hDC))) // 能获取RC?
{
KillGLWindow();
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
if(!wglMakeCurrent(hDC,hRC)) // 能激活RC?
{
KillGLWindow();
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
ShowWindow(hWnd,SW_SHOW); // 显示窗口,这时候窗口才能看到
SetForegroundWindow(hWnd); // 优先级稍微高点
SetFocus(hWnd); // 焦点定位在窗口上
ReSizeGLScene(width, height); // 窗口变化一次,调整透视投影
if (!InitGL()) // 这里是OpenGL的初始化,一定是在窗口创建之后
{
KillGLWindow();
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE; // Success
}
//
int WINAPI WinMain( HINSTANCE hInstance, // 句柄
HINSTANCE hPrevInstance, // 先前句柄
LPSTR lpCmdLine, // 命令行参数
int nCmdShow) // 窗口状态
{
MSG msg; // 窗口消息
BOOL done=FALSE; // 标志变量,用于程序循环控制
// 由用户决定是否进入全屏模式
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // 窗口模式
}
// 创建我们的窗口
if (!CreateGLWindow("OpenGL烟花粒子",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;
}
else // 否则处理窗口消息
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else // 没有消息,此时我们不断的绘制图形
{
if (active) // 程序激活?
{
if (keys[VK_ESCAPE]) // ESC按下?
{
done=TRUE;
}
else // 更新屏幕,不断的绘制
{
DrawGLScene(); //绘制到后台缓存
SwapBuffers(hDC); //双缓存机制,交换
}
}
if (keys[VK_F1]) // F1按下?
{
keys[VK_F1]=FALSE; // 设置为FALSE
KillGLWindow();// 先摧毁窗口
fullscreen=!fullscreen; // 切换模式
// 创新创建窗口,此时的模式已经在窗口和全屏之间切换
if (!CreateGLWindow("OpenGL烟花粒子",640,480,16,fullscreen))
{
return 0;
}
}
}
}
srand( (unsigned)time( NULL ) );
Tick1 = GetTickCount ( );
Tick2 = GetTickCount ( );
if(!LoadGLTextures())
return FALSE;
//关闭程序
KillGLWindow();// 摧毁窗口
return (msg.wParam); //退出程序
}