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

【Vue3】Cesium实现雨雪效果

一、代码思路

1、📁项目结构 

├─ src
│  ├─ assets
│  │  ├─ snowflake_particle.png   // 雪花贴图
│  │  └─ circular_particle.png    // 雨滴贴图
│  └─ components
│     └─ ParticleWeather.vue        // 本文主角
└─ index.html

整个组件只有 一个文件,把它粘进任何 Vite / Vue3 工程即可运行。

2、🎨模板区:一张画布 + 一块控制面板

<div id="cesiumContainer" style="width: 100%; height: 100%;"></div>
  • 这就是 Cesium 官方要求的“挂载点”,ID 必须叫 cesiumContainer,后续 new Cesium.Viewer('cesiumContainer') 会把整个 WebGL 地球塞进去。

<select v-model="weatherType" @change="changeWeather"><option value="snow">雪</option><option value="rain">雨</option><option value="none">无</option>
</select>
  • Vue3 的 v-model 把下拉框与 weatherType 响应式绑定;

  • @change="changeWeather" 只要切选项,就会自动销毁旧粒子系统并重建新的。

3、🚀Cesium 初始化:最简但够用

viewer = new Cesium.Viewer('cesiumContainer', {terrain: undefined,            // 不加载地形,省显存baseLayerPicker: false,        // 右上角图层选择器不要homeButton: false,             // 主页按钮不要animation: false,              // 时间轴控件不要...
});
scene = viewer.scene;

把配置项全部关掉,只保留“地球本体 + 星空 + 大气”。
随后立刻把相机拉到 正俯视 视角:

scene.camera.setView({destination: Cesium.Cartesian3.fromDegrees(0, 0, 10_000_000),orientation: { heading: 0, pitch: -90, roll: 0 }
});
  • fromDegrees(lon, lat, height) 把经纬度/高程变成三维世界坐标;

  • heading = 0 代表机头朝北;pitch = -90 代表相机镜头朝下 90°;

  • 高度 10 000 km 正好能看到完整地球。

4、✨天气粒子系统:一次讲透 7 个核心参数

4.1 ❄️雪
scene.primitives.add(new Cesium.ParticleSystem({modelMatrix: Matrix4.fromTranslation(scene.camera.position),emitter: new Cesium.SphereEmitter(snowRadius),emissionRate: 7000,...updateCallback: snowUpdate})
)
  1. modelMatrix
    把粒子系统原点锁在 相机当前位置,因此无论用户怎么飞,雪都始终在下,实现“跟随视角”。

  2. SphereEmitter(radius)
    在半径 100 km 的球里随机吐雪花,看起来就像“漫天飞雪”。

  3. emissionRate = 7000
    每秒钟吐 7000 片雪花。改到 20 000 就会变成鹅毛大雪。

  4. minimumImageSize / maximumImageSize
    雪花最小 12×12 px,最大 24×24 px,随机变化,避免单调。

4.2 ❄️雪粒子更新回调 snowUpdate
snowGravityScratch = Cesium.Cartesian3.normalize(particle.position, snowGravityScratch);
Cesium.Cartesian3.multiplyByScalar(snowGravityScratch,Cesium.Math.randomBetween(-30, -300),snowGravityScratch
);
particle.velocity = Cesium.Cartesian3.add(particle.velocity,snowGravityScratch,particle.velocity
);
  • 先把当前粒子位置 归一化 得到“向下”的单位向量(地球是球,向下方向各点不同);

  • 乘一个 −30 ~ −300 的随机数,让雪花有快有慢;

  • 叠加到 velocity,实现重力加速度。

透明度根据 距相机距离 衰减:

const distance = Cartesian3.distance(scene.camera.position, particle.position);
particle.endColor.alpha = 1 / (distance / snowRadius + 0.1);

越靠近相机越不透明,越远离越透明,营造“近大远小”的体积感。

4.3 🌧️雨

雨滴贴图是长条形,所以:

JavaScript

复制

imageSize: new Cartesian2(15, 30)

雨滴下降速度固定 −1050 m/s(比雪快得多),其余思路与雪完全一致。

4.4 🌧️场景氛围
scene.skyAtmosphere.hueShift = -0.8;
scene.skyAtmosphere.saturationShift = -0.7;
  • 整体色调偏冷(雪天);

  • 雾效 fog.density 增加,远景被白雾吞噬,氛围感 +1。

5、📸相机控制:一键回到中国

viewer.camera.flyTo({destination: Cartesian3.fromDegrees(104, 30, 5_000_000),orientation: {heading: 0,pitch: Cesium.Math.toRadians(-60),roll: 0},duration: 2
})
  • flyTo 会带 2 秒平滑动画;

  • 104°E, 30°N 大致是中国中心;

  • 高度 5000 km,向下 60° 能看到东亚全貌。

6、🧹生命周期 & 资源清理

onMounted(initViewer);
onUnmounted(() => viewer.destroy());
  • viewer.destroy() 会把 WebGL 上下文、DOM、事件监听一次性销毁,避免内存泄漏;

  • 在 SPA 里来回切路由时尤其重要。

二、🌍工程实现

🛰️准备 Cesium 轨迹文件

官方地址:https://github.com/CesiumGS/cesium/tree/main/Apps/SampleData

下载circular_particle.png和snowflake_particle.png,将文件存放到工程的src\assets文件夹下

🛰️编写核心代码

创建src\components\ParticleWeather.vue,代码如下

<template><div style="width: 100%; height: 100vh; margin: 0; padding: 0; overflow: hidden;"><div id="cesiumContainer" style="width: 100%; height: 100%;"></div><div style="position: absolute; top: 20px; right: 20px; z-index: 100; background: rgba(0, 0, 0, 0.7); padding: 10px; color: white;"><h3>天气效果控制</h3><div style="margin-bottom: 10px;"><label>天气类型:</label><select v-model="weatherType" @change="changeWeather" style="margin-left: 5px;"><option value="snow">雪</option><option value="rain">雨</option><option value="none">无</option></select></div><button @click="resetCamera" style="margin-top: 10px; padding: 5px 10px; background: #4CAF50; color: white; border: none; cursor: pointer;">重置相机</button></div></div>
</template><script setup>
import { onMounted, onUnmounted, ref } from "vue";
import * as Cesium from "cesium";let viewer = null;
let scene = null;
const weatherType = ref('snow');// Cesium Ion 访问令牌(您需要替换为自己的令牌)
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxM2M4ZTg1Ni0zYWFkLTRhMzMtYTE4My05MmZjNmY2YjAxNWYiLCJpZCI6MzI0MzUyLCJpYXQiOjE3NTMyODExOTJ9.FTPTi9u7zGDoZNOEeUq7kQxGEN2sn9NQuxGEY5bZAcI';// 雪粒子相关配置
const snowParticleSize = 12.0;
const snowRadius = 100000.0;
const minimumSnowImageSize = new Cesium.Cartesian2(snowParticleSize,snowParticleSize,
);
const maximumSnowImageSize = new Cesium.Cartesian2(snowParticleSize * 2.0,snowParticleSize * 2.0,
);
let snowGravityScratch = new Cesium.Cartesian3();// 雪粒子更新回调函数
const snowUpdate = function (particle, dt) {// 确保粒子位置有效if (!particle.position || isNaN(particle.position.x) || isNaN(particle.position.y) || isNaN(particle.position.z)) {return;}try {snowGravityScratch = Cesium.Cartesian3.normalize(particle.position,snowGravityScratch,);Cesium.Cartesian3.multiplyByScalar(snowGravityScratch,Cesium.Math.randomBetween(-30.0, -300.0),snowGravityScratch,);particle.velocity = Cesium.Cartesian3.add(particle.velocity,snowGravityScratch,particle.velocity,);// 根据距离相机的距离调整粒子透明度const distance = Cesium.Cartesian3.distance(scene.camera.position,particle.position,);if (distance > snowRadius) {particle.endColor.alpha = 0.0;} else {particle.endColor.alpha = 1.0 / (distance / snowRadius + 0.1);}} catch (error) {// 忽略归一化错误console.warn('Snow particle update error:', error);}
};// 雨粒子相关配置
const rainParticleSize = 15.0;
const rainRadius = 100000.0;
const rainImageSize = new Cesium.Cartesian2(rainParticleSize,rainParticleSize * 2.0,
);
let rainGravityScratch = new Cesium.Cartesian3();// 雨粒子更新回调函数
const rainUpdate = function (particle, dt) {// 确保粒子位置有效if (!particle.position || isNaN(particle.position.x) || isNaN(particle.position.y) || isNaN(particle.position.z)) {return;}try {rainGravityScratch = Cesium.Cartesian3.normalize(particle.position,rainGravityScratch,);rainGravityScratch = Cesium.Cartesian3.multiplyByScalar(rainGravityScratch,-1050.0,rainGravityScratch,);particle.position = Cesium.Cartesian3.add(particle.position,rainGravityScratch,particle.position,);// 根据距离相机的距离调整粒子透明度const distance = Cesium.Cartesian3.distance(scene.camera.position,particle.position,);if (distance > rainRadius) {particle.endColor.alpha = 0.0;} else {particle.endColor.alpha = Cesium.Color.BLUE.alpha / (distance / rainRadius + 0.1);}} catch (error) {// 忽略归一化错误console.warn('Rain particle update error:', error);}
};// 重置相机位置到中国上空
const resetCameraFunction = function () {if (scene) {// 使用flyTo动画平滑过渡到目标位置,确保用户能看到地球viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(104.0, 30.0, 5000000), // 中国中心位置,高度5000公里orientation: {heading: Cesium.Math.toRadians(0.0), // 0度朝向北方pitch: Cesium.Math.toRadians(-60.0), // 向下60度roll: 0.0},duration: 2.0, // 飞行持续时间2秒complete: function() {console.log('Camera position reset to:', scene.camera.position.toString());}});}
};// 开始下雪效果
const startSnow = function () {if (!scene) return;// 移除所有现有粒子系统scene.primitives.removeAll();try {// 添加雪粒子系统scene.primitives.add(new Cesium.ParticleSystem({modelMatrix: new Cesium.Matrix4.fromTranslation(scene.camera.position),minimumSpeed: -1.0,maximumSpeed: 0.0,lifetime: 15.0,emitter: new Cesium.SphereEmitter(snowRadius),startScale: 0.5,endScale: 1.0,image: '/src/assets/snowflake_particle.png',emissionRate: 7000.0,startColor: Cesium.Color.WHITE.withAlpha(0.0),endColor: Cesium.Color.WHITE.withAlpha(1.0),minimumImageSize: minimumSnowImageSize,maximumImageSize: maximumSnowImageSize,updateCallback: snowUpdate,}),);// 调整天空大气层效果以增强雪景scene.skyAtmosphere.hueShift = -0.8;scene.skyAtmosphere.saturationShift = -0.7;scene.skyAtmosphere.brightnessShift = -0.33;scene.fog.density = 0.001;scene.fog.minimumBrightness = 0.8;} catch (error) {console.error('Failed to start snow effect:', error);}
};// 开始下雨效果
const startRain = function () {if (!scene) return;// 移除所有现有粒子系统scene.primitives.removeAll();try {// 添加雨粒子系统scene.primitives.add(new Cesium.ParticleSystem({modelMatrix: new Cesium.Matrix4.fromTranslation(scene.camera.position),speed: -1.0,lifetime: 15.0,emitter: new Cesium.SphereEmitter(rainRadius),startScale: 1.0,endScale: 0.0,image: '/src/assets/circular_particle.png',emissionRate: 9000.0,startColor: new Cesium.Color(0.27, 0.5, 0.7, 0.0),endColor: new Cesium.Color(0.27, 0.5, 0.7, 0.98),imageSize: rainImageSize,updateCallback: rainUpdate,}),);// 调整天空大气层效果以增强雨景scene.skyAtmosphere.hueShift = -0.97;scene.skyAtmosphere.saturationShift = 0.25;scene.skyAtmosphere.brightnessShift = -0.4;scene.fog.density = 0.00025;scene.fog.minimumBrightness = 0.01;} catch (error) {console.error('Failed to start rain effect:', error);}
};// 清除所有天气效果
const clearWeather = function () {if (!scene) return;// 移除所有粒子系统scene.primitives.removeAll();// 重置天空大气层和雾效scene.skyAtmosphere.hueShift = 0.0;scene.skyAtmosphere.saturationShift = 0.0;scene.skyAtmosphere.brightnessShift = 0.0;scene.fog.density = 0.00001;scene.fog.minimumBrightness = 0.0;
};// 切换天气效果
const changeWeather = function () {switch (weatherType.value) {case 'snow':startSnow();break;case 'rain':startRain();break;case 'none':clearWeather();break;}
};// 重置相机
const resetCamera = function () {resetCameraFunction();// 重置相机位置后重新创建粒子系统,使其随相机移动changeWeather();
};// 初始化 Cesium 查看器
const initViewer = () => {try {// 最简化的配置,仅保留必要功能viewer = new Cesium.Viewer('cesiumContainer', {// 不加载地形,减少复杂性terrain: undefined,shouldAnimate: true,// 不指定特定的影像提供商,使用默认值baseLayerPicker: false,homeButton: false,sceneModePicker: false,navigationHelpButton: false,infoBox: false,fullscreenButton: false,animation: false,timeline: false});scene = viewer.scene;// 确保地球可见的最基本配置scene.globe.show = true; // 显式设置地球可见// 禁用深度测试,简化渲染流程scene.globe.depthTestAgainstTerrain = false;console.log('Cesium viewer initialized with minimal configuration');// 立即设置相机位置,不使用动画scene.camera.setView({destination: Cesium.Cartesian3.fromDegrees(0.0, 0.0, 10000000), // 地球中心位置,高度10000公里orientation: {heading: 0.0,pitch: -90.0,roll: 0.0}});console.log('Camera position set to:', scene.camera.position.toString());// 延迟启动粒子系统,确保地球先显示出来setTimeout(() => {startSnow();}, 1000);// 初始化雪效果startSnow();// 监听相机移动事件,使粒子系统随相机移动viewer.scene.camera.changed.addEventListener(() => {if (weatherType.value !== 'none') {changeWeather();}});} catch (error) {console.error('Failed to initialize Cesium viewer:', error);}
};// 生命周期钩子
onMounted(() => {initViewer();
});onUnmounted(() => {if (viewer) {viewer.destroy();viewer = null;scene = null;}
});
</script>

参考:Cesium Sandcastle

http://www.dtcms.com/a/352534.html

相关文章:

  • onnx入门教程(五)——实现 PyTorch-ONNX 精度对齐工具
  • 子串:和为K的子数组
  • 高并发内存池(7)- CentralCache的核心设计
  • 如何对springboot mapper 编写单元测试
  • MATLAB Figure画布中绘制表格详解
  • Cortex-M 的Thumb指令集?
  • k8s pod 启动失败 Failed to create pod sandbox
  • Il2CppInspector 工具linux编译使用
  • 算法概述篇
  • Markdown渲染引擎——js技能提升
  • MyBatis-Flex是如何避免不同数据库语法差异的?
  • 【electron】一、安装,打包配置
  • 全面赋能政务领域——移动云以云化升级推动政务办公效能跃迁
  • 【硬件-笔试面试题-61】硬件/电子工程师,笔试面试题(知识点:RC电路中的充电时间常数)
  • vue3 + jsx 中使用native ui 组件插槽
  • babel使用及其底层原理介绍
  • Java 集合笔记
  • 第二章 进程与线程
  • 简明 | Yolo-v3结构理解摘要
  • Python-机器学习概述
  • ruoyi-vue(十二)——定时任务,缓存监控,服务监控以及系统接口
  • Python 轻量级的 ORM(对象关系映射)框架 - Peewee 入门教程
  • CentOS 7 升级 OpenSSH 10.0p2 完整教程(含 Telnet 备份)
  • 性能瓶颈定位更快更准:ARMS 持续剖析能力升级解析
  • 告别繁琐运维,拥抱自动化:EKS Auto Mode 实战指南
  • C代码学习笔记(二)
  • RK3506 开发板:嵌入式技术赋能多行业转型升级
  • 大数据时代UI前端的智能化升级路径:基于用户行为数据的预测性分析
  • PMP项目管理知识点-⑨项⽬资源管理
  • 大模型应用编排工具Dify之插件探索