Skip to content
Cesium实现粒子效果

需求

需要实现粒子爆炸/气体泄漏的效果

图片直接用cesium自带的smoke那个透明的粒子图片即可

sizeInMeters这个参数控制是否随米而变化

代码

javascript
const viewer = new Cesium.Viewer("cesiumContainer");

// 设置时间范围
const start = Cesium.JulianDate.fromDate(new Date());
const stop = Cesium.JulianDate.addSeconds(start, 30, new Cesium.JulianDate());

viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;

// 粒子系统视图模型
const viewModel = {
  emissionRate: 0, // 初始不发射粒子
  minimumParticleLife: 1.0,
  maximumParticleLife: 2.0,
  minimumSpeed: 5.0,
  maximumSpeed: 10.0,
  startScale: 1.0,
  endScale: 5.0,
  particleSize: 20.0,
};

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../SampleData/smoke.png", // 替换为你自己的粒子图片
    startColor: Cesium.Color.YELLOW.withAlpha(0.7),
    endColor: Cesium.Color.RED.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: 20,
    bursts: [
      new Cesium.ParticleBurst({
        time: 0.0, // 粒子爆炸立即发生
        minimum: 100,
        maximum: 200,
      }),
    ],
    lifetime: 5.0, // 粒子系统的生存时间
    emitter: new Cesium.SphereEmitter(2.0), // 粒子爆炸范围
    emitterModelMatrix: Cesium.Matrix4.fromTranslation(Cesium.Cartesian3.fromDegrees(114.25, 30.5, 500)), // 定点爆炸的位置
  })
);

![[Pasted image 20241019141500.png]]

调试

如果要在Cesium沙盒调试请用以下代码

javascript
const viewer = new Cesium.Viewer("cesiumContainer");

//Set the random number seed for consistent results.
Cesium.Math.setRandomNumberSeed(3);

//Set bounds of our simulation time
const start = Cesium.JulianDate.fromDate(new Date(2015, 2, 25, 16));
const stop = Cesium.JulianDate.addSeconds(start, 120, new Cesium.JulianDate());

//Make sure viewer is at the desired time.
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; //Loop at the end
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;

//Set timeline to simulation bounds
viewer.timeline.zoomTo(start, stop);

const viewModel = {
  emissionRate: 5.0,
  gravity: 0.0,
  minimumParticleLife: 1.2,
  maximumParticleLife: 1.2,
  minimumSpeed: 1.0,
  maximumSpeed: 4.0,
  startScale: 1.0,
  endScale: 5.0,
  particleSize: 25.0,
};

Cesium.knockout.track(viewModel);
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(viewModel, toolbar);

const entityPosition = new Cesium.Cartesian3();
const entityOrientation = new Cesium.Quaternion();
const rotationMatrix = new Cesium.Matrix3();
const modelMatrix = new Cesium.Matrix4();

function computeModelMatrix(entity, time) {
  return entity.computeModelMatrix(time, new Cesium.Matrix4());
}

const emitterModelMatrix = new Cesium.Matrix4();
const translation = new Cesium.Cartesian3();
const rotation = new Cesium.Quaternion();
let hpr = new Cesium.HeadingPitchRoll();
const trs = new Cesium.TranslationRotationScale();

const position2 = Cesium.Cartesian3.fromDegrees(94.875771, 43.596378, 10);

function computeEmitterModelMatrix() {
  return Cesium.Matrix4.fromTranslation(position2);
}


const entity = viewer.entities.add({
  position: position2,
  point: {
    pixelSize: 10, // 点的大小
    color: Cesium.Color.RED, // 红色
    outlineColor: Cesium.Color.BLACK, // 边框颜色
    outlineWidth: 2 // 边框宽度
  }
});
viewer.trackedEntity = entity;

const scene = viewer.scene;
const particleSystem = scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../SampleData/fire.png",

    startColor: Cesium.Color.YELLOW.withAlpha(0.7),
    endColor: Cesium.Color.RED.withAlpha(0.3),

    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: Cesium.Matrix4.fromTranslation(position2),

   // updateCallback: applyGravity,
  }),
);

const gravityScratch = new Cesium.Cartesian3();

function applyGravity(p, dt) {
  // We need to compute a local up vector for each particle in geocentric space.
  const position = p.position;

  Cesium.Cartesian3.normalize(position, gravityScratch);
  Cesium.Cartesian3.multiplyByScalar(
    gravityScratch,
    viewModel.gravity * dt,
    gravityScratch,
  );

  p.velocity = Cesium.Cartesian3.add(p.velocity, gravityScratch, p.velocity);
}

Cesium.knockout
  .getObservable(viewModel, "emissionRate")
  .subscribe(function (newValue) {
    particleSystem.emissionRate = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "particleSize")
  .subscribe(function (newValue) {
    const particleSize = parseFloat(newValue);
    particleSystem.minimumImageSize.x = particleSize;
    particleSystem.minimumImageSize.y = particleSize;
    particleSystem.maximumImageSize.x = particleSize;
    particleSystem.maximumImageSize.y = particleSize;
  });

Cesium.knockout
  .getObservable(viewModel, "minimumParticleLife")
  .subscribe(function (newValue) {
    particleSystem.minimumParticleLife = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "maximumParticleLife")
  .subscribe(function (newValue) {
    particleSystem.maximumParticleLife = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "minimumSpeed")
  .subscribe(function (newValue) {
    particleSystem.minimumSpeed = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "maximumSpeed")
  .subscribe(function (newValue) {
    particleSystem.maximumSpeed = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "startScale")
  .subscribe(function (newValue) {
    particleSystem.startScale = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "endScale")
  .subscribe(function (newValue) {
    particleSystem.endScale = parseFloat(newValue);
  });

const options = [
  {
    text: "Circle Emitter",
    onselect: function () {
      particleSystem.emitter = new Cesium.CircleEmitter(2.0);
    },
  },
  {
    text: "Sphere Emitter",
    onselect: function () {
      particleSystem.emitter = new Cesium.SphereEmitter(2.5);
    },
  },
  {
    text: "Cone Emitter",
    onselect: function () {
      particleSystem.emitter = new Cesium.ConeEmitter(
        Cesium.Math.toRadians(45.0),
      );
    },
  },
  {
    text: "Box Emitter",
    onselect: function () {
      particleSystem.emitter = new Cesium.BoxEmitter(
        new Cesium.Cartesian3(10.0, 10.0, 10.0),
      );
    },
  },
];

Sandcastle.addToolbarMenu(options);

Updated at: