尧图精选

Cesium天气可视化实战:粒子系统与动态云层渲染完整方案

🕒 发布时间:2026/9/7 23:32:50 📁 来源:尧图网络
做 Cesium 天气可视化这件事我前后折腾了近两周。从最开始的粒子系统一通乱调到后面摸清粒子纹理、发射器、生命周期这些参数的相互作用再到气象数据动态接入和云层漂移材质中间踩了不少坑。网上关于 Cesium 粒子系统的资料不少但几乎都是静态 demo真正把“气象数据动态加载”和“雨雪云层渲染”串在一起的完整案例很少。这篇文章就把我这套方案完整拆开讲清楚数据怎么处理、坐标怎么转换、粒子系统怎么调才像真的雨雪、云层怎么做出动态漂移感核心代码都会贴出来原理部分也会用大白话解释。适合正在做数字孪生、智慧城市气象模块或者想在 Cesium 场景里加天气效果但不知道从哪下手的开发者。1. 项目定位与整体方案设计1.1 这个功能解决的是什么场景问题先说清楚这类需求通常出现在哪。最常见的几个场景智慧城市的防汛抗旱系统需要根据气象台发布的降水数据在三维地图上展示降雨范围、雨强分布农业气象平台要按区域显示当前是晴、多云、雨还是雪数字孪生园区、港口的可视化大屏也需要一个“能感知天气”的虚拟场景来配合业务联动。这些场景有一个共同点不是简单做一段动画循环播放而是气象数据变化时三维场景中的雨雪云层也要跟着变。比如某区域发布暴雨预警该区域范围内就出现明显降雨雨强越大粒子越密雨区移动时降雨范围也要跟着移动。这就要求“数据动态加载”和“效果渲染”两条链路打通而不是把粒子效果写死。1.2 技术选型为什么是粒子系统加动态材质Cesium 里实现雨雪效果常见方案有几种Entity 粒子系统、Primitive 粒子系统、后处理特效配合着色器。我最终选择的是Primitive 粒子系统 自定义 Fabric 材质的组合理由有三点第一Primitive 粒子系统相比 Entity 粒子系统性能更好而且可控参数更细。Entity 适合快速点几个特效但在需要大批量粒子、动态调节发射速率、挂接复杂业务逻辑时Primitive 直接操作底层 API 更合适。第二雨雪这种效果本质上就是大量“短线段”或“点”的高速下落运动粒子系统天生就是干这个的不需要引入重量级的后处理管线。后处理方案比如全屏 shader 叠加雨丝虽然视觉效果可以很强但会把整个画面都覆盖掉做局部区域性降雨反而不方便。第三云层效果用动态纹理材质实现最轻量。Cesium 的 Material 系统支持 Fabric 语法自定义可以通过 UV 偏移和帧号变量让云图纹理持续流动视觉上就是云在漂移完全不需要额外依赖。1.3 模块划分与数据流设计整个项目我拆成了三条链路数据链路气象数据源JSON/GeoJSON→ 请求解析 → 提取降水类型、雨强、区域范围 → 转换成粒子系统参数和云层纹理参数。坐标链路经纬度坐标 → 转 Cesium 世界坐标 → 根据数据范围计算粒子发射器尺寸和模型矩阵。渲染链路粒子系统发射雨滴/雪花 → 自定义材质渲染动态云层 → 按相机距离、粒子数量做性能分级。三条链路最后在业务入口统一编排收到新的气象数据更新粒子系统参数同时更新云层材质的透明度、覆盖范围、漂移速度。这套设计的好处是数据、坐标、渲染三者解耦以后换成别的数据源或者改成台风路径可视化只需要替换数据解析和参数映射的部分渲染链路几乎不用动。2. 气象数据准备从地面观测到三维场景坐标2.1 数据源选取气象数据源的选择会影响整个方案的复杂程度。我这里分三种情况列一下数据源类型典型来源更新频率适合场景实时天气接口OpenWeatherMap 等分钟级展示当前天气状态、温度、雨雪地面观测资料中国气象数据网公开数据集小时级历史回放、区域降水分析数值预报产品GFS、ERA5 再分析资料3-6 小时科研级风场、降水预测可视化对于多数业务项目不需要追求科研级精度用实时天气接口或者地面观测资料就够了。重点是拿到数据后要能解析出“降水类型雨/雪/无”“降水强度”“影响区域经纬度范围”这几个核心字段。我自己的做法是数据后台先做一次清洗把气象数据统一输出成这样的结构{ area: { type: Polygon, coordinates: [[[116.3, 39.8], [116.5, 39.8], [116.5, 40.0], [116.3, 40.0]]] }, weather: rain, precipitation: 12.5, windSpeed: 8.3, clouds: 85 }precipitation用来映射粒子发射速率clouds用来控制云层透明度weather决定粒子系统切换成雨还是雪。坐标范围直接用于创建粒子发射器的包围盒和匹配多边形裁剪区域。提示降水强度到粒子发射速率的映射关系没有绝对标准我一般用线性映射再乘一个可调系数。比如 0-50mm/h 降雨量映射到 emissionRate 的 500-4000 之间给运营留一个全局系数便于调出最合适的视觉效果。2.2 坐标转换与插值气象数据给的是经纬度用 Cesium 直接渲染要转成世界坐标。最常用的是Cesium.Cartesian3.fromDegrees这个都知道。真正容易踩坑的是粒子的发射器位置和包围盒尺寸。Cesium 的粒子发射器比如BoxEmitter接收的是一个 Cartesian3 的尺寸参数这个尺寸必须换算成以米为单位的世界坐标增量。如果气象数据范围是一个经纬度矩形比如[116.3, 39.8]到[116.5, 40.0]不能直接拿经纬度差值去填发射器尺寸要先用Cartesian3.fromDegrees(116.3, 39.8, 800)得到左下角世界坐标再用Cartesian3.fromDegrees(116.5, 40.0, 800)得到右上角世界坐标然后相减得到以米为单位的宽、高、深度。const west 116.3, south 39.8, east 116.5, north 40.0; const height 600; const westSouth Cesium.Cartesian3.fromDegrees(west, south, height); const eastNorth Cesium.Cartesian3.fromDegrees(east, north, height); const boxDimensions Cesium.Cartesian3.subtract(eastNorth, westSouth, new Cesium.Cartesian3()); const emitter new Cesium.BoxEmitter(boxDimensions);这样发射器就正好覆盖了整个气象影响区域。如果数据范围跨的经纬度比较大比如整个省的范围发射器尺寸就会非常大这时候要特别小心粒子密度分布否则会出现粒子稀疏到几乎看不见的情况。2.3 动态数据加载的格式与更新策略动态加载气象数据格式上我用 GeoJSON 做区域边界和属性承载用普通的 JSON 做天气参数。GeoJSON 的优势是能直接和 Cesium 的 GeoJsonDataSource 无缝衔接降雨区域的 Polygon、边界线展示非常方便。更新策略方面不建议用高频定时器疯狂刷新粒子参数。气象数据本身变化没那么快正常业务 5-15 分钟刷新一次就够。我采用的是“低频率数据轮询 高频率效果插值”的策略每 10 分钟请求一次最新气象数据拿到新数据后不直接跳变粒子参数而是做一次 2-3 秒的线性插值过渡比如雨强从 10mm/h 渐变到 25mm/h粒子发射速率也跟着平滑变化视觉上不会出现“突降暴雨”的生硬感。setInterval(async () { const weather await fetchWeatherData(); targetEmissionRate mapPrecipitationToRate(weather.precipitation); targetColor weather.weather snow ? snowColor : rainColor; }, 10 * 60 * 1000); // 在 requestAnimationFrame 循环中做插值 currentEmissionRate (targetEmissionRate - currentEmissionRate) * 0.02;这一点对体验的影响非常大第一次做的时候我就是直接赋值结果每次刷新都是一顿一顿的后来加了插值过渡才顺滑。3. 雨雪粒子系统原理与参数调优3.1 粒子系统在 Cesium 中的工作原理要调好雨雪效果得先理解粒子系统的运行机制。Cesium 的ParticleSystem是一个 Primitive它维护了一堆粒子的集合每个粒子有位置、速度、方向、生命周期、颜色、大小等属性。每一帧系统会做三件事根据emissionRate生成新粒子新粒子从发射器Emitter的范围内产生。更新存活粒子的位置位置增量由速度和时间决定。检查粒子生命周期超过maximumParticleLife的粒子被回收并更新粒子透明度。这一点和 GPU 粒子系统的区别在于Cesium 的 ParticleSystem 默认是 CPU 更新的粒子数量上万后性能压力会显现。理解这个底层机制才能明白为什么发射速率不能无脑飙高也才能理解为什么粒子纹理、颜色渐变这些小参数反而更影响视觉质量。雨雪效果本质上都是“粒子高速垂直下落 透明渐隐”同类效果在不同场景下的差异主要由粒子尺寸、速度、纹理形状决定。雨滴是细长的短线雪花是较大的圆形或六角形颗粒它们的下落速度和透明度衰减曲线完全不同。3.2 雨滴效果发射器、纹理与速度的搭配雨滴效果的视觉逼真度主要靠三个参数组合第一个是纹理。雨滴不能用一个圆点贴图去硬撑需要用一张细长的半透明渐变纹理。纹理长度方向与粒子速度方向保持一致下落时就有雨的动感。网上有很多免费的雨滴粒子贴图资源也可以自己用 Canvas 生成一张 16x64 的渐变条。第二个是下落速度。真实雨滴的终速度大约是 8-10m/s但三维场景里如果完全按真实速度渲染反而显得太快、太碎尤其是在大范围俯视视角下几乎看不清。我的经验是把速度设置在场景合理范围内如果粒子系统作用范围是 500 米上空速度设在 18-25m/s 左右视觉上有明显的下落感但不会糊成一片。第三个是发射速率和粒子系统的覆盖范围成正比。上面代码里已经提到先用BoxEmitter用米单位尺寸覆盖目标区域。这个区域如果很大同一帧的粒子密度就会被摊薄。所以雨滴的emissionRate必须随着发射器体积增大而调大否则大片区域里只有零星几滴雨。下面是核心的雨滴粒子系统配置const rainParticleSystem viewer.scene.primitives.add( new Cesium.ParticleSystem({ image: getRainTexture(), startColor: new Cesium.Color(0.6, 0.7, 0.9, 0.5), endColor: new Cesium.Color(0.6, 0.7, 0.9, 0.0), startScale: 1.0, endScale: 0.5, particleSize: 2.0, imageSize: new Cesium.Cartesian2(4, 32), imageScale: 1.2, maximumParticleLife: 1.0, emissionRate: 3000, emitter: rainEmitter, minimumSpeed: 20, maximumSpeed: 28, lifetime: 60, }) );重点解释几个参数的选择原因particleSize是粒子的基础大小imageSize是纹理的实际绘制尺寸长宽比 1:8 是雨滴的关键形状。maximumParticleLife控制在 1 秒左右粒子从出生到消亡只需要下落这一段距离超过这个时间粒子会被回收所以生命周期越短粒子的更新效率越高。startColor和endColor的 alpha 从 0.5 渐变到 0模拟雨滴落下的拖尾渐隐。3.3 雪花效果慢速大粒子加近地面飘落雪花和雨滴的参数差异非常大踩坑主要在这一块。雪花体积大、下落慢还会受风影响产生飘动。粒子系统里体现就三点速度要慢3-8m/s粒子尺寸要大是雨滴的 4-8 倍生命周期要长3-6 秒。还有一点雪花不能像雨滴那样硬邦邦往下掉最好给速度方向加一个随机扰动。Cesium 粒子没有直接提供“随机水平风扰”的参数但可以通过在每一帧调用updateCallback对粒子速度做微调或者更简单的方式是用SphereEmitter略微改变发射角度让粒子发散飘落。const snowParticleSystem viewer.scene.primitives.add( new Cesium.ParticleSystem({ image: getSnowTexture(), startColor: new Cesium.Color(0.92, 0.95, 1.0, 0.9), endColor: new Cesium.Color(0.92, 0.95, 1.0, 0.0), particleSize: 6.0, imageSize: new Cesium.Cartesian2(24, 24), imageScale: 1.0, maximumParticleLife: 4.5, emissionRate: 800, emitter: snowEmitter, minimumSpeed: 2.5, maximumSpeed: 5.5, lifetime: 60, }) );雪花纹理最好用带透明通道的六角形雪花贴图如果临时找不到用Canvas画一个也可以白色六角星加 Gaussian 模糊就是很标准的雪花贴图。注意雪和雨共存的场景要慎重。如果同时开两个粒子系统性能压力会成倍增加。我一般是根据气象数据的降水类型字段做互斥只在雨和雪之间切换避免无谓的 GPU 和 CPU 消耗。4. 动态云层渲染纹理叠加与自定义材质4.1 纹理叠加法给地球贴一层半透明云图云层最简单的实现方式是给 Cesium 的 Globe 表面叠加一层带透明度的云图纹理。具体做法有多种最直接的是在地球上添加一个跟随地球表面动态匹配的矩形 Primitive或者干脆在天空球层叠加一个大平面。这种方案的优点是实现成本低几行代码就能看到效果适合快速验证和低交互场景。但缺点也很明显云图纹理是静止的没有动态漂移感矩形覆盖范围难处理贴地面会出现云层“吃进”地形的穿帮。我最初用scene.globe的imageryLayers叠加过一张整球云图快速是快但效果顶多算“云覆盖率可视化”离“动态云层”还有很大距离。如果你只要求展示“今天多云还是阴天”这方案够用但要动画效果还得看自定义材质。4.2 基于 Fabric 的自定义动态材质让云真正流动起来动态云层我采用的是自定义 Fabric 材质方案。Cesium 的 Material 系统允许通过Fabric定义材质外观支持自定义 GLSL 着色器代码。利用这个能力可以写一个 cloud 材质把云图纹理的采样坐标加上一个随时间变化的偏移量云就会沿着一个方向持续漂移。核心思路是这样的纹理采样的 UV 坐标中U 值加上czm_frameNumber * speed的偏移这样每一帧采样到的纹理位置都在变化云图看起来就在平移。不仅水平方向可以漂我还在垂直方向加一个很小的偏移实现风向略微偏斜的视觉效果。Cesium.Material._materialCache.addMaterial(DriftingCloud, { fabric: { type: DriftingCloud, uniforms: { image: cloudTextureUrl, color: new Cesium.Color(1.0, 1.0, 1.0, 0.65), speed: 0.004 }, source: czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material czm_getDefaultMaterial(materialInput); vec2 st materialInput.st; st.x czm_frameNumber * speed; st.y czm_frameNumber * speed * 0.2; vec4 sampledColor texture2D(image, st); material.diffuse sampledColor.rgb * color.rgb; material.alpha sampledColor.a * color.a; return material; } }, translucent: true });czm_frameNumber是 Cesium 每一帧自动递增的帧号直接用帧号做 UV 偏移不需要额外维护时间变量。speed作为 uniform可以随时在外部动态修改用来模拟风速变化。把自定义材质应用到矩形 Primitive 上时要注意这个矩形应该放置在地球表面上方一定高度比如 800-1500 米避免云层与地形表面完全重合导致的深度冲突闪烁。矩形的经纬度范围根据云层覆盖区域设置宽度和高度按比例设计必要时拆成多个矩形做不同方向的漂移模拟云层分层。4.3 两种方案对比方案实现成本动态效果性能可定制性整球纹理叠加低无高低静态矩形云带低无高中自定义 Fabric 动态材质中持续漂移中高如果你要做的只是“今天多云”这种状态展示方案一就够了。要做出云层移动、风向体现、局部云量变化直接上方案三省得后期返工。5. 完整源码实现与关键配置5.1 项目初始化与基础场景配置先建立基础 Cesium 场景。这里用的是常规的 Viewer 初始化配了几个关键渲染设置去掉默认的天空盒水汽效果干扰开启深度检测方便粒子在空中正确显隐。const viewer new Cesium.Viewer(cesiumContainer, { timeline: false, animation: false, baseLayerPicker: false, geocoder: false, infoBox: false, sceneMode: Cesium.SceneMode.SCENE3D, }); viewer.scene.globe.depthTestAgainstTerrain true; viewer.scene.globe.showGroundAtmosphere false; viewer.scene.fog.enabled false;showGroundAtmosphere false是为了避免大气效果影响远处雨雪的可视透明度fog.enabled false则是防止场景雾效叠加过多影响粒子颜色的纯度。这两个配置在调试时容易被忽略但实际对视觉效果影响不小。5.2 动态加载气象数据的核心模块数据加载模块我封装成独立的类。构造函数接收 viewer 和数据请求地址对外暴露updateData和startPolling两个方法。数据回调用一个简单的发布订阅解耦这样粒子系统模块和云层模块只需要监听数据更新事件各自更新自身状态。class WeatherDataService { constructor(url, interval 600000) { this.url url; this.interval interval; this.listeners []; this.currentData null; } async fetchData() { const res await fetch(this.url); const data await res.json(); this.currentData data; this.listeners.forEach((fn) fn(data)); } startPolling() { this.fetchData(); setInterval(() this.fetchData(), this.interval); } onUpdate(fn) { this.listeners.push(fn); } }核心逻辑在事件消费者那边。数据更新时先判断天气类型切换粒子系统再根据降水强度更新发射速率最后根据云覆盖率更新云层材质的透明度。这里用currentData.weather来驱动雨雪切换weatherService.onUpdate((data) { // 更新粒子系统 if (data.weather snow) { snowParticleSystem.show true; rainParticleSystem.show false; snowParticleSystem.emissionRate data.precipitation * 80; } else if (data.weather rain) { rainParticleSystem.show true; snowParticleSystem.show false; rainParticleSystem.emissionRate data.precipitation * 160; } else { rainParticleSystem.show false; snowParticleSystem.show false; } // 更新云层 cloudMaterial.uniforms.color new Cesium.Color( 1.0, 1.0, 1.0, data.clouds ? data.clouds / 100 : 0 ); });这里 precipitation 单位是 mm/h系数是经验值。实际项目中要根据粒子系统覆盖范围调试确定。覆盖范围大、相机视角高的时候系数要放大特写视角小范围降雨系数要缩小。5.3 雨雪粒子系统的封装代码粒子系统不能裸写在一个初始化函数里因为业务场景里可能要多次切换降雨区域、切换雨雪类型。我封装成一个WeatherParticleManager内部维护当前激活的粒子系统并提供showRain(area, intensity)、showSnow(area, intensity)、clear()三个核心方法。class WeatherParticleManager { constructor(viewer) { this.viewer viewer; this.rainSystem null; this.snowSystem null; } createEmitter(area, depth 500) { const west area[0][0]; const south area[0][1]; const east area[2][0]; const north area[2][1]; const bottomLeft Cesium.Cartesian3.fromDegrees(west, south, depth); const topRight Cesium.Cartesian3.fromDegrees(east, north, depth * 2); return new Cesium.BoxEmitter( Cesium.Cartesian3.subtract(topRight, bottomLeft, new Cesium.Cartesian3()) ); } showRain(area, intensity) { if (this.snowSystem) this.snowSystem.show false; if (!this.rainSystem) { const emitter this.createEmitter(area); this.rainSystem this.viewer.scene.primitives.add( new Cesium.ParticleSystem({ image: rainTexture, startColor: new Cesium.Color(0.6, 0.7, 0.9, 0.5), endColor: new Cesium.Color(0.6, 0.7, 0.9, 0.0), particleSize: 2.0, imageSize: new Cesium.Cartesian2(4, 32), maximumParticleLife: 1.0, emissionRate: 2000, emitter, minimumSpeed: 20, maximumSpeed: 28, lifetime: 60, modelMatrix: Cesium.Matrix4.IDENTITY }) ); } const targetRate intensity * 160; this.rainSystem.emissionRate Cesium.Math.lerp( this.rainSystem.emissionRate, targetRate, 0.05 ); this.rainSystem.show true; } showSnow(area, intensity) { if (this.rainSystem) this.rainSystem.show false; if (!this.snowSystem) { const emitter this.createEmitter(area, 800); this.snowSystem this.viewer.scene.primitives.add( new Cesium.ParticleSystem({ image: snowTexture, startColor: new Cesium.Color(0.92, 0.95, 1.0, 0.9), endColor: new Cesium.Color(0.92, 0.95, 1.0, 0.0), particleSize: 6.0, imageSize: new Cesium.Cartesian2(24, 24), maximumParticleLife: 4.5, emissionRate: 400, emitter, minimumSpeed: 2.5, maximumSpeed: 5.5, lifetime: 60, modelMatrix: Cesium.Matrix4.IDENTITY }) ); } this.snowSystem.emissionRate intensity * 80; this.snowSystem.show true; } clear() { if (this.rainSystem) this.rainSystem.show false; if (this.snowSystem) this.snowSystem.show false; } }有一个细节值得注意modelMatrix如果设置为平移矩阵发射器位置会随着矩阵平移。我在这里给modelMatrix保持单位矩阵发射器的世界坐标已经通过BoxEmitter的尺寸参数和粒子系统的位置决定。实际上 Cesium 的粒子系统最终位置是modelMatrix乘以发射器坐标的结果所以最灵活的方式是粒子系统挂在一个 Entity 上Entity 的 position 控制系统位置modelMatrix控制旋转缩放。5.4 动态云层材质源码云层材质我在第 4.2 已经给出了 Fabric 定义。把这段材质注册好之后使用方式是这样的const cloudRect viewer.scene.primitives.add( new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: new Cesium.PolygonGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy( Cesium.Cartesian3.fromDegreesArray([ 116.2, 39.6, 116.8, 39.6, 116.8, 40.2, 116.2, 40.2 ]) ), height: 1000, vertexFormat: Cesium.VertexFormat.POSITION_AND_ST, }), }), appearance: new Cesium.MaterialAppearance({ material: Cesium.Material.fromType(DriftingCloud), translucent: true, closed: false, }), }) ); const cloudMaterial viewer.scene.primitives.get(cloudRect).appearance.material;这里云层高度设成 1000 米避免了与地形深度冲突。translucent: true必须设置否则半透明效果会异常。closed: false是因为云层不需要闭合表面减少不必要的背面处理开销。如果想做多方向云层流动可以注册两个不同速度的材质变体或者注册同一个材质但通过 uniform 的speed参数区分。高斯漂移的视觉观感比单一方向平移好很多但对纹理要求也高最好用带 alpha 通道的真实卫星云图否则会出现边缘生硬的问题。5.5 与业务框架的集成方式实际项目里Cesium 几乎不会裸跑都会嵌进 Vue3、React 这类框架。以 Vue3 为例封装方式是把WeatherParticleManager和WeatherDataService都挂到一个组合式函数里// useWeather.js import { onMounted, onUnmounted } from vue; export function useWeather(viewer) { const weatherService new WeatherDataService(/api/weather); const particleManager new WeatherParticleManager(viewer); onMounted(() { weatherService.onUpdate((data) { if (!data.area) { particleManager.clear(); return; } if (data.weather snow) { particleManager.showSnow(data.area.coordinates[0], data.precipitation); } else if (data.weather rain) { particleManager.showRain(data.area.coordinates[0], data.precipitation); } }); weatherService.startPolling(); }); onUnmounted(() { particleManager.clear(); }); }这个封装方式配合 Vue 的响应式系统非常自然。业务组件只需要调用useWeather(viewer)传入气象数据接口地址后面所有事情都交给模块内部处理。6. 踩坑记录与性能优化指南6.1 粒子数量一多就卡顿怎么办粒子系统的瓶颈主要在 CPU 端更新。Cesium 的 ParticleSystem 默认是 CPU 更新粒子状态粒子数量超过 5000 时帧率会明显下降。优化手段有三个方向第一降低粒子数量用贴图复杂度弥补数量不足。每个粒子都用一张带渐变、带形状的贴图即使粒子数量少视觉上也比几百个纯色方块强得多。雨滴用细长贴图、雪花用六角形贴图就是这个原因。第二控制发射范围。如果气象区域非常大没必要在整片区域铺满粒子可以只在使用者相机可视范围内渲染粒子系统。简单做法是相机距离超过某个阈值时降低 emissionRate低于阈值时恢复。第三粒子系统的updateCallback少做复杂计算。Cesium 允许自定义粒子更新回调但如果里面做了大量三角函数、矩阵运算性能会很差。能把计算放到 shader 里做的就不要放在回调里。// 相机距离自适应 viewer.camera.changed.addEventListener(() { const distance Cesium.Cartesian3.distance( viewer.camera.positionWC, rainAreaCenter ); if (distance 30000) { rainParticleSystem.emissionRate 500; } else if (distance 10000) { rainParticleSystem.emissionRate 1500; } else { rainParticleSystem.emissionRate 3000; } });6.2 云层贴到地底下、被地形遮挡这是最常见的穿帮问题。云层矩形 Primitive 是贴在 PolygonGeometry 上的如果height设置过低云会被山峰穿破。解决思路很简单云层高度按覆盖区域内的最大地形高度加一个余量来设置。更稳妥的做法是把云层高度设置为动态高度利用Cesium.sampleTerrainMostDetailed获取区域内的最大地形高度再往上加 500-1000 米。这样即使地形起伏很大云层也能稳定浮在地表之上。const positions Cesium.Cartesian3.fromDegreesArray([...]); const terrainPromise Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [ Cesium.Cartographic.fromCartesian(positions[0]), Cesium.Cartographic.fromCartesian(positions[2]) ]); terrainPromise.then((results) { const maxHeight Math.max(...results.map((r) r.height)); cloudHeight maxHeight 800; // 重新创建云层 Primitive });6.3 动态数据刷新导致闪烁气象数据刷新时粒子系统剧烈变化甚至出现一帧闪烁、粒子消失再出现的现象。这个问题的根源在于我最初是直接销毁重建粒子系统。后面改成复用系统、只更新参数闪烁就消失了。复用粒子系统时要注意几个参数是不能直接改的image、emitter、modelMatrix这些涉及底层资源或者几何结构的参数不能在系统运行中随意改写。我用的是把show设为 false保留系统引用下次需要时再设为 true 并更新emissionRate、startColor等标量参数。6.4 常见问题速查表现象原因解决方案粒子没有显示发射器范围太小或高度设置在地表以下检查 BoxEmitter 的尺寸确认高度为正粒子全部挤在一个点modelMatrix 未设置或发射器创建错误检查 modelMatrix 的单位矩阵和平移逻辑确保发射器尺寸不是零向量雨滴看不出下落感粒子速度太小或粒子纹理方向不正确提高 minimumSpeed/maximumSpeed使用长条形纹理雪花瞬间消失maximumParticleLife 太短加长雪花生命周期到 4-6 秒云层半透明失效没有设置 translucent: true在 appearance 中设置 translucent 属性云层闪烁高度与地形冲突调高云层高度或开启 depthTestAgainstTerrain切换天气类型时顿卡销毁重建粒子系统复用粒子系统用 show 属性切换粒子颜色发灰与 fog 或大气效果叠加关闭 scene.fog或者调整粒子颜色的 alpha这些坑基本都是实际开发中反复踩过的每一条背后都有调试过程。粒子系统这个东西参数联动性很强调一个参数往往会影响其他效果所以调试时最好一次只改一个变量改完看效果再动下一个。配合浏览器性能面板观察帧耗时比肉眼判断更准确。最后再分享一个我实际项目里一直在用的小技巧雨雪粒子系统不要一开始就挂到场景里而是等气象数据第一次返回后再创建。这样页面初始化阶段不会白白消耗性能。云层材质也一样等确定有云层数据时才把 Primitive 添加到场景。这套“按需创建、动态复用”的思路在实际项目里比什么参数调优都管用。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →