0
点赞
收藏
分享

微信扫一扫

Cesium粒子系统--Particle System FireWorks原理

微笑沉默 2021-09-21 阅读 103
随笔开源

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)

举报

相关推荐

0 条评论