粒子系统

发表于2015-12-28
评论1 2.3k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594
  以前一直没有接触过粒子系统,上一个引擎由于试运行在iPhone上,也没有专门的实现这一模块。最近由于工作关系不得不进行研究并写一个编辑器。
  通过这一段时间对各种粒子系统,特效系统的研究我也有了进一步的了解,在51期间自己写了一个十分简单的粒子系统框架,给大家共享(见后面的连接),并打算进一步集成到目前正在独立开发的ZeusEngine中。
  看到粒子系统作的各种效果感觉很神秘也很强大,其实粒子系统本质很简单。粒子系统包括发射器,影响器还有图形(或者叫渲染器),这三个部分被集成到一个叫粒子系统的类中,他们是一have a的形式集成近来,也就是说使用成员变量的方式。他们的工作原理可以用一个例子来简单解释:发射器就是一把冲锋枪,粒子就是弹夹中的子弹,影响器就是风,重力等等这些。冲锋枪把弹夹中的粒子发射出去,在外面受到风,重力这些物理因素的影响。这就是粒子系统。
  从上面可以看到,粒子本身就是一组数据结构,一般包括位置,方向,缩放,颜色,uv偏移等等。那么是如何基于这些基本的信息绘制出绚丽的效果呢?这就是图形(渲染器)的作用,这里以常用的billboard为例,渲染器获得了当前活动粒子的这些信息,并根据这些信息构造billboard的几何数据,并加以渲染。而像轨迹,闪电等这些效果则是其他类型的渲染器根据具体的要求单独进行实现的。
  接下来简单说一说Ogre的粒子系统。Ogre的粒子系统的结构和我刚才描述的大致相仿,但需要注意的是,Ogre的发射器可以发射本粒子系统内的其他发射器,从而实现更为复杂的效果。从设计层面上来讲这样做并不彻底,既然可以发射发射器,那为什么不能发射其他的粒子系统呢?这样可以实现更为复杂的效果,而且更重要的是它可以重用已经做好的粒子的资源。
  这个问题我想是和Ogre自身的场景管理机制以及实现复杂度有关。之所以这么说是因为,发射器可以发射本粒子系统内的发射器,这样这些原始发射器和被发射的发射器他们都是用同一个粒子池统一由粒子系统本身进行管理调度,而发射粒子系统,显然无法共用不同粒子系统内的粒子池,如果非要实现共享粒子池将带来很多不必要的复杂度。
  然后就是Ogre的场景管理机制,特效系统是要挂在场景节点上才予以显示更新等,发射器发射出来的特效系统改挂在那呢?这是一个大问题,如果挂在发射他的粒子系统的节点上,那么显然增大原粒子系统的包围盒区域,造成无效更新不合理而且随着粒子的消亡还要动态的从节点上摘除,当粒子量很大的时候效率问题就不得不考虑了,但除此之外还能挂在那呢?似乎也没有一个合理的位置。这个问题的根源就在于Ogre把粒子系统直接和场景节点挂钩,这一点由于Ogre自身的原因(使用了太多的设计模式,各个模块又存在复杂的关系)很难进行修改。
  再说说火炬之光使用的Particle Universe这个第三方库。这个第三方的粒子库的确是比Ogre自身的强大不少。在结构上增加了一个Technique,这个就相当于Ogre的ParticleSystem,而他的ParticleSystem可以包含多个Technique,与此同时还使用了大量的监听器等。他大量扩展了发射器影响器渲染器的类型。当然它的代码量也是相当的巨大的,好在结构还是很清晰的。这里我想专门说一下他的监听器的机制,Particle Universe可以监控粒子的发射,停止,位置等等,同时还可以扩展。他的目的就是对粒子从发射到影响到消失的关键点进行监听以实现特出的处理。这就像一个潘多拉盒子,如果应用不当将会极大的影响设计,为什么这么说呢。他原本的设计目的就是为了更加复杂的粒子效果,但既然可以扩展就会有人在这个地方引入大量引擎无关而逻辑相关的引入,从而引擎部引擎逻辑不逻辑。比方说要求某一个攻击效果跟随某个角色并最终击中,这个原本是一个逻辑的过程(由游戏或者客户端负责粒子的位置更新并进行最终调用方法检测粒子与角色碰撞)而却可能有人想在监听器里完成这所有过程,比方说在粒子发射开始通过DoEmit,来设置角色的指针,然后通过更改粒子更新的实现来达到跟踪那个角色的需求。
  最后说一说特效,粒子只是特效的一部分并不是全部,特效包括粒子,震动,贴画,声音等等。显然特效是一个更高层的封装,将各个特效元素集成近来。
  粒子系统简单框架下载地址,需要自行编译
  http://download.csdn.net/source/2310050
  其中实现了粒子系统的框架,包括离子调度,发射器影响器图形等基类,一个点发射器,线性力影响器,和一个debug的调试图形,可根据需要自行进行扩展。其中没有包括IO。
  初始化和渲染代码如下,在GLSample.cpp文件
//
// 初始化特效系统
//
void InitParticleSystem(void)
{
      VEC3 position;
      VEC3 direction;
      Vec3Set(&position, 0.0f, 0.0f, 0.0f);
      Vec3Set(&direction, 0.0f, 1.0f, 0.0f);
      particleSystem.SetPosition(&position);
      particleSystem.SetDirection(&direction);
      particleSystem.SetParticleQuota(500, CParticle::VISTUAL, NULL);
      particleSystem.SetGfx(CGfxBase::GFX_DEBUG);
      if (CEmitterPoint *pEmitter = (CEmitterPoint *)particleSystem.AddEmitter(CEmitterBase::EMITTER_POINT)) {
            pEmitter->SetKeepLocal(FALSE);
            pEmitter->SetPosition(&position);
            pEmitter->SetDirection(&direction);
            pEmitter->SetEmitRate(50);
            pEmitter->SetBeginTime(0, 0);
            pEmitter->SetDurationTime(30, 60);
            pEmitter->SetRepeatDelayTime(0, 0);
            pEmitter->SetLiveTime(60, 60);
            pEmitter->SetVelocity(2.5f, 5.0f);
            pEmitter->SetRadian(0.0f, PI/4);
      }
      if (CAffectorLinear *pAffector = (CAffectorLinear *)particleSystem.AddAffector(CAffectorBase::AFFECTOR_LINEAR)) {
            pAffector->SetBeginTime(0.0f, 0.0f);
            pAffector->SetDurationTime(0.1f, 0.5f);
            VEC3 forceVector;
            Vec3Set(&forceVector, 1.0f, 0.0f, 0.0f);
            pAffector->SetForceVector(&forceVector);
      }
      particleSystem.Play();
}
//
// 渲染
//
DWORD Render(void)
{
      LARGE_INTEGER beginCount;
      LARGE_INTEGER endCount;
      LARGE_INTEGER frequency;
      QueryPerformanceFrequency(&frequency);
      QueryPerformanceCounter(&beginCount);
      {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            gluLookAt(0.0f, 0.0f, 1000.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
            particleSystem.Update();
            particleSystem.Render();
      }
      QueryPerformanceCounter(&endCount);
      DWORD dwTime = (DWORD)(1000000*(endCount.QuadPart - beginCount.QuadPart)/frequency.QuadPart);
      DWORD dwFPS = (DWORD)(1000000/dwTime);
      char szTitle[1024];
      sprintf(szTitle, "FPS = %d, Runtime = %f", dwFPS, dwTime/1000.0f);
      SetWindowText(hWnd, szTitle);
      return 0;
}
粒子系统实现效果,其中的蓝轴是粒子的朝向

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台