OpenGL粒子系统详解及编程实现

粒子系统的基本思想是:采用许多形状简单的微小粒子作为基本元素,用它们来表示不规则模糊物体。这些粒子都有各自的生命周期,在系统中都要经历“产生” 、 “运动和生长”及“消亡”三个阶段。粒子系统是一个有“生命”的系统,因此不象传统方法那样只能生成瞬时静态的景物画面,而是可以产生一系列运动进化的画面,这使得模拟动态的自然景物成为可能。

这篇文章讲解不用GPU加速的一个粒子系统实例。

利用粒子系统生成画面的基本步骤:  
1、产生新的粒子;  
2、赋予每一新粒子一定的属性;  
3、删去那些已经超过生存期的粒子;  
4、根据粒子的动态属性对粒子进行移动和变换;  
5、显示由有生命的粒子组成的图像。   

粒子系统采用随机过程来控制粒子的产生数量,确定新产生粒子的一些初始随机属性,如初始运动方向、初始大小、初始颜色、初始透明度、初始形状以及生存期等,并在粒子的运动和生长过程中随机地改变这些属性。粒子系统的随机性使模拟不规则模糊物体变得十分简便。  
粒子系统应用的关键在于如何描述粒子的运动轨迹,也就是构造粒子的运动函数。函数选择的恰当与否,决定效果的逼真程度。其次,坐标系的选定(即视角)也有一定的关系,视角不同,看到的效果自然不一样了。  


我们需要定义一个数据结构来描述粒子。这里定义一个struct类型,包含一些描述粒子特性的数据成员:

typedef struct//structrue for particle
{
  bool active;//active or not
  float life;//last time
  float fade;//describe the decreasing of life
  float r,g,b;//color
  float x,y,z;//the position
  float xi,yi,zi;//what direction to move
  float xg,yg,zg;//gravity
}particles;
其数据成员的意义:

active:粒子是否激活,没有激活的粒子将不会显示在屏幕上;

life:粒子的生命时间,也就是持续显示的时间。后面会被用在粒子的颜色的a值上。当它为0时,粒子完全透明,也就看不见了。所以其值应该不大于1.0f。

fade;描述life的衰减程度;

r、g、b:粒子的颜色

xi、yi、zi描述粒子的运动方向;

xg、yg、zg:描述粒子运动受到的阻力的方向。


接着我们要定义一个粒子集合,包含很多个粒子:

#define MAX_PARTICLES 1000
particles paticle[MAX_PARTICLES];

对粒子系统进行初始化:

for (loop = 0;loop < MAX_PARTICLES;++loop)
  {
    paticle[loop].active = true;
    paticle[loop].life = 1.0f;
    paticle[loop].fade = float(rand() % 100) / 1000.0f + 0.003f;
    paticle[loop].r = colors[loop * (12 / MAX_PARTICLES)][0];
    paticle[loop].g = colors[loop * (12 / MAX_PARTICLES)][1];
    paticle[loop].b = colors[loop * (12 / MAX_PARTICLES)][2];

    paticle[loop].xi = float((rand() % 50) - 26.0f) * 10.0f;
    paticle[loop].yi = float((rand() % 50) - 25.0f) * 10.0f;
    paticle[loop].zi = float((rand() % 50) - 25.0f) * 10.0f;

    paticle[loop].xg=0.8f;
    paticle[loop].yg=0.8f;
    paticle[loop].zg=0.8f;
  }

colors是预定义的以供使用的颜色数组:

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


粒子系统的绘制: 
我们把每个粒子看成单独的一个对象来看待。在这里用由两个三角形组成的序列来表示,采用  GL_TRIANGLE_STRIP  方法绘制。 

glBegin(GL_TRIANGLE_STRIP);
  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 
glEnd();
这里不用绘制四边形的方法是因为绘制三角形速度要快很多,而且大多数图形卡本来就是把绘制i四边形的工作转化成为三角形实现。但是并不是所以的图形卡都这么做,所以我们手动实现。  GL_TRIANGLE_STRIP  的运作原理可以参考这里:  GL_TRIANGLE_STRIP  

在本程序中,我们启用了混合。我们开启了混合功能,既为顶点绘制指定了颜色值,也使用了纹理贴图。

我们从硬盘上读取bmp图像,加载纹理。采用了GLAUX库实现。可以参考: GLAUX


程序完整代码:

#pragma comment(lib,"GLAUX.LIB")

#include <iostream>
#include <GL/glut.h>
#include <GL/glaux.h>
using namespace std;

#define MAX_PARTICLES 1000

typedef struct//structrue for particle
{
  bool active;//active or not
  float life;//last time
  float fade;//describe the decreasing of life
  float r,g,b;//color
  float x,y,z;//the position
  float xi,yi,zi;//what direction to move
  float xg,yg,zg;//gravity
}particles;

bool rainbow = true;
bool sp,rp;//space button and return button pressed or not?
float slowdown = 2.0f;
float xspeed,yspeed;
float zoom = -40.0f;
bool gkeys[256];

GLuint loop,col,delay,texture[1];

particles paticle[MAX_PARTICLES];

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

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 
} 

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 
} 

void reshape(int w,int h)
{
  if(0 == h)
    h = 1;
  glViewport(0,0,(GLsizei)w,(GLsizei)h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0f,(GLfloat)w / (GLfloat)h,0.1f,200.0f);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

int init()
{
  if (!LoadGLTextures())
  {
    return false;
  }
  glShadeModel(GL_SMOOTH);
  glClearColor(0.0f,0.0f,0.0f,0.0f);
  glClearDepth(1.0f);
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE);
  //really nice perspective calculations
  glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
  glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D,texture[0]);

  for (loop = 0;loop < MAX_PARTICLES;++loop)
  {
    paticle[loop].active = true;
    paticle[loop].life = 1.0f;//full life is 1.0f
    //life decrease rate(a random value add 0.003f : never equals 0)
    paticle[loop].fade = float(rand() % 100) / 1000.0f + 0.003f;
    paticle[loop].r = colors[loop * (12 / MAX_PARTICLES)][0];
    paticle[loop].g = colors[loop * (12 / MAX_PARTICLES)][1];
    paticle[loop].b = colors[loop * (12 / MAX_PARTICLES)][2];

    paticle[loop].xi = float((rand() % 50) - 26.0f) * 10.0f;
    paticle[loop].yi = float((rand() % 50) - 25.0f) * 10.0f;
    paticle[loop].zi = float((rand() % 50) - 25.0f) * 10.0f;

    paticle[loop].xg=0.0f;
    paticle[loop].yg=-0.8f;
    paticle[loop].zg=0.8f;
  }
  return true;
}

void display()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  for (loop = 0;loop < MAX_PARTICLES;++loop)
  {
    if (paticle[loop].active)//the particle is alive
    {
      float x = paticle[loop].x;
      float y = paticle[loop].y;
      //our scene is moved into the screen
      float z = paticle[loop].z + zoom;

      glColor4f(paticle[loop].r,
        paticle[loop].g,
        paticle[loop].r,
        //use life as alpha value:
        //as particle dies,it becomes more and more transparent
        paticle[loop].life);

      //draw particles : use triangle strip instead of quad to speed
      glBegin(GL_TRIANGLE_STRIP);
        //top right
        glTexCoord2d(1,1);
        glVertex3f(x + 0.5f,y + 0.5f,z);
        //top left
        glTexCoord2d(0,1);
        glVertex3f(x - 0.5f,y + 0.5f,z);
        //bottom right
        glTexCoord2d(1,0);
        glVertex3f(x + 0.5f,y - 0.5f,z);
        //bottom left
        glTexCoord2d(0,0);
        glVertex3f(x - 0.5f,y - 0.5f,z);
      glEnd();

      //Move On The X Axis By X Speed 
      paticle[loop].x+=paticle[loop].xi/(slowdown*1000); 
      //Move On The Y Axis By Y Speed 
      paticle[loop].y+=paticle[loop].yi/(slowdown*1000); 
      //Move On The Z Axis By Z Speed 
      paticle[loop].z+=paticle[loop].zi/(slowdown*1000); 

      //add gravity or resistance : acceleration
      paticle[loop].xi += paticle[loop].xg;
      paticle[loop].yi += paticle[loop].yg;
      paticle[loop].zi += paticle[loop].zg;

      //decrease particles' life
      paticle[loop].life -= paticle[loop].fade;

      //if particle is dead,rejuvenate it
      if (paticle[loop].life < 0.0f)
      {
        paticle[loop].life = 1.0f;//alive again
        paticle[loop].fade = float(rand() % 100) / 1000.0f + 0.003f;

        //reset its position
        paticle[loop].x = 0.0f;
        paticle[loop].y = 0.0f;
        paticle[loop].z = 0.0f;

        //X Axis Speed And Direction 
        paticle[loop].xi=xspeed+float((rand()%60)-32.0f); 
        // Y Axis Speed And Direction 
        paticle[loop].yi=yspeed+float((rand()%60)-30.0f); 
        // Z Axis Speed And Direction 
        paticle[loop].zi=float((rand()%60)-30.0f); 

        paticle[loop].r=colors[col][0];			// Select Red From Color Table
        paticle[loop].g=colors[col][1];			// Select Green From Color Table
        paticle[loop].b=colors[col][2];			// Select Blue From Color Table
      }
    }
  }

  glutSwapBuffers();
}

void keyboard(unsigned char key,int x,int y)
{
  switch(key)
  {
  case 'y':
    gkeys['y'] = true;
    break;
  case 'c':
    ++col;
    if (col >= 12)
    {
      col -= 12;
    }
    glutPostRedisplay();
    break;
  case 'l':
    slowdown += 0.3;
    glutPostRedisplay();
    break;
  case 'h':
    slowdown += 0.3;
    glutPostRedisplay();
    break;
  default:
    break;
  }
}

int main(int argc,char** argv)
{
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(400,400);
  glutInitWindowPosition(100,100);
  glutCreateWindow("Particle");
  init();
  glutDisplayFunc(display);
  glutIdleFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值