分享
 
 
 

翻译的NEHE19课教程

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

第十九课

欢迎来到第十九课.你已经学习了很多知识,并且现在想自己来实践.我将在这讲解一个新命令... 三角形带(我的理解就是画很多三角形来组合成我们要的形状),它非常容易使用,当画很多三角形的时候能加快你程序的运行速度.在本课中,我将会教你该如何做一个半复杂的微粒程序.一旦您了解微粒程序的原理后,在创建例如:火,烟,喷泉等效果将是很轻松的事情.我必须警告你!直到今天我从未写一个真正的粒子程序.我想写一个"出名"的复杂的粒子程序.我尝试过,但在我了解我不能控制所有点变疯狂之后我放弃了!!!你也许不相信我要告诉你的,但这个课程从头到尾都是我自己的想法.开始我没有一点想法,并且没有任何技术数据放在我的面前.我开始考虑粒子,突然我的脑袋装满想法(脑袋开启了??):给予每个粒子生命,任意变化颜色,速度,重力影响等等.来适应环境的变化,把每个粒子看成单一的从这个点运动到另一个点的颗粒.(这句话没有理解,不好意思.我自己还晕着那)很快我完成了这个项目.我看看时钟然后有个想法突然出现.四个小时过去了!我偶尔记得已经停止喝咖啡,眨眼,但是4个小时...?尽管这个程序我觉得很棒,并象我想象的那么严密的运行,但它不可能是最好的粒子引擎,这个我不关心,只要他运行好就可以.并且我能把它运行在我的项目中.如果你是那种想了解透彻的人,那么你要花费很多时间在网络上查找资料并弄明白它.在程序中有很多小的代码会看起来很模糊:)本课教程所用的部分代码来自于Lesson1.但有很多新的代码,因此我将重写一些发生代码变化的部分(使它更容易了解).

下面用到的代码来自于Lesson1,我将会增加5行新的代码在我们程序的前面.第一行"stdio.h"允许我们读文件中的数据.它和我们以前用在纹理映射当中是一样的.第二行定义了一些我们要在屏幕上显示的粒子的数目.告诉程序AX_PARTICLES在这里的数值为1000.第三条行将不断分离的彩色的粒子栓牢在一起,并设置为默认情况.sp和rp用来确定空格键和返回键是否有按住.

#include <windows.h> // Header File For Windows

#include <stdio.h> // Header File For Standard Input/Output ( ADD )

#include <gl\gl.h> // Header File For The OpenGL32 Library

#include <gl\glu.h> // Header File For The GLu32 Library

#include <gl\glaux.h> // Header File For The GLaux Library

#define MAX_PARTICLES 1000 // Number Of Particles To Create ( NEW )

HDC hDC=NULL; // Private GDI Device Context

HGLRC hRC=NULL; // Permanent Rendering Context

HWND hWnd=NULL; // Holds Our Window Handle

HINSTANCE hInstance; // Holds The Instance Of The Application

bool keys[256]; // Array Used For The Keyboard Routine

bool active=TRUE; // Window Active Flag Set To TRUE By Default

bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default

bool rainbow=true; // Rainbow Mode? ( ADD )

bool sp; // Spacebar Pressed? ( ADD )

bool rp; // Return Key Pressed? ( ADD )

下面四行是复杂的变量.变量slowdown控制粒子移动的快慢.数值愈高,移动越慢.数值越底,移动越快.如果数值降低,粒子将快速的移动!粒子的速度影响它们在荧屏中移动的距离.记住速度慢的粒子不会射很远的.变量xspeed和yspeed控制尾部的方向.xspeed将会增加粒子在x轴上速度.如果xspeed是正值粒子将会向右边移动多.如果xspeed负价值,粒子将会向左边移动多.那个值越高,就向那个方向移动比较多.yspeed工作相同的方法,但是在y轴上.因为有其它的因素影响粒子的运动,所以我要说"多".xspeed和yspeed有助于在我们想要的方向上移动粒子.最后是变量zoom,我们用该变量移入或移出我们的屏幕.在粒子引擎里,有时可看见更多的图象,而且当接近你时很酷

float slowdown=2.0f; // Slow Down Particles

float xspeed; // Base X Speed (To Allow Keyboard Direction Of Tail)

float yspeed; // Base Y Speed (To Allow Keyboard Direction Of Tail)

float zoom=-40.0f; // Used To Zoom Out

我们定义了一个复杂的循环变量叫做Loop.我们用这变量预先定义粒子并在屏幕中画粒子.col用来给予粒子不同的颜色.delay用来控制在彩虹模式中圆的颜色变化.最后,我们设定一个存储空间(粒子纹理).我用纹理而不用点的重要原因是,点的速度慢,而且挺麻烦的.其次纹理很酷:)你用一个正方形的粒子,一张你脸的小图片,一张星星的图片等等.很好控制!

GLuint loop; // Misc Loop Variable

GLuint col; // Current Color Selection

GLuint delay; // Rainbow Effect Delay

GLuint texture[1]; // Storage For Our Particle Texture

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

typedef struct // Create A Structure For Particle

{

bool active; // Active (Yes/No)

float life; // Particle Life

float fade; // Fade Speed

变量r,g和b用来表示粒子的红色强度,绿色强度和蓝色强度.当r的值变成1.0f时粒子将会很红,当三个变量全为1.0f时则粒子将变成白色.

float r; // 红色数值

float g; // 绿色数值

float b; // 蓝色数值

变量x.y和z控制粒子在屏幕上显示的位置.x表示粒子在x轴上的位置.y表示y轴上的位置.z表示粒子z轴上的位置

float x; // X Position

float y; // Y Position

float z; // Z Position

下面三个变量很重要.这三个变量控制粒子在每个轴上移动的快慢和方向.如果xi是负价粒子将会向左移动,正值将会向右移动.如果yi是负值粒子将会向下移动,正值将向上.最后,如果zi负值粒子将会向荧屏内部移动,正植将移向观察者.

float xi; // X Direction

float yi; // Y Direction

float zi; // Z Direction

最后,另外3个变量!每一个变量可被看成加速度.如果xg正值时,粒子将会被拉倒右边,负值将拉向左边.所以如果粒子向左移动(负的)而我们给它一个正的加速度,粒子速度将变慢.最后将向反方向移动(高中物理).yg拉下或拉上.zg拉进或拉出屏幕.

float xg; // X Gravity

float yg; // Y Gravity

float zg; // Z Gravity

结构的名字为particles.

}

particles; // Particles Structure

下面我们创建一个数组叫particle.数组存储MAX_PARTICLES个元素.也就是说我们创建1000(MAX_PARTICLES)个粒子,存储空间为每个粒子提供相应的信息

particles particle[MAX_PARTICLES]; // Particle Array (Room For Particle Info)

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

static GLfloat colors[12][3]= // Rainbow Of Colors

{

{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}

};

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc

纹理载入代码没有变.

AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image

{

FILE *File=NULL; // File Handle

if (!Filename) // Make Sure A Filename Was Given

{

return NULL; // If Not Return NULL

}

File=fopen(Filename,"r"); // Check To See If The File Exists

if (File) // Does The File Exist?

{

fclose(File); // Close The Handle

return auxDIBImageLoad(Filename); // Load The Bitmap And Return A Pointer

}

return NULL; // If Load Failed Return NULL

}

这段代码调用前面的代码载入位图,并将其转换成一个纹理.Status 变量跟踪纹理是否已载入并被创建了

int LoadGLTextures() // Load Bitmaps And Convert To Textures

{

int Status=FALSE; // Status Indicator

AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture

memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL

纹理通过装载代码来载入粒子位图并转化成线形的滤波纹理

if (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // Load Particle Texture

{

Status=TRUE; // Set The Status To TRUE

glGenTextures(1, &texture[0]); // Create One Textures

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, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB,GL_UNSIGNED_BYTE, TextureImage[0]->data);

}

if (TextureImage[0]) // If Texture Exists

{

if (TextureImage[0]->data) // If Texture Image Exists

{

free(TextureImage[0]->data); // Free The Texture Image Memory

}

free(TextureImage[0]); // Free The Image Structure

}

return Status; // Return The Status

}

我对这段代码的唯一改变就是加大了观看范围.我们可以看到200.0f unit个粒子(以前100.0f uint)

GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window

{

if (height==0) // Prevent A Divide By Zero By

{

height=1; // Making Height Equal One

}

glViewport(0, 0, width, height); // Reset The Current Viewport

glMatrixMode(GL_PROJECTION); // Select The Projection Matrix

glLoadIdentity(); // Reset The Projection Matrix

// Calculate The Aspect Ratio Of The Window

gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,200.0f); // ( 修改的 )

glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix

glLoadIdentity(); // Reset The Modelview Matrix

}

如果你用Lesson1的代码,用下面的代码替换以前的.我增加代码载入我们的纹理并绑定我们的粒子

int InitGL(GLvoid) // All Setup For OpenGL Goes Here

{

if (!LoadGLTextures()) // Jump To Texture Loading Routine

{

return FALSE; // If Texture Didn't Load Return FALSE

}

我们使用光滑的阴影,清除背景为黑色,关闭深度测试,绑定并映射纹理.启用映射位图后我们选择粒子纹理

glShadeModel(GL_SMOOTH); // Enables Smooth Shading

glClearColor(0.0f,0.0f,0.0f,0.0f); // Black Background

glClearDepth(1.0f); // Depth Buffer Setup

glDisable(GL_DEPTH_TEST); // Disables Depth Testing

glEnable(GL_BLEND); // Enable Blending

glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Type Of Blending To Perform

glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); // Really Nice Perspective Calculations

glHint(GL_POINT_SMOOTH_HINT,GL_NICEST); // Really Nice Point Smoothing

glEnable(GL_TEXTURE_2D); // Enable Texture Mapping

glBindTexture(GL_TEXTURE_2D,texture[0]); // Select Our Texture

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

for (loop=0;loop<MAX_PARTICLES;loop++) // Initials All The Textures

{

particle[loop].active=true; // Make All The Particles Active

particle[loop].life=1.0f; // Give All The Particles Full Life

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

particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // Random Fade Speed

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

particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0]; // Select Red Rainbow Color

particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1]; // Select Red Rainbow Color

particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2]; // Select Red Rainbow Color

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

particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // Random Speed On X Axis

particle[loop].yi=float((rand()%50)-25.0f)*10.0f; // Random Speed On Y Axis

particle[loop].zi=float((rand()%50)-25.0f)*10.0f; // Random Speed On Z Axis

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

particle[loop].xg=0.0f; // Set Horizontal Pull To Zero

particle[loop].yg=-0.8f; // Set Vertical Pull Downward

particle[loop].zg=0.0f; // Set Pull On Z Axis To Zero

}

return TRUE; // Initialization Went OK

}

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

int DrawGLScene(GLvoid) // Where We Do All The Drawing

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer

glLoadIdentity(); // Reset The ModelView Matrix

我们开始创建一个循环loop.这个环将会更新每一个粒子.

for (loop=0;loop<MAX_PARTICLES;loop++) // Loop Through All The Particles

{

首先我们做的事物是检查粒子是否活跃.如果不活跃,则不被更新.在这个程序中,它们始终活跃.但是在你自己的程序中,你可能想要使某粒子不活跃

if (particle[loop].active) // If The Particle Is Active

{

下面三个变量是我们确定x,y和z位置的暂时变量.注意:在z的位置上我们加上zoom以便我们的场景在以前的基础上再移入zoom个位置.particle[loop].x告诉我们要画的x的位置.particle[loop].y告诉我们要画的y的位置.particle[loop].z告诉我们要画的z的位置

float x=particle[loop].x; // Grab Our Particle X Position

float y=particle[loop].y; // Grab Our Particle Y Position

float z=particle[loop].z+zoom; // Particle Z Pos + Zoom

既然知道粒子位置,就能给粒子上色.particle[loop].r保存粒子的亮红,particle[loop].g保存粒子的亮绿,particle[loop].b保存粒子的亮蓝.注意我用alpha为粒子生命.当粒子要燃尽时,它会越来越透明直到它最后消失.这就是为什么粒子的生命不应该超过1.0f.如果你想粒子燃烧时间长,可降低fade减小的速度

// Draw The Particle Using Our RGB Values, Fade The Particle Based On It's Life

glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);

我们有粒子的位置,并设置颜色了.所以现在我们来画我们的粒子.我们用一个三角形带来代替一个四边形这样使程序运行快一点.很多3D card画三角形带比画四边形要快的多.有些3D card将四边形分成两个三角形,而有些不.所以我们按照我们自己的想法来,所以我们来画一个生动的三角形带

glBegin(GL_TRIANGLE_STRIP); // Build Quad From A Triangle Strip

从红色的书引述:三角形带就是画一连续的三角形(三个边的多角形)使用vertices V0,V1,V2,然后V2,V1,V3(注意顺序),然后V2,V3,V4等等.画三角形的顺序一样才能保证三角形带为相同的表面.要求方向是很重要的,例如:剔除,最少用三点来画当第一个三角形使用vertices0,1和2被画.如果你看图片你将会理解用顶点0,1和2构造第一个三角形(顶端右边,顶端左边,底部的右边).第二个三角形用点vertices2,1和3构造.再一次,如果你注意图片,点vertices2,1和3构造第二个三角形(底部右边,顶端左边,底部左边).注意:两个三角形画点顺序相同.我看到很多的网站要求第二个三角形反方向画.这是不对的.Opengl从新整理顶点来保证所有的三角形为同一方向!注意:你在屏幕上看见的三角形个数是你叙述的顶点的个数减2.在程序中在我们有4个顶点,所以我们看见二个三角形

glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z); // Top Right

glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // Top Left

glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // Bottom Right

glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z); // Bottom Left

最后我们告诉Opengl我们画完三角形带

glEnd(); // Done Building Triangle Strip

现在我们能移动粒子.下面公式可能看起来很奇怪,其实很简单.首先我们取得当前粒子的x位置.然后把x运动速度加上粒子被减速1000倍后的值.所以如果粒子在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); // Move On The X Axis By X Speed

particle[loop].y+=particle[loop].yi/(slowdown*1000); // Move On The Y Axis By Y Speed

particle[loop].z+=particle[loop].zi/(slowdown*1000); // Move On The Z Axis By Z Speed

在计算出下一步粒子移到那里,开始考虑重力和阻力.在下面的第一行,将阻力(xg)和移动速度(xi)相加.我们的移动速度是10和阻力是1.每时每刻粒子都在抵抗阻力.第二次画粒子时,阻力开始作用,移动速度将会从10掉到9.第三次画粒子时,阻力再一次作用,移动速度降低到8.如果粒子燃烧为超过10次重画,它将会最后结束,并向相反方向移动.因为移动速度会变成负值.阻力同样使用于y和z移动速度

particle[loop].xi+=particle[loop].xg; // Take Pull On X Axis Into Account

particle[loop].yi+=particle[loop].yg; // Take Pull On Y Axis Into Account

particle[loop].zi+=particle[loop].zg; // Take Pull On Z Axis Into Account

下行将粒子的生命减少.如果我们不这么做,粒子无法烧尽.我们用粒子当前的life减去当前的fade值.每粒子都有不同的fade值,因此他们全部将会以不同的速度烧尽

particle[loop].life-=particle[loop].fade; // Reduce Particles Life By 'Fade'

现在我们检查当生命为零的话粒子是否活着

if (particle[loop].life<0.0f) // If Particle Is Burned Out

{

如果粒子是小时(烧尽),我们将会使它复原.我们给它全值生命和新的衰弱速度.

particle[loop].life=1.0f; // Give It New Life

particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // Random Fade Value

我们也重新设定粒子在屏幕中心放置.我们重新设定粒子的x,y和z位置为零.

particle[loop].x=0.0f; // Center On X Axis

particle[loop].y=0.0f; // Center On Y Axis

particle[loop].z=0.0f; // Center On Z Axis

在粒子从新设置之后,将给它新的移动速度/方向.注意:我增加最大和最小值,粒子移动速度为从50到60的任意值,但是这次我们没将移动速度乘10.我们这次不想要一个爆发的效果,而要比较慢地移动粒子.也注意我把xspeed和x轴移动速度相加,y轴移动速度和yspeed相加.这个控制粒子的移动方向.

particle[loop].xi=xspeed+float((rand()%60)-32.0f); // X Axis Speed And Direction

particle[loop].yi=yspeed+float((rand()%60)-30.0f); // Y Axis Speed And Direction

particle[loop].zi=float((rand()%60)-30.0f); // Z Axis Speed And Direction

最后我们分配粒子一种新的颜色.变量col保存一个数字从1到11(12种颜色),我们用这个变量去找红,绿,蓝亮度在颜色箱里面.下面第一行表示红色的强度,数值保存在colors[col][0].所以如果col是0,红色的亮度就是1.0f.绿色的和蓝色的值用相同的方法读取.如果你不了解为什么红色亮度为1.0f那col就为0.我将一点点的解释.看着程序的最前面.找到那行:static GLfloat colors[12][3].注意:12行3列.三个数字的第一行是红色强度.第二行是绿色强度而且第三行是蓝色强度.[0],[1]和[2]下面描述的1st,2nd和3rd就是我刚提及的.如果col等于0,我们要看第一个组.11 是最後一个组(第12种颜色).

particle[loop].r=colors[col][0]; // Select Red From Color Table

particle[loop].g=colors[col][1]; // Select Green From Color Table

particle[loop].b=colors[col][2]; // Select Blue From Color Table

}

下行描述加速度的数值是多少.通过8号键,我们增加yg(y 地心引力)值.这引起向上的力.This code is located here in the program because it makes our life easier by applying the gravity to all of our particles thanks to the loop.如果这个程序在循环外面,那么我们必须生成另一个循环做相同的工作,因此我们最好放在这里

// If Number Pad 8 And Y Gravity Is Less Than 1.5 Increase Pull Upwards

if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;

这行是产生相反的效果.通过2号键,减小yg值,引起向下的力

// If Number Pad 2 And Y Gravity Is Greater Than -1.5 Increase Pull Downwards

if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;

现在更改向右的拉力.如果按下6号键时增加向右的拉力.

// If Number Pad 6 And X Gravity Is Less Than 1.5 Increase Pull Right

if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;

最后如果4号键被按下则增加向左的拉力.这些按键给了我们很酷的结果.举例来说:你可以用粒子造一条向上设的水流.通过增加向下的引力可以形成泉水

// If Number Pad 4 And X Gravity Is Greater Than -1.5 Increase Pull Left

if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;

我仅仅为乐趣增加了一些代码.我的兄弟产生很棒的效果:)通过按住tab键所有粒子都回到屏幕中心.所有的粒子在从新开始运动,再产生一个大的爆发.在粒子变弱之后,你最初的效果会再一次出现

if (keys[VK_TAB]) // Tab Key Causes A Burst

{

particle[loop].x=0.0f; // Center On X Axis

particle[loop].y=0.0f; // Center On Y Axis

particle[loop].z=0.0f; // Center On Z Axis

particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // Random Speed On X Axis

particle[loop].yi=float((rand()%50)-25.0f)*10.0f; // Random Speed On Y Axis

particle[loop].zi=float((rand()%50)-25.0f)*10.0f; // Random Speed On Z Axis

}

}

}

return TRUE; // Everything Went OK

}

代码KillGLWindow(),CreateGLWindow()和WndProc()中没有改变,所以我们直接跳到WinMain().我将重写代码

int WINAPI WinMain( HINSTANCE hInstance, // Instance

HINSTANCE hPrevInstance, // Previous Instance

LPSTR lpCmdLine, // Command Line Parameters

int nCmdShow) // Window Show State

{

MSG msg; // Windows Message Structure

BOOL done=FALSE; // Bool Variable To Exit Loop

// Ask The User Which Screen Mode They Prefer

if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)

{

fullscreen=FALSE; // Windowed Mode

}

// Create Our OpenGL Window

if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))

{

return 0; // Quit If Window Was Not Created

}

这是第一次对WinMain的改变.增加一些代码来看看使用者是否要在fullscreen模式或窗户模式下运行,我增加一些代码.如果决定使用fullscreen模式,我将slowdown有2.0f变成1.0f.如果你不想要可以不用这个代码.我增加了这行代码加速我3dfx上的fullscreen模式.

if (fullscreen) // Are We In Fullscreen Mode ( ADD )

{

slowdown=1.0f; // Speed Up The Particles (3dfx Issue) ( ADD )

}

while(!done) // Loop That Runs Until done=TRUE

{

if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?

{

if (msg.message==WM_QUIT) // Have We Received A Quit Message?

{

done=TRUE; // If So done=TRUE

}

else // If Not, Deal With Window Messages

{

TranslateMessage(&msg); // Translate The Message

DispatchMessage(&msg); // Dispatch The Message

}

}

else // If There Are No Messages

{

if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Updating View Only If Active

{

done=TRUE; // ESC or DrawGLScene Signalled A Quit

}

else // Not Time To Quit, Update Screen

{

SwapBuffers(hDC); // Swap Buffers (Double Buffering)

我喜欢简单的代码.在一行上不想包含很多东西,所以使代码象一个清洁工:)下面的代码检查"+"是否被按下.如果它和slowdown一起实现则slowdown减少0.01f.粒子就可以较快速地移动.

if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f; // Speed Up Particles

下面的代码检查"-"是否被按下.如果它和slowdown一起实现则slowdown增加0.01f.粒子就可以较慢速地移动.我实质的极限是4.0f,我不想它太慢的运动,你可以随你的要求改变最大最小值

if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f; // Slow Down Particles

下面的代码检测Page Up是否被按下.如果是,则zoom增加.从而导致粒子靠近我们

if (keys[VK_PRIOR]) zoom+=0.1f; // Zoom In

下行代码检测Page Down是否别按下,如果是,则zoom减小.从而导师粒子离开我们

if (keys[VK_NEXT]) zoom-=0.1f; // Zoom Out

下面的代码检验enter键是否被按下.如果是,并且没有被一直按着,我们将让计算机把rp变为true,然后我们固定彩虹模式.如果彩虹模式为true,将其变成false.如果为false,将其变成true.最后一行检测enter是否被释放,如果释放rp为false并告诉计算机该键不被按下

if (keys[VK_RETURN] && !rp) // Return Key Pressed

{

rp=true; // Set Flag Telling Us It's Pressed

rainbow=!rainbow; // Toggle Rainbow Mode On / Off

}

if (!keys[VK_RETURN]) rp=false; // If Return Is Released Clear Flag

下面程序有点乱.第一行检查space键是否被按下并没有没一直按着.并检查彩虹模式是否开始运行,如果是,检查delay是否大于25.delay是我创建的显示彩虹效果的数值.如果你曾经改变颜色结构,粒子将显示不同颜色.通过创建一个delay,在颜色改变之前,一组粒子将是一种颜色.如果space按下,彩虹运行,delay值大于25则颜色改变

if ((keys[' '] && !sp) || (rainbow && (delay>25))) // Space Or Rainbow Mode

{

下面行是为了当space按下则彩虹关掉而设置的.如果我们不关掉彩虹模式,颜色会继续变化直到enter再被按下.也就是说人们按下space来代替enter是想叫粒子颜色自己变化

if (keys[' ']) rainbow=false; // If Spacebar Is Pressed Disable Rainbow Mode

如果space键被按下,或者彩虹模式已开始,并且delay大于25,我们叫计算机知道space键被按下通过叫sp为true.然后我们将delay设定回0以便它能在到25.最后我们增加col的值以便它通过颜色箱里面改变成另一个颜色.

sp=true; // Set Flag Telling Us Space Is Pressed

delay=0; // Reset The Rainbow Color Cycling Delay

col++; // Change The Particle Color

如果颜色值大于11,我们把它重新设为零.如果我们不重新设定为零,程序将去找第13颜色.而我们只有12种颜色!寻找不存在的颜色将会导致程序瘫痪

if (col>11) col=0; // If Color Is To High Reset It

}

最后如果space键不被按下,我们将sp设为false。

if (!keys[' ']) sp=false; // If Spacebar Is Released Clear Flag

现在对粒子增加一些控制.还记得我们从开始定义的2变量么?一个xspeed,一个yspeed.在粒子燃尽之后,我们给它新的移动速度且把新的速度加入到xspeed和yspeed中.这样当粒子被创建时将影响粒子的速度. 举例来说:粒子在x轴上的速度为5在y轴上的速度为0.当我们减少xspeed到-10,我们将以-10(xspeed)+5(最初的移动速度)的速度移动.这样我们将以5的速度向左移动.明白了么??无论如何,下面的代码检测UP是否被按下.如果它,yspeed将增加这将引起粒子向上运动.最大速度不超过200.速度在快就不好看了

// If Up Arrow And Y Speed Is Less Than 200 Increase Upward Speed

if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;

这行检查Down键是否被按下,如果它是,yspeed将减少.这将引起粒子向下运动.再一次,最大速度为200

// If Down Arrow And Y Speed Is Greater Than -200 Increase Downward Speed

if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;

现在我们检查Right键是否被按下.如果它是..xspeed将被增加.粒子将移到右边.最大速度为200

// If Right Arrow And X Speed Is Less Than 200 Increase Speed To The Right

if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;

最后我们检查Left键是否被按下.如果是...你猜....xspeed被减小,粒子开始向左移动.最大速度为200

// If Left Arrow And X Speed Is Greater Than -200 Increase Speed To The Left

if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;

最后我们要增加delay的数值.像我在前面所说,delay是控制彩色变化的

delay++; // Increase Rainbow Mode Color Cycling Delay Counter

像前几课一样,我们最后还需要更正窗体的标题.

if (keys[VK_F1]) // Is F1 Being Pressed?

{

keys[VK_F1]=FALSE; // If So Make Key FALSE

KillGLWindow(); // Kill Our Current Window

fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode

// Recreate Our OpenGL Window

if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))

{

return 0; // Quit If Window Was Not Created

}

}

}

}

}

// Shutdown

KillGLWindow(); // Kill The Window

return (msg.wParam); // Exit The Program

在课程中,我试着把所有细节都讲清楚,并且简单的了解粒子系统.这个粒子系统能在游戏产生例如火,水,雪,爆炸,流行等效果.程序能简单的修改参数来实现新的效果(例:烟花效果)

第一次翻译英文教程,因本人英语水平实在有限,教程中难免错误,希望大家指教.希望我翻译对大家有帮助!!!!!!!!!!!

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有