前言:
本文基于 Vue3 + Cesium,实现了3D Tiles 城市模型加载,并且可以通过按钮控制是否下雪
实现效果:
- 地图加载完成后,自动飞往预设的城市视角。
- 城市的3D Tiles模型渐进式加载。
- 右上角按钮一键控制打开下雪 / 关闭下雪,下雪的效果自然流畅。
本案例中Cesium版本为1.141.0
演示动态图示如下:

演示地址如下:
核心技术栈:
- Vue3
- CesiumJS(3D 地球)
- 3D Tiles(城市模型加载)
- GLSL 着色器(雪花特效实现)
完整代码
1. template结构:
xml
<template>
<div class="main">
<!-- 地球容器 -->
<div class="content" ref="content" id="earth"></div>
<div class="btn-border" v-if="isLoading">
<el-button type="primary" size="default" class="btn" @click="flyTo">初始位置</el-button>
<!-- 控制是否下雪的开关 -->
<el-button type="primary" size="default" class="btn" @click="snowControl">{{ isSnow ? '关闭下雪' : '下雪' }}</el-button>
</div>
<!-- 加载中提示 -->
<div class="loading" v-if="!isLoading">Loading...</div>
</div>
</template>
2. script代码:
ini
<script setup>
import { onMounted, nextTick, ref, onUnmounted } from 'vue';
import { token } from '../../utils/common.js';
import { ElMessage } from 'element-plus';
// 是否在下雪
let isSnow = ref(false);
// 地图是否在加载中
let isLoading = ref(false);
let myMar = null;
onUnmounted(() => {
// 如果正在下雪,先移除后处理阶段(PostProcessStage)
if (isSnow.value) {
window.viewer.scene.postProcessStages.remove(window.snow);
window.snow = null;
isSnow.value = false;
}
// 销毁 Cesium Viewer 实例,释放资源
if (window.viewer) {
window.viewer.destroy();
window.viewer = null;
}
if (myMar) {
clearTimeout(myMar);
myMar = null;
}
});
// 组件挂载后:初始化地图
onMounted(() => {
nextTick(() => {
initMap();
});
});
// 初始化 Cesium 地图
const initMap = () => {
// 设置 Cesium Ion 的token(这里替换成您的Cesium Ion token)
Cesium.Ion.defaultAccessToken = token;
// 设置默认视角范围(中国区域)
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.5, 20.4, 110.4, 61.2);
// 创建 Viewer 实例
window.viewer = new Cesium.Viewer('earth', {
animation: false, // 关闭动画控件
timeline: false, // 关闭时间轴
infoBox: false, // 关闭信息框
geocoder: false, // 关闭地理编码搜索
homeButton: false, // 关闭主页按钮
sceneModePicker: false, // 关闭场景模式切换
baseLayerPicker: false, // 关闭底图选择器
navigationHelpButton: false, // 关闭导航帮助
fullscreenButton: false, // 关闭全屏按钮
selectionIndicator: false, // 关闭选择指示器
shouldAnimate: false, // 关闭自动播放动画
contextOptions: { // WebGL 上下文配置
webgl: {
powerPreference: "high-performance", // 高性能模式
preserveDrawingBuffer: false // 不保留绘图缓冲(节省内存)
}
}
});
// 设置模拟时间(可选,用于光影效果)
let utc = Cesium.JulianDate.fromDate(new Date('2026/05/02 15:00:00'));
// 加载 3D 模型
addModel();
myMar = setTimeout(() => {
isLoading.value = true;
// 飞往模型位置
flyTo();
}, 6000);
};
// 飞往模型位置的方法
const flyTo = () => {
window.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.38930915426614, 39.90736899736818, 83.88926560891),
orientation: {
heading: Cesium.Math.toRadians(204.08974092232688), // 航向角
pitch: Cesium.Math.toRadians(-12.743790286888126), // 俯仰角
roll: Cesium.Math.toRadians(0.0004915824795912258) // 翻滚角
},
duration: 6 // 飞行时长6秒
});
};
// 加载 3D Tiles 模型
const addModel = async () => {
try {
const tileset = await Cesium.Cesium3DTileset.fromUrl(
// 注:请将下方的 URL 替换为您的 3D Tiles 模型服务地址
'YOUR_3D_TILES_URL / tileset.json',
{
maximumScreenSpaceError: 48, // 屏幕空间误差(越小越精细,越大越省性能)
maximumSimultaneousTileLoads: 16, // 同时加载瓦片数(默认 8,可适当增加)
preloadAncestors: false, // 不预加载祖先瓦片(节省内存)
preloadSiblings: true, // 预加载兄弟瓦片(提升流畅度)
maximumMemoryUsage: 512, // 【关键】内存上限 512MB(防止爆内存)
skipLevelOfDetail: true, // 跳过细节层级(提升加载速度)
baseScreenSpaceError: 1024 // 基础屏幕空间误差(用于 LOD)
}
);
// 将模型添加到场景中
window.tileset = window.viewer.scene.primitives.add(tileset);
} catch (err) {
console.error('3D Tiles 加载失败', err);
}
};
// 控制下雪效果的方法
const snowControl = () => {
// 如果正在下雪,则关闭下雪
if (isSnow.value) {
window.viewer.scene.postProcessStages.remove(window.snow);
window.snow = null;
isSnow.value = false;
} else {
// 否则,创建下雪的着色器(GLSL)相关的代码
const Snow = `
uniform sampler2D colorTexture;
in vec2 v_textureCoordinates;
out vec4 fragColor;
float snow(vec2 uv, float scale) {
float time = czm_frameNumber / 60.0;
float w = smoothstep(1., 0., -uv.y * (scale / 10.));
if (w < .1)
return 0.;
uv += time / scale;
uv.y += time * 2. / scale;
uv.x += sin(uv.y + time * .5) / scale;
uv *= scale;
vec2 s = floor(uv), f = fract(uv), p;
float k = 3., d;
p = .5 + .35 * sin(11. * fract(sin((s + p + scale) * mat2(7, 3, 6, 5)) * 5.)) - f;
d = length(p);
k = min(d, k);
k = smoothstep(0., k, sin(f.x + f.y) * 0.01);
return k * w;
}
void main(void) {
vec2 resolution = czm_viewport.zw;
vec2 uv = (gl_FragCoord.xy * 2. - resolution.xy) / min(resolution.x, resolution.y);
vec3 finalColor = vec3(0);
float c = 0.0;
c += snow(uv, 30.) * .0;
c += snow(uv, 20.) * .0;
c += snow(uv, 15.) * .0;
c += snow(uv, 10.);
c += snow(uv, 8.);
c += snow(uv, 6.);
c += snow(uv, 5.);
finalColor = vec3(c);
vec4 color = texture(colorTexture, v_textureCoordinates);
fragColor = mix(color, vec4(finalColor, 1.0), 0.5);
}
`;
// 创建后处理阶段(下雪效果)
window.snow = new Cesium.PostProcessStage({
name: 'czm_snow', // 阶段名称
fragmentShader: Snow // 自定义着色器
});
// 添加到场景的后处理链中
window.viewer.scene.postProcessStages.add(window.snow);
isSnow.value = true; // 标记正在下雪
}
};
</script>
3. css样式代码:
css
* {
margin: 0;
padding: 0;
}
.main {
width: 100%;
height: 100vh;
position: relative;
}
.content {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
}
.loading {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 3;
display: flex;
justify-content: center;
align-items: center;
font-size: 34px;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
color: #000000;
}
.btn-border {
position: absolute;
right: 24px;
top: 24px;
z-index: 2;
display: flex;
justify-content: start;
align-items: center;
}
.btn {
margin-left: 20px;
cursor: pointer;
}
关键知识点总结:
- Cesium 后处理阶段(PostProcessStage):可实现雨雪、光斑、模糊等全屏特效,不影响 3D 模型本身。
- GLSL 着色器:雪花通过数学函数随机生成,配合时间实现连续飘落动画。
- 3D Tiles 优化:免费工具生成的瓦片可通过
maximumScreenSpaceError等参数优化加载速度。 - Vue3 + Cesium 规范:必须在
onUnmounted销毁实例、清理定时器、移除特效,避免内存泄漏。
模型说明: 文中 3D 城市模型来源于 Sketchfab免费共享库,本人仅作技术演示使用。