当前位置: 首页 > news >正文

Cesium快速入门到精通系列教程七:粒子效果

在 Cesium 1.93 中实现粒子效果需通过 ParticleSystem 类进行配置,结合发射器、生命周期、物理模拟等参数实现火焰、烟雾、雨雪等动态效果。

一、粒子系统基础

概念:粒子系统是一种模拟复杂物理效应的图形技术,由大量小粒子组成,这些小粒子可以代表火花、烟雾、雨滴、火焰等效果,通过控制单个粒子的行为来呈现出复杂的 “模糊” 对象效果。

作用:用于增强 Cesium 中的三维场景的视觉效果,比如模拟飞机引擎爆炸、飞机坠毁烟雾轨迹、火箭尾焰、天气现象等。

二、粒子系统核心参数配置

​参数​​作用​​示例值​​引用​
image粒子贴图(需透明PNG)"smoke.png"
startColor/endColor粒子颜色渐变(含透明度)Cesium.Color.RED.withAlpha(0.8) → 黄色透明
startScale/endScale粒子大小变化(初始到结束的缩放倍率)1.04.0(烟雾膨胀)
emissionRate每秒发射粒子数量(控制密度)火焰:20-50;烟雾:5-10
emitter发射器类型:<br>- CircleEmitter(圆形)<br>- ConeEmitter(锥形,适合火焰)new Cesium.ConeEmitter(0.8)
speed/minimumSpeed粒子初始速度(米/秒)雨滴:-5.0(向下);烟雾:1.0~4.0
particleLife粒子存活时间(秒)火焰:1.5-2.0;烟雾:5.0
updateCallback自定义物理效果(如重力、风力)模拟重力:修改粒子速度向量

三、实现步骤(以卡车烟雾为例) 

​​1、初始化实体与矩阵​​

绑定粒子位置到模型(如卡车引擎):

const truckEntity = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),model: { uri: "truck.glb" }
});
const modelMatrix = entity.computeModelMatrix(viewer.clock.currentTime);

​​2、设置发射器偏移​​

通过 emitterModelMatrix 微调局部位置:

function computeEmitterMatrix() {const offset = new Cesium.Cartesian3(-4.0, 0.0, 1.4); // 引擎位置偏移return Cesium.Matrix4.fromTranslation(offset);
}

3、创建粒子系统​​

配置参数并添加到场景:

const particleSystem = viewer.scene.primitives.add(new Cesium.ParticleSystem({image: "smoke.png",startColor: Cesium.Color.LIGHTSEAGREEN.withAlpha(0.7),endColor: Cesium.Color.WHITE.withAlpha(0.0),startScale: 1.0,endScale: 4.0,emissionRate: 5.0,particleLife: 5.0,minimumSpeed: 1.0,maximumSpeed: 4.0,modelMatrix: modelMatrix,          // 绑定卡车位置emitterModelMatrix: computeEmitterMatrix(), // 引擎偏移emitter: new Cesium.CircleEmitter(0.5) // 圆形发射})
);

四、常见效果示例

1. ​​雨滴效果​​

image: "raindrop.png",
startColor: Cesium.Color.WHITE.withAlpha(0.8),
endColor: Cesium.Color.BLUE.withAlpha(0.2),
minimumSpeed: -5.0,  // 向下运动
emissionRate: 1000.0,
particleLife: 3.0,
emitter: new Cesium.BoxEmitter(new Cesium.Cartesian3(10, 10, 0)) // 盒状区域

关键:速度负值模拟下落,盒状发射器覆盖天空 

2. ​​火焰效果​​

image: "fire.png",
startColor: Cesium.Color.RED.withAlpha(0.9),
endColor: Cesium.Color.YELLOW.withAlpha(0.5),
emissionRate: 30.0,
particleLife: 1.5,
emitter: new Cesium.ConeEmitter(Cesium.Math.toRadians(45)) // 锥形向上

 贴图建议:使用红黄渐变纹理增强真实感。

五、高级技巧

1、​​物理模拟​​

通过 updateCallback 添加重力或风力:

function applyGravity(particle, dt) {particle.velocity.z -= 9.8 * dt; // 重力加速度
}
// 在粒子系统中配置:
updateCallback: applyGravity

​​2、矩阵定位​​

  • modelMatrix:将粒子绑定到实体世界坐标。
  • emitterModelMatrix:在实体局部坐标系偏移(避免从中心发射) 。

​​3、动态属性​​

使用 bursts 实现爆炸效果:

bursts: [new Cesium.ParticleBurst({ time: 5.0, minimum: 50, maximum: 100 })
]

六、注意事项

  • 贴图路径​​:使用绝对路径或托管于Cesium Ion,避免跨域问题。
  • ​​性能优化​​:高密度粒子(如雨雪)需控制 emissionRate,避免卡顿。
  • ​​版本兼容​​:1.93中 ParticleSystem API 与旧版一致,可直接迁移。

七、完整实例

<template><div id="cesiumContainer" class="fullSize"></div><div id="loadingOverlay"><h1>Loading...</h1></div><div id="toolbar"><table><tbody><tr><td>Rate</td><td><input type="range" min="0.0" max="100.0" step="1" data-bind="value: emissionRate, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: emissionRate"></td></tr><tr><td>Size</td><td><input type="range" min="2" max="60.0" step="1" data-bind="value: particleSize, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: particleSize"></td></tr><tr><td>Min Life</td><td><input type="range" min="0.1" max="30.0" step="1"data-bind="value: minimumParticleLife, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: minimumParticleLife"></td></tr><tr><td>Max Life</td><td><input type="range" min="0.1" max="30.0" step="1"data-bind="value: maximumParticleLife, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: maximumParticleLife"></td></tr><tr><td>Min Speed</td><td><input type="range" min="0.0" max="30.0" step="1" data-bind="value: minimumSpeed, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: minimumSpeed"></td></tr><tr><td>Max Speed</td><td><input type="range" min="0.0" max="30.0" step="1" data-bind="value: maximumSpeed, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: maximumSpeed"></td></tr><tr><td>Start Scale</td><td><input type="range" min="0.0" max="10.0" step="1" data-bind="value: startScale, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: startScale"></td></tr><tr><td>End Scale</td><td><input type="range" min="0.0" max="10.0" step="1" data-bind="value: endScale, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: endScale"></td></tr><tr><td>Gravity</td><td><input type="range" min="-20.0" max="20.0" step="1" data-bind="value: gravity, valueUpdate: 'input'"><input type="text" size="5" data-bind="value: gravity"></td></tr></tbody></table></div>
</template><script setup>
Cesium.Ion.defaultAccessToken = '你的defaultAccessToken'
import { onMounted } from "vue";
import * as Cesium from "cesium";
import "./Widgets/widgets.css";window.CESIUM_BASE_URL = "/"; // 设置Cesium静态资源路径(public目录)onMounted(async () => {const viewer = new Cesium.Viewer("cesiumContainer");//Set the random number seed for consistent results.Cesium.Math.setRandomNumberSeed(3);//Set bounds of our simulation timeconst 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 endviewer.clock.multiplier = 1;viewer.clock.shouldAnimate = true;//Set timeline to simulation boundsviewer.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();function computeEmitterModelMatrix() {hpr = Cesium.HeadingPitchRoll.fromDegrees(0.0, 0.0, 0.0, hpr);trs.translation = Cesium.Cartesian3.fromElements(-4.0, 0.0, 1.4, translation);trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr, rotation);return Cesium.Matrix4.fromTranslationRotationScale(trs, emitterModelMatrix);}const pos1 = Cesium.Cartesian3.fromDegrees(-75.15787310614596, 39.97862668312678);const pos2 = Cesium.Cartesian3.fromDegrees(-75.1633691390455, 39.95355089912078);const position = new Cesium.SampledPositionProperty();position.addSample(start, pos1);position.addSample(stop, pos2);const entity = viewer.entities.add({availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({start: start,stop: stop,}),]),model: {uri: "/model/CesiumMilkTruck.glb",minimumPixelSize: 64,},viewFrom: new Cesium.Cartesian3(-100.0, 0.0, 100.0),position: position,orientation: new Cesium.VelocityOrientationProperty(position),});viewer.trackedEntity = entity;const scene = viewer.scene;const particleSystem = scene.primitives.add(new Cesium.ParticleSystem({image: "/model/smoke.png",startColor: Cesium.Color.LIGHTSEAGREEN.withAlpha(0.7),endColor: Cesium.Color.WHITE.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: viewModel.emissionRate,bursts: [// these burst will occasionally sync to create a multicolored effectnew 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 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);}viewer.scene.preUpdate.addEventListener(function (scene, time) {particleSystem.modelMatrix = computeModelMatrix(entity, time);// Account for any changes to the emitter model matrix.particleSystem.emitterModelMatrix = computeEmitterModelMatrix();// Spin the emitter if enabled.if (viewModel.spin) {viewModel.heading += 1.0;viewModel.pitch += 1.0;viewModel.roll += 1.0;}});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);
})</script><style scoped>
@import url(@/assets/bucket.css);* {margin: 0;padding: 0;
}#cesiumContainer {width: 100wh;height: 100vh;
}#toolbar {background: rgba(42, 42, 42, 0.8);padding: 4px;border-radius: 4px;
}#toolbar input {vertical-align: middle;padding-top: 2px;padding-bottom: 2px;
}#toolbar .header {font-weight: bold;
}
</style>

 

相关文章:

  • Java 中字节流的使用详解
  • 【GESP真题解析】第 18 集 GESP 三级 2025 年 3 月编程题 1:2025
  • 用 Lazarus IDE 写一个邮件客户端软件,能收发邮件,编写邮件
  • 八股---7.JVM
  • Qwen系列之Qwen3解读:最强开源模型的细节拆解
  • 开源项目实战学习之YOLO11:12.7 ultralytics-models-transformer.py
  • LLMs之RLVR:《Absolute Zero: Reinforced Self-play Reasoning with Zero Data》翻译与解读
  • 基于定制开发开源AI智能名片S2B2C商城小程序的首屏组件优化策略研究
  • 计数思想-众数
  • 【Java学习笔记】日期类
  • 香橙派3B学习笔记8:snap安装管理软件包_打包俩个有调用的python文件
  • cpp自学 day2(—>运算符)
  • unipp---HarmonyOS 应用开发实战
  • PHP环境极速搭建
  • 开源大模型网关:One API实现主流AI模型API的统一管理与分发
  • 工作记录 2018-08-21
  • leetcode189-轮转数组
  • 开源项目实战学习之YOLO11:12.6 ultralytics-models-tiny_encoder.py
  • 学习python做表格6月8日补录
  • 手写 vue 源码 === runtime-core 实现
  • 速升网站/seo下载站
  • 佛山app开发公司/seo快速推广
  • sharepoint做网站/发布新闻的平台有哪些
  • 建设银行网站如何下载u盾/可以直接进入网站的正能量
  • 信息行业网站建设/深圳seo网站优化公司
  • 网站建设方案ppt 枫子科技/成都专业网站推广公司