Cesium为我们提供了非常棒的烟花的粒子特效,我们可以在沙盒中的Particle System FireWorks.html中看到

考虑到篇幅问题,我会把自己认为比较关键的代码拿出来,进行分析

首先我们放重头戏,我们看看粒子系统是如何放入场景中的

scene.primitives.add(
    new Cesium.ParticleSystem({
          image: getImage(),
          startColor: color,
          endColor: color.withAlpha(0.0),
          particleLife: life,
          speed: 100.0,
          imageSize: particlePixelSize,
          emissionRate: 0,
          emitter: new Cesium.SphereEmitter(0.1),
          bursts: bursts,
          lifetime: lifetime,
          updateCallback: force,
          modelMatrix: modelMatrix,
          emitterModelMatrix: emitterModelMatrix,
    })
);

我们可以看到,粒子发射器的发射速率为0个/s(即不发射粒子),而烟花效果依靠的是bursts,使其在一定的时间间隔中,瞬间释放大量粒子

我们看看getImage的过程:

这里使用canvas2D生成一张20*20像素的图片,图片中绘制了一个半径为8的圆

function getImage() {
    if (!Cesium.defined(particleCanvas)) {
        particleCanvas = document.createElement("canvas");
        particleCanvas.width = 20;
        particleCanvas.height = 20;
        var context2D = particleCanvas.getContext("2d");
        context2D.beginPath();
        context2D.arc(8, 8, 8, 0, Cesium.Math.TWO_PI, true);
        context2D.closePath();
        context2D.fillStyle = "rgb(255, 255, 255)";
        context2D.fill();
    }
    return particleCanvas;
}

如果你认真一些,你会发现这个圆其实并不是在20*20的图片的中心位置

颜色和偏移量、爆发时间间隔

color和粒子发射器的位置offset粒子爆发间隔burst的time都是系统根据参数在一定范围内生成的随机数

var colorOptions = [
      {
            minimumRed: 0.75,
            green: 0.0,
            minimumBlue: 0.8,
            alpha: 1.0,
      },
      {
            red: 0.0,
            minimumGreen: 0.75,
            minimumBlue: 0.8,
            alpha: 1.0,
      },
      {
            red: 0.0,
            green: 0.0,
            minimumBlue: 0.8,
            alpha: 1.0,
      },
      {
            minimumRed: 0.75,
            minimumGreen: 0.75,
            blue: 0.0,
            alpha: 1.0,
      },
];

for (var i = 0; i < numberOfFireworks; ++i) {
    var x = Cesium.Math.randomBetween(xMin, xMax);
    var y = Cesium.Math.randomBetween(yMin, yMax);
    var z = Cesium.Math.randomBetween(zMin, zMax);
    var offset = new Cesium.Cartesian3(x, y, z);
    var color = Cesium.Color.fromRandom(
        colorOptions[i % colorOptions.length]
    );

    var bursts = [];
    for (var j = 0; j < 3; ++j) {
        bursts.push(
            new Cesium.ParticleBurst({
                time: Cesium.Math.nextRandomNumber() * lifetime,
                minimum: burstSize,
                maximum: burstSize,
            })
        );
    }

    createFirework(offset, color, bursts);
}

而offset则用来计算出粒子的emitterModelMatrix,我们使用emitterModelMatrix用来表示粒子发射区域相对于modelMatrix的偏移量

这里通过在不同的位置,分别创建粒子系统,使得一个粒子系统负责一处位置、一种颜色的粒子发射效果

核心功能createFirework

function createFirework(offset, color, bursts) {
	var position = Cesium.Cartesian3.add(emitterInitialLocation, offset, new Cesium.Cartesian3());
	var emitterModelMatrix = Cesium.Matrix4.fromTranslation(position, emitterModelMatrixScratch);
	var particleToWorld = Cesium.Matrix4.multiply(modelMatrix, emitterModelMatrix, new Cesium.Matrix4());
	var worldToParticle = Cesium.Matrix4.inverseTransformation(particleToWorld, particleToWorld);

	var size = Cesium.Math.randomBetween(minimumExplosionSize,maximumExplosionSize);
	var particlePositionScratch = new Cesium.Cartesian3();
	var force = function (particle) {
		var position = Cesium.Matrix4.multiplyByPoint(
			worldToParticle,
			particle.position,
			particlePositionScratch
		);
		if (Cesium.Cartesian3.magnitudeSquared(position) >= size * size) {
			Cesium.Cartesian3.clone(
			Cesium.Cartesian3.ZERO,
			particle.velocity
			);
		}
	};

	var normalSize = (size - minimumExplosionSize) / (maximumExplosionSize - minimumExplosionSize);
	var minLife = 0.3;
	var maxLife = 1.0;
	var life = normalSize * (maxLife - minLife) + minLife;

	scene.primitives.add(
		new Cesium.ParticleSystem({
			image: getImage(),
			startColor: color,
			endColor: color.withAlpha(0.0),
			particleLife: life,
			speed: 100.0,
			imageSize: particlePixelSize,
			emissionRate: 0,
			emitter: new Cesium.SphereEmitter(0.1),
			bursts: bursts,
			lifetime: lifetime,
			updateCallback: force,
			modelMatrix: modelMatrix,
			emitterModelMatrix: emitterModelMatrix,
		})
	);
}

我们先看上面代码中的回调函数force,我们认真看就会发现,这个回调函数force其实非常的鸡肋,它通过计算发射出的粒子距离粒子发射器中心的距离,如果距离大于size,则让粒子的速度变成0,使其不再移动

particleToWorld作为粒子相对于世界坐标系的位置,而worldToParticle则是上一个矩阵的逆。因为每个粒子的位置都是世界坐标系下的位置,因此需要使用worldToParticle乘以这个粒子的位置,从而得到粒子相对于粒子发射器的位置。

可以看到这里根据offset计算出emitterModelMatrix,得到每个粒子系统最后的位置,而size则是粒子最大发射的距离(在数学上,因为有负数的存在,所以我们使用两个值的平方进行距离的比较)

因为初次写博客,文笔生涩,还望大家见谅,同时欢迎大家一起讨论学习。

如果觉得粒子系统的使用还是有难度,那么建议大家使用earthUI,在其中完全可以通过操作UI得到想要的效果(教程在bilibili)

posted on 2020-05-31 21:52  Hannes·Wang  阅读(354)  评论(0编辑  收藏  举报