Vue3 + Cesium 实现热气球第一人称自动飞行(支持手机端)
前言
大家好,今天给大家带来一个Cesium 实现的热气球第一人称飞行的完整 Demo。
整个项目基于 Vue3 + Cesium 开发,实现了:
-
热气球沿预定路线自动飞行
-
第一人称视角跟随
-
循环飞行
-
完美支持手机端 / PC 端
本案例中Cesium版本为1.141.0
演示图示如下:
演示地址如下:
[](web3d-demo-collection (xiazhi.tech))
完整代码
1. template 结构:
<template>
<div class="main">
<!-- 地图主容器 -->
<div class="content" ref="content" id="earth"></div>
<!-- Loading 遮罩 -->
<div class="loading" v-if="!isLoading">Loading...</div>
</div>
</template>
2. script 代码:
<script setup>
import { onMounted, nextTick, ref, onUnmounted } from 'vue';
import { token } from '../../utils/common.js';
// 飞行路径数据(经纬度 + 高度 + 时间)
const pathData = ref([
{ longitude: 121.168742, latitude: 31.296495, height: 3400, time: 0 },
{ longitude: 121.807188, latitude: 30.976068, height: 3400, time: 720 }
]);
// 是否加载完成(控制 Loading)
let isLoading = ref(false);
let myMar = null;
onMounted(() => {
nextTick(() => {
// DOM 准备好后再初始化地图
initMap();
});
});
onUnmounted(() => {
// 移除相机更新监听
window.viewer.scene.preUpdate.removeEventListener(adjustVoid);
// 销毁 Cesium Viewer,释放 WebGL 资源
if (window.viewer) {
window.viewer.destroy();
window.viewer = null;
}
// 清除定时器
if (myMar) {
clearTimeout(myMar);
myMar = null;
}
});
// 初始化地图的方法
const initMap = () => {
Cesium.Ion.defaultAccessToken = token;
// 设置默认相机视角(中国区域)
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.5, 20.4, 110.4, 61.2);
// 创建 Cesium Viewer
window.viewer = new Cesium.Viewer('earth', {
animation: true, // 开启动画控件,播放/暂停(必须为true)
timeline: true, // 开启显示时间轴(必须为true)
infoBox: false, // 关闭点击信息弹窗
geocoder: false, // 关闭地名搜索
homeButton: false, // 关闭返回首页按钮
sceneModePicker: false, // 关闭 2D/3D 切换
baseLayerPicker: false, // 关闭底图选择器
navigationHelpButton: false, // 关闭操作帮助
fullscreenButton: false, // 关闭全屏按钮
selectionIndicator: false, // 关闭选中高亮
shouldAnimate: true // 开启允许时间轴自动推进(必须为true)
});
// 设置 Cesium 时间为 2026-05-02 15:00
let utc = Cesium.JulianDate.fromDate(new Date('2026/05/02 15:00:00'));
// 转为北京时间(+8 小时)
window.viewer.clock.currentTime = Cesium.JulianDate.addHours(utc, 8, new Cesium.JulianDate());
// / 时间流速(12 倍速),数字越大时间过的越快
window.viewer.clock.multiplier = 12;
// 调用添加飞机模型的方法
addPlaneModel();
};
// // 计算飞行路径(时间 → 位置),把路径点变成Cesium识别的路线。
const computePath = (source) => {
// 创建时间 → 位置 采样器
let property = new Cesium.SampledPositionProperty();
for (let i = 0; i < source.length; i++) {
// 根据时间偏移量计算 JulianDate
let time = Cesium.JulianDate.addSeconds(window.start, source[i].time, new Cesium.JulianDate);
// 经纬度转笛卡尔坐标
let position = Cesium.Cartesian3.fromDegrees(source[i].longitude, source[i].latitude, source[i].height);
// 添加时间-位置采样
property.addSample(time, position);
}
return property;
};
// 添加飞机实体
const addPlaneModel = () => {
// 飞行开始时间
window.start = Cesium.JulianDate.fromDate(new Date('2026/05/02 15:00:00'));
// 飞行结束时间
window.stop = Cesium.JulianDate.addSeconds(window.start, 720, new Cesium.JulianDate());
// 设置时钟
window.viewer.clock.startTime = window.start.clone();
window.viewer.clock.currentTime = window.start.clone();
window.viewer.clock.stopTime = window.stop.clone();
// 时间轴缩放到飞行区间
window.viewer.timeline.zoomTo(window.start, window.stop);
// 时间结束后循环
window.viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
// 计算飞行轨迹
let property = computePath(pathData.value);
// 创建飞机实体
window.plane = {
availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
start: window.start,
stop: window.stop
})]),
position: property,
orientation: new Cesium.VelocityOrientationProperty(property),
model: {
uri: 'models/airBalloon.glb',
minimumPixelSize: 1,
scale: 2.2
}
};
// 添加到场景中
window.viewer.entities.add(window.plane);
// 6 秒后关闭 Loading,启动第一人称
myMar = setTimeout(() => {
isLoading.value = true;
window.viewer.scene.preUpdate.addEventListener(adjustVoid);
}, 6000);
};
// 第一人称视角跟随
const adjustVoid = () => {
// 只有在播放状态且有飞机实体时才执行
if (window.viewer.clock.shouldAnimate === true && window.plane) {
// 获取当前时刻飞机位置
let center = window.plane.position.getValue(
window.viewer.clock.currentTime
);
// 获取当前时刻飞机朝向
let orientation = window.plane.orientation.getValue(
window.viewer.clock.currentTime
);
// 四元数 → 旋转矩阵
var mtx3 = Cesium.Matrix3.fromQuaternion(orientation);
// 旋转 + 平移 → 模型矩阵
var mtx4 = Cesium.Matrix4.fromRotationTranslation(mtx3, center);
// 提取航向、俯仰、滚转
var hpr = Cesium.Transforms.fixedFrameToHeadingPitchRoll(mtx4);
// 当前航向角
const headingTemp = hpr.heading;
// 当前俯仰角
const pitchTemp = hpr.pitch;
// 调整视角(右偏 90°,下压 12°)
const heading = Cesium.Math.toRadians(Cesium.Math.toDegrees(headingTemp) + 90);
const pitch = Cesium.Math.toRadians(Cesium.Math.toDegrees(pitchTemp) - 12);
// 相机距离
const range = 200.0;
// 锁定相机到飞机第一人称视角
window.viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, range));
}
};
</script>
3.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: 2;
display: flex;
justify-content: center;
align-items: center;
font-size: 34px;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
color: #000000;
}
核心功能总结:
1. 飞行路线
通过 SampledPositionProperty 将经纬度 + 时间绑定,实现自动沿路线飞行。
2. 第一人称跟随
本例代码中的核心方法:adjustVoid
- 每帧获取热气球位置与朝向
- 计算旋转矩阵
- 提取航向 / 俯仰
- 用
lookAt锁定相机
3. 自动播放
- shouldAnimate: true
clockRange: LOOP_STOP循环播放multiplier = 12控制飞行速度
模型版权声明
本文演示所用热气球 3D 模型来源于 Sketchfab:
Hot Air Balloon by Anton Krupnov
许可协议:CC BY 4.0 International
模型原始链接:sketchfab.com/3d-models/h…
说明:本人仅用于技术演示,未对模型本身做任何修改