粒子系统的粒子属性配置
一、ParticleSystem简介
在Cesium中,粒子系统ParticleSystem是一种用于模拟和呈现大量小粒子效果的功能。这些小粒子可以代表火花、烟雾、雨滴等效果,用于增强Cesium中的三维场景的视觉效果。Cesium的粒子系统允许用户创建、配置和控制这些粒子的外观和行为。
Cesium的粒子系统提供了许多配置选项,通过指定粒子的纹理、颜色、生命周期、速度等属性,结合粒子系统的粒子回调函数,以满足不同的粒子效果的需求。
二、ParticleSystem配置项
属性名 | 描述 |
---|---|
show | 是否显示粒子系统。 |
updateCallback | 每帧调用回调函数来更新粒子。 |
emitter | 该系统的粒子发射器。 |
modelMatrix | 将粒子系统从模型变换到世界坐标的 4x4 变换矩阵。 |
emitterModelMatrix | 4x4 变换矩阵,用于在粒子系统局部坐标系内变换粒子系统发射器。 |
emissionRate | 每秒发射的粒子数。 |
bursts | 一系列的ParticleBurst ,周期性地发射粒子爆发。 |
loop | 粒子系统在完成时是否应循环其爆发。 |
scale | 设置在粒子生命周期内应用于粒子图像的比例。 |
startScale | 应用于粒子生命初期图像的初始比例。 |
endScale | 应用到粒子寿命结束时的图像的最终比例。 |
color | 设置粒子在其粒子生命周期内的颜色。 |
startColor | 粒子在其生命开始时的颜色。 |
endColor | 粒子在其生命结束时的颜色。 |
image | 用于广告牌的 URI、HTMLImageElement 或 HTMLCanvasElement。 |
imageSize | 如果设置,则覆盖minimumImageSize和maximumImageSize输入,以像素为单位缩放粒子图像的尺寸。 |
minimumImageSize | 设置最小边界(宽度乘高度),高于该边界可随机缩放粒子图像的尺寸(以像素为单位)。 |
maximumImageSize | 设置最大边界(宽度乘高度),低于该边界可随机缩放粒子图像的尺寸(以像素为单位)。 |
sizeInMeters | 设置粒子的大小是以米还是像素为单位。true 以米为单位确定颗粒大小;否则,大小以像素为单位。 |
speed | 如果设置,则用该值覆盖最小速度和最大速度输入。 |
minimumSpeed | 设置以米每秒为单位的最小界限,高于该界限将随机选择粒子的实际速度。 |
maximumSpeed | 设置以米每秒为单位的最大界限,低于该界限将随机选择粒子的实际速度。 |
lifetime | 粒子系统发射粒子的时间长度(以秒为单位)。 |
particleLife | 如果设置,则使用该值覆盖minimumParticleLife和maximumParticleLife输入。 |
minimumParticleLife | 设置粒子生命可能持续时间的最小界限(以秒为单位),超过该界限将随机选择粒子的实际生命。 |
maximumParticleLife | 设置粒子生命可能持续时间的最大界限(以秒为单位),低于该界限将随机选择粒子的实际生命。 |
mass | 设置粒子的最小和最大质量(以千克为单位)。 |
minimumMass | 设置粒子质量的最小界限(以千克为单位)。粒子的实际质量将被选择为高于该值的随机量。 |
maximumMass | 设置粒子的最大质量(以千克为单位)。粒子的实际质量将被选择为低于该值的随机量。 |
三、案例代码
结合Vue 3 + TS + Cesium1.113实现粒子效果控制面板。
需要替换Ceisum的token。
Cesium.Ion.defaultAccessToken ="your token";
1、项目初始化
完成项目的地球底图初始化:参照Cesium入门基础一:cesium加载地球与环境搭建-CSDN博客
2、控制面板组件
利用v-model
实现数据的双向绑定,数据改变时触发@change
绑定的数据更新函数changeParticleSystem
。
<template>
<div id="viewContainer"></div>
<div id="toolbar" class="toolbar">
<table>
<tbody>
<tr>
<td>emissionRate:粒子数目</td>
<td>
<input
type="range"
min="0.0"
max="100.0"
step="1"
v-model="emissionRate"
@change="changeParticleSystem('emissionRate')"
/>
<input
type="text"
size="5"
v-model="emissionRate"
@change="changeParticleSystem('emissionRate')"
/>
</td>
</tr>
<tr>
<td>particleSize:粒子尺寸</td>
<td>
<input
type="range"
min="2"
max="60.0"
step="1"
v-model="particleSize"
@change="changeParticleSystem('particleSize')"
/>
<input
type="text"
size="5"
v-model="particleSize"
@change="changeParticleSystem('particleSize')"
/>
</td>
</tr>
<tr>
<td>minimumParticleLife:最小生命界限</td>
<td>
<input
type="range"
min="0.1"
max="30.0"
step="1"
v-model="minimumParticleLife"
@change="changeParticleSystem('minimumParticleLife')"
/>
<input
type="text"
size="5"
v-model="minimumParticleLife"
@change="changeParticleSystem('minimumParticleLife')"
/>
</td>
</tr>
<tr>
<td>maximumParticleLife:最大生命界限</td>
<td>
<input
type="range"
min="0.1"
max="30.0"
step="1"
v-model="maximumParticleLife"
@change="changeParticleSystem('maximumParticleLife')"
/>
<input
type="text"
size="5"
v-model="maximumParticleLife"
@change="changeParticleSystem('maximumParticleLife')"
/>
</td>
</tr>
<tr>
<td>minimumSpeed:最小速度</td>
<td>
<input
type="range"
min="0.0"
max="30.0"
step="1"
v-model="minimumSpeed"
@change="changeParticleSystem('minimumSpeed')"
/>
<input
type="text"
size="5"
v-model="minimumSpeed"
@change="changeParticleSystem('minimumSpeed')"
/>
</td>
</tr>
<tr>
<td>maximumSpeed:最大速度</td>
<td>
<input
type="range"
min="0.0"
max="30.0"
step="1"
v-model="maximumSpeed"
@change="changeParticleSystem('maximumSpeed')"
/>
<input
type="text"
size="5"
v-model="maximumSpeed"
@change="changeParticleSystem('maximumSpeed')"
/>
</td>
</tr>
<tr>
<td>startScale:初始比例</td>
<td>
<input
type="range"
min="0.0"
max="10.0"
step="1"
v-model="startScale"
@change="changeParticleSystem('startScale')"
/>
<input
type="text"
size="5"
v-model="startScale"
@change="changeParticleSystem('startScale')"
/>
</td>
</tr>
<tr>
<td>endScale:最终比例</td>
<td>
<input
type="range"
min="0.0"
max="10.0"
step="1"
v-model="endScale"
@change="changeParticleSystem('endScale')"
/>
<input
type="text"
size="5"
v-model="endScale"
@change="changeParticleSystem('endScale')"
/>
</td>
</tr>
<tr>
<td>gravity:重力</td>
<td>
<input
type="range"
min="-20.0"
max="20.0"
step="1"
v-model="gravity"
@change="changeParticleSystem('gravity')"
/>
<input
type="text"
size="5"
v-model="gravity"
@change="changeParticleSystem('gravity')"
/>
</td>
</tr>
</tbody>
</table>
</div>
</template>
//监听面板数值变化,更改粒子系统属性
const changeParticleSystem = (particleProperty: string) => {
switch (particleProperty) {
case "emissionRate":
particleSystem.emissionRate = +emissionRate.value;
break;
case "particleSize":
particleSystem.minimumImageSize.x = +particleSize.value;
particleSystem.minimumImageSize.y = +particleSize.value;
particleSystem.maximumImageSize.x = +particleSize.value;
particleSystem.maximumImageSize.y = +particleSize.value;
break;
case "minimumParticleLife":
particleSystem.minimumParticleLife = +minimumParticleLife.value;
break;
case "maximumParticleLife":
particleSystem.maximumParticleLife = +maximumParticleLife.value;
break;
case "minimumSpeed":
particleSystem.minimumSpeed = +minimumSpeed.value;
break;
case "maximumSpeed":
particleSystem.maximumSpeed = +maximumSpeed.value;
break;
case "startScale":
particleSystem.startScale = +startScale.value;
break;
case "endScale":
particleSystem.endScale = +endScale.value;
break;
case "gravity":
gravity.value = +gravity.value;
break;
}
};
3、飞机飞行轨迹
利用SampledPositionProperty
(采样位置属性)结合时间动画,实现飞机沿指定轨迹运行,并设置视角追踪为飞机模型。
let entityAir: Cesium.Entity; //飞机模型实体
const startTime = Cesium.JulianDate.fromDate(new Date()); //动画开始时间
//动画结束时间
const stopTime = Cesium.JulianDate.addSeconds(
startTime,
120,
new Cesium.JulianDate()
);
//添加飞机模型
const addEntity = () => {
const positionAir = new Cesium.SampledPositionProperty();
const positionStart = Cesium.Cartesian3.fromDegrees(
-75.15787310614596,
39.97862668312678
);
const positionStop = Cesium.Cartesian3.fromDegrees(
-75.1633691390455,
39.95355089912078
);
positionAir.addSample(startTime, positionStart);
positionAir.addSample(stopTime, positionStop);
entityAir = viewer.entities.add({
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: startTime,
stop: stopTime,
}),
]),
model: {
uri: "/Cesium_Air.glb",
minimumPixelSize: 64,
},
position: positionAir,
orientation: new Cesium.VelocityOrientationProperty(positionAir),//设置飞机速度方向
});
viewer.trackedEntity = entityAir;//设置视角追踪
//设置时间轴
viewer.clock.startTime = startTime.clone();
viewer.clock.stopTime = stopTime.clone();
viewer.clock.currentTime = startTime.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;
viewer.timeline.zoomTo(startTime, stopTime);
};
4、创建粒子系统
//计算模型矩阵
const computeModelMatrix = (
entity: { computeModelMatrix: (arg0: any, arg1: Cesium.Matrix4) => any },
time: any
) => {
return entity.computeModelMatrix(time, new Cesium.Matrix4());
};
let particleSystem: Cesium.ParticleSystem;//粒子系统
const addParticleSystem = () => {
particleSystem = scene.primitives.add(
new Cesium.ParticleSystem({
image: "/smoke.png",
startColor: Cesium.Color.BLACK.withAlpha(0.7),
endColor: Cesium.Color.WHITE.withAlpha(0),
startScale: startScale.value,
endScale: endScale.value,
minimumParticleLife: minimumParticleLife.value,
maximumParticleLife: maximumParticleLife.value,
minimumSpeed: minimumSpeed.value,
maximumSpeed: maximumSpeed.value,
imageSize: new Cesium.Cartesian2(particleSize.value, particleSize.value),
emissionRate: emissionRate.value,
bursts: [
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,
})
);
};
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 computeEmitterModelMatrix = () => {
hpr = Cesium.HeadingPitchRoll.fromDegrees(0.0, 0, 0.0, hpr);
trs.translation = Cesium.Cartesian3.fromElements(-15.0, 0.0, 0, translation);
trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr, rotation);
return Cesium.Matrix4.fromTranslationRotationScale(trs, emitterModelMatrix);
};
const gravityScratch = new Cesium.Cartesian3();
//粒子系统的更新的回调函数
const applyGravity = (
p: { position: any; velocity: Cesium.Cartesian3 },
dt: number
) => {
const position = p.position;
//计算粒子位置的归一化向量
Cesium.Cartesian3.normalize(position, gravityScratch);
//归一化向量乘以重力标量
Cesium.Cartesian3.multiplyByScalar(
gravityScratch,
gravity.value * -dt,
gravityScratch
);
//向量和
p.position = Cesium.Cartesian3.add(p.position, gravityScratch, p.position);
};
注意:
整个创建粒子系统的运算分为两个坐标系,一个是粒子生成时用的本地坐标系A,一个是地球所在的坐标系B,在创建粒子系统的时候,可以直接在坐标系B中直接进行计算,但是过于麻烦,所以我们引入一个本地坐标系A,例如:一个标准的笛卡尔直角坐标系,去计算每个粒子,然后通过转换矩阵X,把这个坐标系A转换到坐标系B中,这个转换矩阵X就是
modelMatrix
。在坐标系A中,将粒子发射器本身作为一个局部坐标系C,然后围绕这个坐标系C生成粒子,可以通过一个转换矩阵Y,将坐标系C转换到坐标系A,例如:绕坐标系A的Y轴进行旋转。这个转换矩阵Y就是
emitterModelMatrix
完整代码:
<template>
<div id="viewContainer"></div>
<div id="toolbar" class="toolbar">
<table>
<tbody>
<tr>
<td>emissionRate:粒子数目</td>
<td>
<input
type="range"
min="0.0"
max="100.0"
step="1"
v-model="emissionRate"
@change="changeParticleSystem('emissionRate')"
/>
<input
type="text"
size="5"
v-model="emissionRate"
@change="changeParticleSystem('emissionRate')"
/>
</td>
</tr>
<tr>
<td>particleSize:粒子尺寸</td>
<td>
<input
type="range"
min="2"
max="60.0"
step="1"
v-model="particleSize"
@change="changeParticleSystem('particleSize')"
/>
<input
type="text"
size="5"
v-model="particleSize"
@change="changeParticleSystem('particleSize')"
/>
</td>
</tr>
<tr>
<td>minimumParticleLife:最小生命界限</td>
<td>
<input
type="range"
min="0.1"
max="30.0"
step="1"
v-model="minimumParticleLife"
@change="changeParticleSystem('minimumParticleLife')"
/>
<input
type="text"
size="5"
v-model="minimumParticleLife"
@change="changeParticleSystem('minimumParticleLife')"
/>
</td>
</tr>
<tr>
<td>maximumParticleLife:最大生命界限</td>
<td>
<input
type="range"
min="0.1"
max="30.0"
step="1"
v-model="maximumParticleLife"
@change="changeParticleSystem('maximumParticleLife')"
/>
<input
type="text"
size="5"
v-model="maximumParticleLife"
@change="changeParticleSystem('maximumParticleLife')"
/>
</td>
</tr>
<tr>
<td>minimumSpeed:最小速度</td>
<td>
<input
type="range"
min="0.0"
max="30.0"
step="1"
v-model="minimumSpeed"
@change="changeParticleSystem('minimumSpeed')"
/>
<input
type="text"
size="5"
v-model="minimumSpeed"
@change="changeParticleSystem('minimumSpeed')"
/>
</td>
</tr>
<tr>
<td>maximumSpeed:最大速度</td>
<td>
<input
type="range"
min="0.0"
max="30.0"
step="1"
v-model="maximumSpeed"
@change="changeParticleSystem('maximumSpeed')"
/>
<input
type="text"
size="5"
v-model="maximumSpeed"
@change="changeParticleSystem('maximumSpeed')"
/>
</td>
</tr>
<tr>
<td>startScale:初始比例</td>
<td>
<input
type="range"
min="0.0"
max="10.0"
step="1"
v-model="startScale"
@change="changeParticleSystem('startScale')"
/>
<input
type="text"
size="5"
v-model="startScale"
@change="changeParticleSystem('startScale')"
/>
</td>
</tr>
<tr>
<td>endScale:最终比例</td>
<td>
<input
type="range"
min="0.0"
max="10.0"
step="1"
v-model="endScale"
@change="changeParticleSystem('endScale')"
/>
<input
type="text"
size="5"
v-model="endScale"
@change="changeParticleSystem('endScale')"
/>
</td>
</tr>
<tr>
<td>gravity:重力</td>
<td>
<input
type="range"
min="-20.0"
max="20.0"
step="1"
v-model="gravity"
@change="changeParticleSystem('gravity')"
/>
<input
type="text"
size="5"
v-model="gravity"
@change="changeParticleSystem('gravity')"
/>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import * as Cesium from "cesium";
import { onMounted, ref } from "vue";
Cesium.Ion.defaultAccessToken ="your token";
let viewer: Cesium.Viewer;
let scene: Cesium.Scene;
//粒子系统属性
const emissionRate = ref(5);
const particleSize = ref(25);
const minimumParticleLife = ref(1.2);
const maximumParticleLife = ref(1.2);
const minimumSpeed = ref(1);
const maximumSpeed = ref(4);
const startScale = ref(1);
const endScale = ref(5);
const gravity = ref(0);
let entityAir: Cesium.Entity; //飞机模型实体
const startTime = Cesium.JulianDate.fromDate(new Date()); //动画开始时间
//动画结束时间
const stopTime = Cesium.JulianDate.addSeconds(
startTime,
120,
new Cesium.JulianDate()
);
// 初始化地图
const initMap = async () => {
viewer = new Cesium.Viewer("viewContainer", {
infoBox: false,
baseLayerPicker: false, //右上角图层选择按钮
geocoder: false, //搜索框
homeButton: false, //home按钮
sceneModePicker: true, //模式切换按钮
navigationHelpButton: false, //右上角帮助按钮
fullscreenButton: false, //右下角全屏按钮
terrainProvider: await Cesium.createWorldTerrainAsync(),
});
scene = viewer.scene;
addEntity();
addParticleSystem();
scene.preUpdate.addEventListener((_scene, time) => {
particleSystem.modelMatrix = computeModelMatrix(entityAir, time);
particleSystem.emitterModelMatrix = computeEmitterModelMatrix();
});
};
//添加飞机模型
const addEntity = () => {
const positionAir = new Cesium.SampledPositionProperty();
const positionStart = Cesium.Cartesian3.fromDegrees(
-75.15787310614596,
39.97862668312678
);
const positionStop = Cesium.Cartesian3.fromDegrees(
-75.1633691390455,
39.95355089912078
);
positionAir.addSample(startTime, positionStart);
positionAir.addSample(stopTime, positionStop);
entityAir = viewer.entities.add({
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: startTime,
stop: stopTime,
}),
]),
model: {
uri: "/Cesium_Air.glb",
minimumPixelSize: 64,
},
position: positionAir,
orientation: new Cesium.VelocityOrientationProperty(positionAir),//设置飞机速度方向
});
viewer.trackedEntity = entityAir;//设置视角追踪
//设置时间轴
viewer.clock.startTime = startTime.clone();
viewer.clock.stopTime = stopTime.clone();
viewer.clock.currentTime = startTime.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;
viewer.timeline.zoomTo(startTime, stopTime);
};
//计算模型矩阵
const computeModelMatrix = (
entity: { computeModelMatrix: (arg0: any, arg1: Cesium.Matrix4) => any },
time: any
) => {
return entity.computeModelMatrix(time, new Cesium.Matrix4());
};
let particleSystem: Cesium.ParticleSystem;//粒子系统
const addParticleSystem = () => {
particleSystem = scene.primitives.add(
new Cesium.ParticleSystem({
image: "/smoke.png",
startColor: Cesium.Color.BLACK.withAlpha(0.7),
endColor: Cesium.Color.WHITE.withAlpha(0),
startScale: startScale.value,
endScale: endScale.value,
minimumParticleLife: minimumParticleLife.value,
maximumParticleLife: maximumParticleLife.value,
minimumSpeed: minimumSpeed.value,
maximumSpeed: maximumSpeed.value,
imageSize: new Cesium.Cartesian2(particleSize.value, particleSize.value),
emissionRate: emissionRate.value,
bursts: [
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,
})
);
};
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 computeEmitterModelMatrix = () => {
hpr = Cesium.HeadingPitchRoll.fromDegrees(0.0, 0, 0.0, hpr);
trs.translation = Cesium.Cartesian3.fromElements(-15.0, 0.0, 0, translation);
trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr, rotation);
return Cesium.Matrix4.fromTranslationRotationScale(trs, emitterModelMatrix);
};
const gravityScratch = new Cesium.Cartesian3();
//粒子系统的更新的回调函数
const applyGravity = (
p: { position: any; velocity: Cesium.Cartesian3 },
dt: number
) => {
const position = p.position;
//计算粒子位置的归一化向量
Cesium.Cartesian3.normalize(position, gravityScratch);
//归一化向量乘以重力标量
Cesium.Cartesian3.multiplyByScalar(
gravityScratch,
gravity.value * -dt,
gravityScratch
);
//向量和
p.position = Cesium.Cartesian3.add(p.position, gravityScratch, p.position);
};
//监听面板数值变化,更改粒子系统属性
const changeParticleSystem = (particleProperty: string) => {
switch (particleProperty) {
case "emissionRate":
particleSystem.emissionRate = +emissionRate.value;
break;
case "particleSize":
particleSystem.minimumImageSize.x = +particleSize.value;
particleSystem.minimumImageSize.y = +particleSize.value;
particleSystem.maximumImageSize.x = +particleSize.value;
particleSystem.maximumImageSize.y = +particleSize.value;
break;
case "minimumParticleLife":
particleSystem.minimumParticleLife = +minimumParticleLife.value;
break;
case "maximumParticleLife":
particleSystem.maximumParticleLife = +maximumParticleLife.value;
break;
case "minimumSpeed":
particleSystem.minimumSpeed = +minimumSpeed.value;
break;
case "maximumSpeed":
particleSystem.maximumSpeed = +maximumSpeed.value;
break;
case "startScale":
particleSystem.startScale = +startScale.value;
break;
case "endScale":
particleSystem.endScale = +endScale.value;
break;
case "gravity":
gravity.value = +gravity.value;
break;
}
};
onMounted(() => {
initMap();
});
</script>
<style scoped>
#viewContainer {
height: calc(100vh - 100px);
width: calc(100vw - 260px);
}
.toolbar {
position: absolute;
top: 30px;
background-color: rgba(42, 42, 42, 0.8);
color: aliceblue;
}
</style>