cesium粒子系统

1,粒子系统的作用。cesium粒子系统可以用来模拟汽车尾气,烟花,火焰,雨雪,落叶等。例如下面


汽车尾气

烟花效果

2,我们看下生成粒子的代码

  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",

    startColor: Cesium.Color.LIGHTSEAGREEN.withAlpha(0.7),
    endColor: Cesium.Color.WHITE.withAlpha(0.0),

    startScale: viewModel.startScale,
    endScale: viewModel.endScale,

    minimumParticleLife: viewModel.minimumParticleLife,
    maximumParticleLife: viewModel.maximumParticleLife,

    minimumSpeed: viewModel.minimumSpeed,
    maximumSpeed: viewModel.maximumSpeed,

    imageSize: new Cesium.Cartesian2(
      viewModel.particleSize,
      viewModel.particleSize
    ),

    emissionRate: viewModel.emissionRate,

    bursts: [
      // these burst will occasionally sync to create a multicolored effect
      new Cesium.ParticleBurst({
        time: 5.0,
        minimum: 10,
        maximum: 100,
      }),
      new Cesium.ParticleBurst({
        time: 10.0,
        minimum: 50,
        maximum: 100,
      }),
      new Cesium.ParticleBurst({
        time: 15.0,
        minimum: 200,
        maximum: 300,
      }),
    ],

    lifetime: 16.0,

    emitter: new Cesium.CircleEmitter(2.0),

    emitterModelMatrix: computeEmitterModelMatrix(),

    updateCallback: applyGravity,
  })
)

这段代码初始化了ParticleSystem类,我们看下这个类的参数情况。
3,ParticleSystem类


ParticleSystem类

(1)show 是否显示粒子系统
(2)updateCallback 每帧更新粒子的回调
(3)emitter 粒子发射器,就是发射粒子的方式。cesium粒子系统提供的发射器有BoxEmitter盒子发射器,发射的粒子都在一个盒子内。CircleEmitter圆形发射器,发射的粒子都在一个圆形范围内。ConeEmitter锥形发射器,发射的粒子都在一个圆锥内。SphereEmitter球体发射器,发射的粒子都在一个包围球内。
(4)modelMatrix 粒子系统的偏移矩阵
(5)emitterModelMatrix 粒子系统相对自身位置的偏移矩阵
(6)emissionRate 单位粒子的发射数量
(7)bursts 特定时间粒子产生的数量,可用做粒子爆炸效果
(8)loop 粒子系统是否一直循环存在
(9)scale 粒子的缩放比例
(10)startScale粒子产生时候的缩放比例
(11)endScale粒子消亡时候的缩放比例
(12)color粒子的颜色
(13)startColor 粒子开始产生时候的颜色
(14)endColor 粒子消亡时候的颜色
(15)image 用于产生粒子的图片
(16)imageSize 粒子图片的尺寸
(17)minimumImageSize 随机粒子产生时候最小的尺寸
(18)maximumImageSize 随机粒子产生时候最大的尺寸
(19)sizeInMeters 粒子尺寸的单位是像素还是米
(20)speed 粒子的速度
(21)minimumSpeed 随机粒子的最小速度
(22)maximumSpeed 随机粒子的最大速度
(23)lifetime 粒子系统的生命周期
(24)particleLife 粒子的生命周期
(25)minimumParticleLife 随机粒子的最小生命周期
(26)maximumParticleLife 随机粒子的最大生命周期
(27)mass粒子的质量
(28)minimumMass 随机粒子的最小质量
(29)maximumMass 随机粒子的最大质量
4,粒子系统的更新


粒子系统更新的函数调用栈

从图中我们可用看到
先是Scene的render方法->Scene.updateAndExecuteCommands->.....->ParticleSystem.update
我们再看看ParticleSystem.update方法做了哪些事情

 * @private
 * 触发粒子系统更新
 */
ParticleSystem.prototype.update = function (frameState) {
  if (!this.show) {
    return;
  }
  //创建billboard集合存放粒子
  if (!defined(this._billboardCollection)) {
    this._billboardCollection = new BillboardCollection();
  }
  //更新粒子池,向粒子池添加新粒子
  if (this._updateParticlePool) {
    updateParticlePool(this);
    this._updateParticlePool = false;
  }

  // Compute the frame time
  //计算间隔两帧时间差
  var dt = 0.0;
  if (this._previousTime) {
    dt = JulianDate.secondsDifference(frameState.time, this._previousTime);
  }

  if (dt < 0.0) {
    dt = 0.0;
  }

  var particles = this._particles;
  var emitter = this._emitter;
  var updateCallback = this.updateCallback;

  var i;
  var particle;

  // update particles and remove dead particles
  var length = particles.length;
  for (i = 0; i < length; ++i) {
    particle = particles[i];
    //判断粒子是否死亡,否则更新广告牌
    if (!particle.update(dt, updateCallback)) {
      removeBillboard(particle);
      // Add the particle back to the pool so it can be reused.
      addParticleToPool(this, particle);
      particles[i] = particles[length - 1];
      --i;
      --length;
    } else {
      //更新粒子广告牌参数
      updateBillboard(this, particle);
    }
  }
  particles.length = length;
  //计算发射的粒子数量
  var numToEmit = calculateNumberToEmit(this, dt);

  if (numToEmit > 0 && defined(emitter)) {
    // Compute the final model matrix by combining the particle systems model matrix and the emitter matrix.
    //计算粒子系统最终的位移矩阵
    if (this._matrixDirty) {
      this._combinedMatrix = Matrix4.multiply(
        this.modelMatrix,
        this.emitterModelMatrix,
        this._combinedMatrix
      );
      this._matrixDirty = false;
    }

    var combinedMatrix = this._combinedMatrix;

    for (i = 0; i < numToEmit; i++) {
      // Create a new particle.
      //获取一个粒子
      particle = getOrCreateParticle(this);

      // Let the emitter initialize the particle.
      //使用粒子发射器给粒子设置一个相对发射器中心的偏移坐标
      this._emitter.emit(particle);

      //For the velocity we need to add it to the original position and then multiply by point.
      Cartesian3.add(
        particle.position,
        particle.velocity,
        rotatedVelocityScratch
      );
      Matrix4.multiplyByPoint(
        combinedMatrix,
        rotatedVelocityScratch,
        rotatedVelocityScratch
      );

      // Change the position to be in world coordinates
      particle.position = Matrix4.multiplyByPoint(
        combinedMatrix,
        particle.position,
        particle.position
      );

      // Orient the velocity in world space as well.
      Cartesian3.subtract(
        rotatedVelocityScratch,
        particle.position,
        particle.velocity
      );
      Cartesian3.normalize(particle.velocity, particle.velocity);

      // Add the particle to the system.
      //先将粒子系统的参数赋给粒子,然后将粒子添加进粒子系统。再更新粒子广告牌
      addParticle(this, particle);
      updateBillboard(this, particle);
    }
  }
  //更新粒子系统的广告牌集合
  this._billboardCollection.update(frameState);
  //将当前帧的时间保存
  this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
  this._currentTime += dt;
  //如果当前时间大于粒子系统时间,且没设置loop属性为true则粒子系统执行完成,粒子系统消失。
  if (
    this._lifetime !== Number.MAX_VALUE &&
    this._currentTime > this._lifetime
  ) {
    //如果粒子系统启用循环,则重置粒子系统当前时间,爆炸数组的执行状态
    if (this.loop) {
      this._currentTime = CesiumMath.mod(this._currentTime, this._lifetime);
      if (this.bursts) {
        var burstLength = this.bursts.length;
        // Reset any bursts
        for (i = 0; i < burstLength; i++) {
          this.bursts[i]._complete = false;
        }
      }
    } else {
      this._isComplete = true;
      this._complete.raiseEvent(this);
    }
  }

  // free particles in the pool and release billboard GPU memory
  //每120帧释放粒子池内存
  if (frameState.frameNumber % 120 === 0) {
    freeParticlePool(this);
  }
};

粒子系统更新机制.png

先上创建了一个cesium广告牌的集合"this._billboardCollection = new BillboardCollection(); "
然后更新粒子池调用updateParticlePool方法,updateParticlePool方法的作用是预估粒子系统中可能有多少个粒子,然后调用" new Particle()"创建粒子,并向粒子池添加新粒子。

然后计算当前帧的时间和粒子系统上一个帧状态的时间计算时间差"dt"

  var dt = 0.0;
  if (this._previousTime) {
    dt = JulianDate.secondsDifference(frameState.time, this._previousTime);
  }

  if (dt < 0.0) {
    dt = 0.0;
  }

然后遍历粒子系统中的粒子是否还存活。存活的话就调用updateBillboard方法来更新粒子的状态,例如粒子的颜色,速度,尺寸等。如果粒子已经死亡,调用removeBillboard方法将粒子广告牌隐藏掉,并调用addParticleToPool方法将死亡的粒子添加到粒子池中。

// update particles and remove dead particles
  var length = particles.length;
  for (i = 0; i < length; ++i) {
    particle = particles[i];
    //判断粒子是否死亡,否则更新广告牌
    if (!particle.update(dt, updateCallback)) {
      removeBillboard(particle);
      // Add the particle back to the pool so it can be reused.
      addParticleToPool(this, particle);
      particles[i] = particles[length - 1];
      --i;
      --length;
    } else {
      //更新粒子广告牌参数
      updateBillboard(this, particle);
    }
  }

计算需要发射的粒子数量

  var numToEmit = calculateNumberToEmit(this, dt);

合并粒子系统的模型矩阵和偏移矩阵

 if (numToEmit > 0 && defined(emitter)) {
    // Compute the final model matrix by combining the particle systems model matrix and the emitter matrix.
    //计算粒子系统最终的位移矩阵
    if (this._matrixDirty) {
      this._combinedMatrix = Matrix4.multiply(
        this.modelMatrix,
        this.emitterModelMatrix,
        this._combinedMatrix
      );
      this._matrixDirty = false;
    }

    var combinedMatrix = this._combinedMatrix;

调用getOrCreateParticle方法从粒子池中获取粒子,或者新创建粒子。然后调用”this._emitter.emit(particle);“发射粒子,粒子发射器的作用的限定发射的粒子在一个特定范围内,比如圆内,球内,盒子内等。然后调用combinedMatrix矩阵重新计算粒子的位置和速度。调用” addParticle(this, particle);“将粒子的参数信息,例如粒子生命周期,粒子的颜色,尺寸等赋给粒子,然后将粒子添加到粒子系统中。调用”updateBillboard(this, particle)"更新粒子广告牌的状态。


    for (i = 0; i < numToEmit; i++) {
      // Create a new particle.
      //获取一个粒子
      particle = getOrCreateParticle(this);

      // Let the emitter initialize the particle.
      //使用粒子发射器给粒子设置一个相对发射器中心的偏移坐标
      this._emitter.emit(particle);

      //For the velocity we need to add it to the original position and then multiply by point.
      Cartesian3.add(
        particle.position,
        particle.velocity,
        rotatedVelocityScratch
      );
      Matrix4.multiplyByPoint(
        combinedMatrix,
        rotatedVelocityScratch,
        rotatedVelocityScratch
      );

      // Change the position to be in world coordinates
      particle.position = Matrix4.multiplyByPoint(
        combinedMatrix,
        particle.position,
        particle.position
      );

      // Orient the velocity in world space as well.
      Cartesian3.subtract(
        rotatedVelocityScratch,
        particle.position,
        particle.velocity
      );
      Cartesian3.normalize(particle.velocity, particle.velocity);

      // Add the particle to the system.
      //先将粒子系统的参数赋给粒子,然后将粒子添加进粒子系统。再更新粒子广告牌
      addParticle(this, particle);
      updateBillboard(this, particle);
    }

更新粒子系统的广告牌集合

//更新粒子系统的广告牌集合
  this._billboardCollection.update(frameState);

重置粒子系统的当前时间和上一个状态时间

  this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
  this._currentTime += dt;

判断粒子系统的生命周期是否结束,如果粒子系统设置了"loop"循环机制,就重置粒子系统的当前时间,重置”this.bursts[i]._complete = false;“粒子爆炸效果的状态。

//如果当前时间大于粒子系统时间,且没设置loop属性为true则粒子系统执行完成,粒子系统消失。
  if (
    this._lifetime !== Number.MAX_VALUE &&
    this._currentTime > this._lifetime
  ) {
    //如果粒子系统启用循环,则重置粒子系统当前时间,爆炸数组的执行状态
    if (this.loop) {
      this._currentTime = CesiumMath.mod(this._currentTime, this._lifetime);
      if (this.bursts) {
        var burstLength = this.bursts.length;
        // Reset any bursts
        for (i = 0; i < burstLength; i++) {
          this.bursts[i]._complete = false;
        }
      }
    } else {
      this._isComplete = true;
      this._complete.raiseEvent(this);
    }
  }

每120帧释放粒子系统的GPU缓存,重置粒子池,到此粒子系统更新结束。

  //每120帧释放粒子池内存
  if (frameState.frameNumber % 120 === 0) {
    freeParticlePool(this);
  }

5, Particle粒子类


Particle类

(1),mass 粒子的质量
(2),position粒子的位置
(3),velocity粒子是速度
(4),life粒子的生命周期
(5),image用做例子的图片
(6),startColor 粒子生成时候的颜色
(7),endColor 粒子死亡时候的颜色
(8),startScale 粒子生成时候的缩放比例
(9),endScale 粒子死亡时候的缩放比例
(10),imageSize 用作粒子的图片的尺寸

6,粒子的更新

我们再看看”Particle“粒子类的”update"方法做了啥

 * @private
 */
Particle.prototype.update = function (dt, particleUpdateFunction) {
  // Apply the velocity
  //计算位移
  Cartesian3.multiplyByScalar(this.velocity, dt, deltaScratch);
  //当前坐标+位移
  Cartesian3.add(this.position, deltaScratch, this.position);

  // Update any forces.
  //触发粒子的更新回调
  if (defined(particleUpdateFunction)) {
    particleUpdateFunction(this, dt);
  }

  // Age the particle
  this._age += dt;

  // Compute the normalized age.
  //计算粒子存活的期限比例,比如life是100,this._age是50,则粒子存活了50%的时间
  if (this.life === Number.MAX_VALUE) {
    this._normalizedAge = 0.0;
  } else {
    this._normalizedAge = this._age / this.life;
  }

  // If this particle is older than it's lifespan then die.
  //如果粒子的年纪比生命期限大,粒子死亡,返回false
  return this._age <= this.life;
};
export default Particle;

根据时间和速度更新粒子的位置

//计算位移
  Cartesian3.multiplyByScalar(this.velocity, dt, deltaScratch);
  //当前坐标+位移
  Cartesian3.add(this.position, deltaScratch, this.position);

如果存在粒子更新时候的回调函数,就执行回调函数

//触发粒子的更新回调
  if (defined(particleUpdateFunction)) {
    particleUpdateFunction(this, dt);
  }

计算粒子的寿命比例“this._normalizedAge”

  this._age += dt;

  // Compute the normalized age.
  //计算粒子存活的期限比例,比如life是100,this._age是50,则粒子存活了50%的时间
  if (this.life === Number.MAX_VALUE) {
    this._normalizedAge = 0.0;
  } else {
    this._normalizedAge = this._age / this.life;
  }

根据粒子存活时间和生命周期做比较判断粒子是否还存活

  return this._age <= this.life;

7,ParticleEmitter粒子发射器类
ParticleEmitter有一个emit方法用来执行粒子的发射操作


ParticleEmitter.prototype.emit

ParticleEmitter是基类不能被直接实例化,cesium一共提供了4种粒子发射器,分别是BoxEmitter 盒子发射器,CircleEmitter圆形发射器,ConeEmitter圆锥发射器,SphereEmitter球形发射器。
我们以CircleEmitter为例看下发射器做了哪些工作

CircleEmitter.prototype.emit = function (particle) {
  //在0~2Π 随机产生一个角度
  var theta = CesiumMath.randomBetween(0.0, CesiumMath.TWO_PI);
  //在0~rad半径 随机产生一个数值
  var rad = CesiumMath.randomBetween(0.0, this._radius);

  var x = rad * Math.cos(theta);
  var y = rad * Math.sin(theta);
  var z = 0.0;
  //相对圆心的坐标
  particle.position = Cartesian3.fromElements(x, y, z, particle.position);
  //粒子的速度
  particle.velocity = Cartesian3.clone(Cartesian3.UNIT_Z, particle.velocity);
};

想emit方法传入一个粒子对象,然后圆形会把粒子的坐标范围限制在圆形内,并给定粒子一个默认的速度。其它粒子发射器类似,都是将粒子限定在一个包围范围之内。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容