第八章 Cesium 实现动态模型拖尾效果:从原理到完整实现
效果图
在三维地理信息可视化中,为移动模型添加拖尾效果可以极大增强视觉表现力,常用于模拟飞行器尾迹、导弹轨迹等动态场景。本文将详细介绍如何使用 Cesium 引擎实现模型拖尾效果,并提供完整可运行的代码示例。
实现原理
Cesium 中的粒子系统 (ParticleSystem) 是实现拖尾效果的核心,其原理是:
- 持续发射大量微小粒子
- 为粒子设置物理运动规律
- 控制粒子生命周期中的颜色和透明度变化
- 将粒子发射器绑定到移动模型上
通过这种方式,可以模拟出自然流畅的拖尾效果,且可以通过参数调整实现不同的视觉效果。
完整代码实现
以下是带有详细注释的完整代码:
Cesium模型拖尾效果实现
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>cesium模型拖尾效果</title><!-- 加载Cesium核心库和样式 --><script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><style>body {margin: 0;padding: 0;overflow: hidden;height: 100vh;}#cesiumContainer {width: 100%;height: 100%;}</style>
</head><body><div id="cesiumContainer"></div><script>// 初始化Cesium Viewerconst viewer = new Cesium.Viewer("cesiumContainer", {shouldAnimate: true, // 启用动画});// 清除默认图层viewer.imageryLayers.removeAll();// 添加ArcGIS卫星影像图层(高清卫星地图)viewer.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerImageryProvider({url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'}));// 定义飞机初始位置(经纬度和高度)const planePosition = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883, 800.0);// 粒子系统相对于模型的偏移量const particlesOffset = new Cesium.Cartesian3(-8.950115473940969,34.852766731753945,-30.235411095432937,);// 计算相机位置(基于飞机位置和偏移量)const cameraLocation = Cesium.Cartesian3.add(planePosition,particlesOffset,new Cesium.Cartesian3(),);// 重置相机视角函数const resetCamera = function () {viewer.camera.lookAt(cameraLocation, new Cesium.Cartesian3(-450, -300, 200));};resetCamera();// 创建粒子图像(圆形粒子)let particleCanvas;function getImage() {if (!Cesium.defined(particleCanvas)) {particleCanvas = document.createElement("canvas");particleCanvas.width = 20;particleCanvas.height = 20;const 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;}// 向场景添加飞机模型const hpr = new Cesium.HeadingPitchRoll(0.0, Cesium.Math.PI_OVER_TWO, 0.0);const orientation = Cesium.Transforms.headingPitchRollQuaternion(planePosition,hpr,);const entity = viewer.entities.add({model: {uri: "../../../public/scene.gltf", // 模型路径(请替换为实际模型路径)scale: 10.0, // 缩放比例},position: planePosition,orientation: orientation,// 模型加载回调onLoad: (model) => {console.log("模型加载成功", model);},onError: (error) => {console.error("模型加载失败", error);},});// 创建粒子系统的模型矩阵(确定粒子系统位置)const translationOffset = Cesium.Matrix4.fromTranslation(particlesOffset,new Cesium.Matrix4(),);const translationOfPlane = Cesium.Matrix4.fromTranslation(planePosition,new Cesium.Matrix4(),);const particlesModelMatrix = Cesium.Matrix4.multiplyTransformation(translationOfPlane,translationOffset,new Cesium.Matrix4(),);// 定义火箭推进器粒子系统参数const rocketOptions = {numberOfSystems: 50.0, // 粒子系统数量iterationOffset: 0.1, // 迭代偏移量cartographicStep: 0.000001, // 经纬度步长baseRadius: 0.0005, // 基础半径// 粒子颜色选项colorOptions: [{minimumRed: 1.0,green: 0.5,minimumBlue: 0.05,alpha: 1.0,},{red: 0.9,minimumGreen: 0.6,minimumBlue: 0.01,alpha: 1.0,},{red: 0.8,green: 0.05,minimumBlue: 0.09,alpha: 1.0,},{minimumRed: 1,minimumGreen: 0.05,blue: 0.09,alpha: 1.0,},],};// 定义彗星尾迹粒子系统参数const cometOptions = {numberOfSystems: 100.0, // 粒子系统数量iterationOffset: 0.003, // 迭代偏移量cartographicStep: 0.0000001, // 经纬度步长baseRadius: 0.0005, // 基础半径// 粒子颜色选项colorOptions: [{red: 0.6,green: 0.6,blue: 0.6,alpha: 1.0,},{red: 0.6,green: 0.6,blue: 0.9,alpha: 0.9,},{red: 0.5,green: 0.5,blue: 0.7,alpha: 0.5,},],};// 粒子运动函数(控制粒子的物理行为)let scratchCartesian3 = new Cesium.Cartesian3();let scratchCartographic = new Cesium.Cartographic();const forceFunction = function (options, iteration) {return function (particle, dt) {// 限制时间增量,避免大跳变dt = Cesium.Math.clamp(dt, 0.0, 0.05);// 计算粒子位置变化scratchCartesian3 = Cesium.Cartesian3.normalize(particle.position,new Cesium.Cartesian3(),);scratchCartesian3 = Cesium.Cartesian3.multiplyByScalar(scratchCartesian3,-40.0 * dt,scratchCartesian3,);scratchCartesian3 = Cesium.Cartesian3.add(particle.position,scratchCartesian3,scratchCartesian3,);// 转换为地理坐标进行计算scratchCartographic = Cesium.Cartographic.fromCartesian(scratchCartesian3,Cesium.Ellipsoid.WGS84,scratchCartographic,);// 计算角度,使粒子呈圆形分布const angle = (Cesium.Math.PI * 2.0 * iteration) / options.numberOfSystems;iteration += options.iterationOffset;// 应用水平和垂直方向的偏移scratchCartographic.longitude +=Math.cos(angle) * options.cartographicStep * 30.0 * dt;scratchCartographic.latitude +=Math.sin(angle) * options.cartographicStep * 30.0 * dt;// 将地理坐标转换回笛卡尔坐标particle.position = Cesium.Cartographic.toCartesian(scratchCartographic);};};// 创建粒子系统const matrix4Scratch = new Cesium.Matrix4();let scratchAngleForOffset = 0.0;const scratchOffset = new Cesium.Cartesian3();const imageSize = new Cesium.Cartesian2(15.0, 15.0);// 创建多个粒子系统的函数function createParticleSystems(options, systemsArray) {const length = options.numberOfSystems;for (let i = 0; i < length; ++i) {// 计算每个粒子系统的角度偏移scratchAngleForOffset = (Math.PI * 2.0 * i) / options.numberOfSystems;scratchOffset.x += options.baseRadius * Math.cos(scratchAngleForOffset);scratchOffset.y += options.baseRadius * Math.sin(scratchAngleForOffset);// 创建发射器模型矩阵const emitterModelMatrix = Cesium.Matrix4.fromTranslation(scratchOffset,matrix4Scratch,);// 随机选择颜色const color = Cesium.Color.fromRandom(options.colorOptions[i % options.colorOptions.length],);// 获取粒子力函数const force = forceFunction(options, i);// 创建并添加粒子系统const item = viewer.scene.primitives.add(new Cesium.ParticleSystem({image: getImage(), // 粒子图像startColor: color, // 初始颜色endColor: color.withAlpha(0.0), // 结束颜色(透明)particleLife: 3.5, // 粒子生命周期speed: 0.00005, // 粒子速度imageSize: imageSize, // 粒子大小emissionRate: 30.0, // 发射率emitter: new Cesium.CircleEmitter(0.1), // 发射器形状lifetime: 0.1, // 发射器生命周期updateCallback: force, // 更新回调函数modelMatrix: particlesModelMatrix, // 模型矩阵emitterModelMatrix: emitterModelMatrix, // 发射器模型矩阵}));systemsArray.push(item);}}// 创建两种粒子系统数组const rocketSystems = [];const cometSystems = [];createParticleSystems(rocketOptions, rocketSystems);createParticleSystems(cometOptions, cometSystems);// 控制粒子系统显示的工具函数function showAll(systemsArray, show) {const length = systemsArray.length;for (let i = 0; i < length; ++i) {systemsArray[i].show = show;}}// 切换不同拖尾效果的选项const options = [{text: "彗星尾迹",onselect: function () {showAll(rocketSystems, false);showAll(cometSystems, true);resetCamera();},},{text: "火箭推进器",onselect: function () {showAll(cometSystems, false);showAll(rocketSystems, true);resetCamera();},},];// 默认显示彗星尾迹showAll(cometSystems, true);</script>
</body>
</html>
代码解析
初始化 Cesium 环境
- 创建 Viewer 实例并配置基础参数
- 移除默认图层,添加卫星影像图层提升视觉效果
模型与相机设置
- 定义模型初始位置和姿态
- 计算合适的相机视角,确保模型和拖尾效果清晰可见
粒子系统核心
- 创建粒子图像:使用 canvas 绘制圆形粒子
- 定义粒子系统参数:包括数量、颜色、生命周期等
- 实现粒子运动函数:控制粒子的物理行为,使其呈现拖尾效果
多样化拖尾效果
- 定义两种不同风格的拖尾效果(火箭推进器和彗星尾迹)
- 通过参数差异实现不同的视觉表现:颜色、密度、扩散范围等
自定义与扩展
修改粒子属性
- 调整
imageSize
改变粒子大小 - 修改
particleLife
控制拖尾长度 - 调整
emissionRate
改变粒子密度
- 调整
更改拖尾颜色
- 在
colorOptions
数组中添加或修改颜色配置 - 可以使用
Cesium.Color
的静态方法创建更丰富的颜色
- 在
实现模型移动
- 添加模型位置更新逻辑
- 同步更新粒子系统的位置矩阵,保持拖尾与模型的相对位置
性能优化
- 减少
numberOfSystems
和emissionRate
可提升性能 - 复杂场景下可考虑使用 Billboard 替代部分粒子
- 减少
通过以上方法,可以实现各种炫酷的模型拖尾效果,为三维地理信息可视化增添更多动态元素和视觉冲击力。