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类
(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);
}
};
先上创建了一个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粒子类
(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是基类不能被直接实例化,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方法传入一个粒子对象,然后圆形会把粒子的坐标范围限制在圆形内,并给定粒子一个默认的速度。其它粒子发射器类似,都是将粒子限定在一个包围范围之内。