Cesium 粒子系统简介

852 阅读8分钟

原文地址:Introduction to Particle Systems – Cesium

什么是粒子系统?

粒子系统是一种在计算机图形学中广泛应用的技术,用于模拟复杂的物理效果。它通过集合许多小的图像元素(称为粒子),在整体上形成更复杂的视觉效果,如火焰、烟雾、天气现象或烟花。这些复杂效果由每个粒子的初始位置、速度、寿命等属性来控制。

粒子效果在电影和视频游戏中非常常见。例如,可以使用粒子系统模拟飞机引擎的爆炸,或者在飞机坠毁时产生烟雾尾迹。

粒子系统基础

让我们来看一个基础粒子系统的代码示例:

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",
    imageSize: new Cesium.Cartesian2(20, 20),
    startScale: 1.0,
    endScale: 4.0,
    particleLife: 1.0,
    minimumSpeed: 5.0,
    maximumSpeed: 10.0,
    emitter: new Cesium.CircleEmitter(0.5),
    emissionRate: 5.0,
    modelMatrix: entity.computeModelMatrix(
      viewer.clock.startTime,
      new Cesium.Matrix4()
    ),
    lifetime: 16.0,
  })
);

上述代码创建了一个粒子系统(ParticleSystem)对象,通过参数控制每个粒子(Particle)在其生命周期中的外观和行为。粒子由粒子发射器(ParticleEmitter)生成,具有特定的位置和类型,并在一定时间后消失。

不知道你发现没有,上述属性的设置有不同的模式:

  1. 具体值:一些属性直接设置为特定的数值,例如 emissionRate,即粒子的发射速率。

  2. 动态属性:有些属性是动态变化的。例如,这里没有使用具体的缩放值scale,而是使用了startScaleendScale,这些属性允许您在粒子生命周期内指定粒子的大小从初始尺寸逐渐变化到最终尺寸。同样的原理也适用于startColorendColor

  3. 范围属性:某些属性具有最大值和最小值。对于这些属性,粒子的实际值将在最大值和最小值之间随机分配,并在整个生命周期内保持该值不变。例如,可以使用 minimumSpeedmaximumSpeed 来确定每个粒子的速度范围。其他允许这种随机变化的属性还包括 imageSizespeedlifeparticleLife

发射器

当粒子生成时,其初始位置和速度由 ParticleEmitter 控制。发射器每秒会生成一定数量的粒子,这个数量由 emissionRate 参数指定,粒子的初始速度根据发射器的类型随机确定。Cesium 提供了多种开箱即用的粒子发射器类型。

BoxEmitter

BoxEmitter 在一个立方体内随机选择位置来初始化粒子,并从立方体的各个面发射出去。它接受一个 Cartesian3 参数,用来指定盒子的宽度、高度和深度。

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",
    imageSize: new Cesium.Cartesian2(20, 20),
    startScale: 1.0,
    endScale: 4.0,
    particleLife: 1.0,
    speed: 5.0,
    emitter: new Cesium.BoxEmitter(new Cesium.Cartesian3(0.5, 0.5, 0.5)),
    emissionRate: 5.0,
    modelMatrix: entity.computeModelMatrix(
      viewer.clock.startTime,
      new Cesium.Matrix4()
    ),
    lifetime: 16.0,
  })
);

CircleEmitter

CircleEmitter 在一个圆形区域内随机选择位置来初始化粒子,并沿着发射器向上(z轴)发射。它接受一个浮点数参数,用于指定这个圆的半径。

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",
    color: Cesium.Color.MAGENTA,
    emissionRate: 5.0,
    emitter: new Cesium.CircleEmitter(5.0),
    imageSize: new Cesium.Cartesian2(25.0, 25.0),
    modelMatrix: entity.computeModelMatrix(
      viewer.clock.startTime,
      new Cesium.Matrix4()
    ),
    lifetime: 16.0,
  })
);

如果没有指定发射器,默认会使用 CircleEmitter

ConeEmitter

ConeEmitter 在锥体的顶端初始化粒子,并以随机角度从锥体中发射出去。它接受一个浮点参数来指定锥体的角度。

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",
    color: Cesium.Color.MAGENTA,
    emissionRate: 5.0,
    emitter: new Cesium.ConeEmitter(Cesium.Math.toRadians(30.0)),
    imageSize: new Cesium.Cartesian2(25.0, 25.0),
    modelMatrix: entity.computeModelMatrix(
      viewer.clock.startTime,
      new Cesium.Matrix4()
    ),
    lifetime: 16.0,
  })
);

SphereEmitter

SphereEmitter 在一个球体内随机初始化粒子,并从球体中心向外发射。它接受一个浮点参数来指定球体的半径。

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",
    color: Cesium.Color.MAGENTA,
    emissionRate: 5.0,
    emitter: new Cesium.SphereEmitter(5.0),
    imageSize: new Cesium.Cartesian2(25.0, 25.0),
    modelMatrix: entity.computeModelMatrix(
      viewer.clock.startTime,
      new Cesium.Matrix4()
    ),
    lifetime: 16.0,
  })
);

配置粒子系统

粒子发射率

emissionRate 控制每秒发射的粒子数量,从而影响粒子系统的密度。

可以指定一个 burst 数组,以在指定的时间发射大量粒子,实现爆炸效果。

bursts: [
  new Cesium.ParticleBurst({ time: 5.0, minimum: 300, maximum: 500 }),
  new Cesium.ParticleBurst({ time: 10.0, minimum: 50, maximum: 100 }),
  new Cesium.ParticleBurst({ time: 15.0, minimum: 200, maximum: 300 }),
]

这些 bursts 将在指定时间内发射出介于最小和最大值之间数量的粒子。

粒子系统的寿命

默认情况下,粒子系统会永久运行。如果需要限制粒子系统的持续时间,可以使用 lifetime 参数指定运行的秒数,并将 loop 设置为 false。

lifetime: 16.0,
loop: false,

通过 particleLife 控制每个粒子的寿命。将 particleLife 设置为 5.0 会使系统中的每个粒子具有相同的寿命值。如果想要每个粒子的寿命随机化,可以使用 minimumParticleLifemaximumParticleLife 变量。

minimumParticleLife: 5.0,
maximumParticleLife: 10.0

设置粒子样式

颜色

粒子通过指定的纹理(image)和颜色(color)来进行样式设置,可以在粒子的生命周期内改变这些属性,从而创造出动态效果。

以下代码使烟雾粒子从绿色逐渐变为白色并淡出。

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

大小

粒子的大小通过 imageSize 参数进行控制。要实现大小的随机化,可以使用 minimumImageSize.xmaximumImageSize.x 来控制宽度(单位为像素),同时使用 minimumImageSize.ymaximumImageSize.y 来控制高度(单位为像素)。

下述代码将创建 30 到 60 像素之间的方形粒子:

minimumImageSize: new Cesium.Cartesian2(30.0, 30.0),
maximumImageSize: new Cesium.Cartesian2(60.0, 60.0),

粒子的大小可以通过 startScaleendScale 属性在其生命周期内进行调节,使其随时间扩大或缩小。

startScale: 1.0,
endScale: 4.0,

速度

速度可以通过 speed 属性或 minimumSpeedmaximumSpeed 属性来控制。

minimumSpeed: 5.0,
maximumSpeed: 10.0,

更新回调

借助应用更新函数,能够进一步对粒子系统进行定制。此类更新函数允许手动处置每个粒子的属性,达成诸如重力、风或者颜色变化之类的效果。

粒子系统提供了一个名为 updateCallback 参数,用于在模拟过程中修改粒子的属性。这个函数接受两个参数:一个粒子对象和模拟的时间步长。大多数基于物理效果的更新函数会对粒子的速度向量予以修改,以此变更其方向或者速度。以下是一个使粒子对重力做出反应的示例:

const gravityVector = new Cesium.Cartesian3();
const gravity = -(9.8 * 9.8);
function applyGravity(p, dt) {
  // 计算每个粒子在地心空间中的局部向上向量。
  const position = p.position;
  Cesium.Cartesian3.normalize(position, gravityVector);
  Cesium.Cartesian3.multiplyByScalar(
    gravityVector,
    gravity * dt,
    gravityVector
  );
  p.velocity = Cesium.Cartesian3.add(p.velocity, gravityVector, p.velocity);
}

这个函数计算了一个重力向量,并使用重力加速度来改变粒子的速度。将这个函数赋值给粒子系统的 updateCallback

updateCallback: applyGravity

定位

粒子系统使用两种 Matrix4 变换矩阵来进行定位:

  • modelMatrix:这是一个变换矩阵,用于将粒子系统从模型的局部坐标系转换到世界坐标系。简单来说,这个矩阵定义了粒子系统在整个场景中的位置和方向。通过设置这个矩阵,你可以控制粒子系统在世界中的放置位置以及它的朝向。
  • emitterModelMatrix:这是另一个变换矩阵,用于在粒子系统的局部坐标系中定位和定向粒子发射器。也就是说,这个矩阵定义了粒子发射器在粒子系统内部的位置和方向。通过设置这个矩阵,你可以精确控制粒子发射器在粒子系统内部的相对位置和方向。

这两个矩阵的协同使用使你能够非常灵活地控制粒子系统的位置和发射方向。例如,你可以将粒子系统放置在一个移动的实体(如卡车)上,并在实体的局部坐标系中调整发射器的位置,以实现从特定位置(如卡车后部)发射粒子的效果。当然,您也可以仅使用其中的一个变换矩阵,而将另一个保留为单位矩阵。

接下来,我们将借助一个示例来展示如何运用这两个变换矩阵。首先,需要为我们的粒子系统添加一个实体。打开 Hello World Sandcastle 示例,并添加以下代码,将一个送牛奶的卡车模型添加到 viewer 中:

const entity = viewer.entities.add({
  model: {
    uri: "../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
  },
  position: Cesium.Cartesian3.fromDegrees(
    -75.15787310614596,
    39.97862668312678
  ),
});
viewer.trackedEntity = entity;

我们希望在卡车后部添加一个烟雾效果。首先创建一个模型矩阵,将粒子系统定位和定向为与牛奶卡车实体一致:

const time = viewer.clock.startTime;
const modelMatrix = entity.computeModelMatrix(time, new Cesium.Matrix4());

这会将粒子系统放置在卡车的中心。为了将其定位到卡车后部,可以使用一个平移矩阵进行调整:

function computeEmitterModelMatrix() {
  const translation = Cesium.Cartesian3.fromElements(-4.0, 0.0, 1.4);
  const rotation = Cesium.Quaternion.IDENTITY;
  const scale = new Cesium.Cartesian3(1.0, 1.0, 1.0);
  
  return Cesium.Matrix4.fromTranslationRotationScale(
    new Cesium.TranslationRotationScale(translation, rotation, scale),
    new Cesium.Matrix4()
  );
}

现在,我们可以创建粒子系统并将其添加到场景中:

const particleSystem = viewer.scene.primitives.add(
  new Cesium.ParticleSystem({
    image: "../../SampleData/smoke.png",
    startColor: Cesium.Color.LIGHTSEAGREEN.withAlpha(0.7),
    endColor: Cesium.Color.WHITE.withAlpha(0.0),
    startScale: 1.0,
    endScale: 4.0,
    particleLife: 1.0,
    minimumSpeed: 1.0,
    maximumSpeed: 4.0,
    imageSize: new Cesium.Cartesian2(25, 25),
    emissionRate: 5.0,
    lifetime: 16.0,
    modelMatrix: entity.computeModelMatrix(
      viewer.clock.startTime,
      new Cesium.Matrix4()
    ),
    emitterModelMatrix: computeEmitterModelMatrix(),
  })
);

另外需要注意的是,我们可以随着时间的推移更新模型或发射器矩阵。例如,如果我们希望在卡车上对发射器位置进行动画处理,可以修改 emitterModelMatrix,而保持 modelMatrix 不变。

查看完整的代码示例:Particle System demo

了解更多

想要了解更多示例代码,请参见: