前言
我们在基于 Cesium 开发数字孪生、古建筑展示、三维地图等项目时,经常会导入 3DTiles 格式瓦片模型。相比于普通 glTF/GLB 模型,3DTiles 瓦片模型适配超大场景、高精度古建筑、城市园区等,也是目前三维GIS项目主流使用的模型格式。
但绝大多数开发者都会遇到同一个难题:
在cesium中普通模型(gltf/glb)可以直接改位置、角度,而3DTiles 瓦片模型没有 position/rotation 属性,无法直接修改位置与旋转参数。
针对以上痛点,本文将带大家从零搭建一套可视化控制面板。本文通过可视化面板上可调节的参数,精准控制3DTiles模型的离地面高度、绕XYZ三轴旋转角度以及模型经纬度细微偏移,所有参数修改即时生效,实现所见即所得的调试效果。
演示动态图示如下:

演示地址如下:
最终实现效果
本案例基于 Vue3 + ElementPlus + Cesium 开发,右侧独立控制面板包含6项核心调节参数,外加两个辅助功能按钮:
- 模型离地面高度:支持正负数值,正数模型上浮、负数模型下沉
- X轴旋转:控制模型前后俯仰角度,矫正模型前倾、后仰问题
- Y轴旋转:控制模型左右倾斜角度,修复模型侧向歪斜
- Z轴旋转:水平面旋转,用于调整模型朝向
- 经度偏移:东西方向精细化微调
- 纬度偏移:南北方向精细化微调
- 辅助功能:一键重置所有参数、相机复位至模型最佳观测视角
矩阵变换核心原理
Cesium 中所有3DTiles模型调节,底层全部依靠矩阵运算,整体执行流程:
- 模型初始化加载,获取模型原始中心点经纬度、高度,作为调节基准值;
- 监听控制面板参数变化,实时收集离地面高度、xyz轴旋转、经纬度偏移数据;
- 基于ENU(东-北-上)地球坐标系,生成模型平移矩阵、三轴独立旋转矩阵;
- 矩阵合并运算,生成最终变换矩阵;
- 将最终矩阵赋值给瓦片根节点
_root.transform,实时更新模型状态。
Demo整体结构
整个页面拆分为三大模块,分工明确,解耦易维护:
- HTML结构:Cesium渲染容器、功能按钮、参数控制面板;
- CSS样式:全局布局、地球容器样式、右侧悬浮控制面板样式;
- Script逻辑:Cesium初始化、3DTiles加载、矩阵运算、参数监听、辅助功能。
分步编码实现
编写HTML结构
搭建页面基础布局,使用ElementPlus数字输入框双向绑定参数,参数变更触发修改方法。所有输入框支持正负数值,满足模型多角度、多方位调节需求。
xml
<template>
<div class="main">
<!-- Cesium 三维场景渲染容器 -->
<div class="content" ref="content" id="earth"></div>
<!-- 右侧整体控制面板区域 -->
<div class="map-control">
<!-- 功能按钮区:参数重置、视角复位 -->
<div class="map-btn">
<el-button type="primary" @click="restart">参数恢复</el-button>
<el-button type="primary" @click="flyTo">初始位置</el-button>
</div>
<!-- 模型参数调节面板 -->
<div class="model-black">
<!-- 面板标题 -->
<div class="model-tt">设计模型</div>
<!-- 所有参数行布局容器 -->
<div class="model-row">
<!-- 模型离地高度调节 -->
<div class="model-row-tt">高度</div>
<el-input-number class="model-number-input" v-model="heightVal" @change="changeModel" :step="1"></el-input-number>
<!-- X轴旋转调节,限制数值范围 -100 ~ 100 -->
<div class="model-row-tt">X轴旋转</div>
<el-input-number class="model-number-input" v-model="rxVal" @change="changeModel" :step="1" :min="-100" :max="100"></el-input-number>
<!-- Y轴旋转调节,限制数值范围 -100 ~ 100 -->
<div class="model-row-tt">Y轴旋转</div>
<el-input-number class="model-number-input" v-model="ryVal" @change="changeModel" :step="1" :min="-100" :max="100"></el-input-number>
<!-- Z轴旋转调节,无数值范围限制 -->
<div class="model-row-tt">Z轴旋转</div>
<el-input-number class="model-number-input" v-model="rzVal" @change="changeModel"
:step="1"></el-input-number>
<!-- 经度方向平移调节,步长0.1,精细微调 -->
<div class="model-row-tt">经度平移</div>
<el-input-number class="model-number-input" v-model="tLon" @change="changeModel"
:step="0.1"></el-input-number>
<!-- 纬度方向平移调节,步长0.1,精细微调 -->
<div class="model-row-tt">纬度平移</div>
<el-input-number class="model-number-input" v-model="tLat" @change="changeModel"
:step="0.1"></el-input-number>
</div>
</div>
</div>
</div>
</template>
Script逻辑实现(逐模块讲解)
1. 初始化响应式变量
定义双向绑定的参数变量、全局实例变量,用于存储模型状态与Cesium实例。
csharp
import { onMounted, nextTick, ref, onUnmounted } from 'vue';
import { token } from '../../utils/common.js';
import { ElMessage } from 'element-plus';
// 模型调节参数
let heightVal = ref(0); // 模型离地面高度
let rxVal = ref(0); // X轴旋转角度
let ryVal = ref(0); // Y轴旋转角度
let rzVal = ref(0); // Z轴旋转角度
let tLon = ref(0); // 经度偏移量
let tLat = ref(0); // 纬度偏移量
let params = ref({}); // 全局参数存储
// 模型原始基准经纬度
let longitude = ref(0);
let latitude = ref(0);
2. 初始化Cesium场景
初始化Viewer实例,关闭冗余控件、开启高性能渲染,限制默认可视区域为中国范围,减少资源占用。
less
onMounted(() => {
nextTick(() => {
initMap();
});
});
// 初始化Cesium地图
const initMap = () => {
// 设置Cesium官方授权Token,这里填您的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'));
// 调用加载3DTiles模型的方法
addModel();
// 调用相机飞往模型初始位置的方法
flyTo();
};
3. 加载3DTiles模型并获取基准坐标
加载本案例中古建筑模型的3DTiles瓦片,获取模型包围球中心点经纬度,保存为基准坐标,后续所有偏移调节都基于该坐标计算。
javascript
// 加载3DTiles模型
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);
// 获取模型中心点笛卡尔坐标,转换为经纬度
const cartographic = Cesium.Cartographic.fromCartesian(
window.tileset.boundingSphere.center
);
longitude.value = Cesium.Math.toDegrees(cartographic.longitude);
latitude.value = Cesium.Math.toDegrees(cartographic.latitude);
// 初始化模型默认参数
params.value = {
tx: longitude.value,
ty: latitude.value,
tz: cartographic.height,
rx: 0,
ry: 0,
rz: 0
};
// 调初始化模型姿态的方法
changeModel();
} catch (err) {
console.error('3D Tiles 加载失败', err);
}
};
4. 核心:矩阵合成方法
根据当前面板参数,分别生成xyz旋转矩阵,结合经纬度、离地面高度合成最终变换矩阵,是本案例最核心的代码。
ini
// 合成模型变换矩阵
const update3dtilesMaxtrix = () => {
// 1. 将角度转为弧度,生成三轴独立旋转矩阵
let mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(params.value.rx));
let my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(params.value.ry));
let mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(params.value.rz));
// 转换为四维矩阵,用于矩阵合并
let rotationX = Cesium.Matrix4.fromRotationTranslation(mx);
let rotationY = Cesium.Matrix4.fromRotationTranslation(my);
let rotationZ = Cesium.Matrix4.fromRotationTranslation(mz);
// 2. 计算模型最终坐标(基准坐标+偏移量+高度)
let position = Cesium.Cartesian3.fromDegrees(params.value.tx, params.value.ty, heightVal.value);
// 3. 创建ENU坐标系基准矩阵
let m = Cesium.Transforms.eastNorthUpToFixedFrame(position);
// 4. 矩阵叠加:基准位置 + X/Y/Z三轴旋转
Cesium.Matrix4.multiply(m, rotationX, m);
Cesium.Matrix4.multiply(m, rotationY, m);
Cesium.Matrix4.multiply(m, rotationZ, m);
return m;
};
5. 参数监听与模型实时更新
监听输入框参数变化,更新偏移、旋转参数,调用矩阵方法,实时更新模型姿态。经纬度增加阻尼系数,避免偏移幅度过大。
ini
// 参数变更,实时更新模型
const changeModel = () => {
// 更新xyz旋转参数
params.value.rx = rxVal.value;
params.value.ry = ryVal.value;
params.value.rz = rzVal.value;
// 设置阻尼系数500,实现精细化微调,防止位移过大
params.value.tx = longitude.value + tLon.value / 500;
params.value.ty = latitude.value + tLat.value / 500;
// 赋值给瓦片根节点,刷新模型状态
window.tileset._root.transform = update3dtilesMaxtrix();
};
/**
* 模型高度偏移计算方法
* 作用:单独计算模型垂直方向平移矩阵,实现高度升降
*/
const changeHeight = () => {
// 1. 获取3DTiles模型包围球中心点,转为经纬度+高度的笛卡尔坐标对象
const cartographic = Cesium.Cartographic.fromCartesian(
window.tileset.boundingSphere.center
);
// 2. 根据模型中心点经纬度,生成地面基准点(高度为0,贴地位置)
const surface = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude
);
// 3. 基于同一经纬度,生成偏移高度后的目标坐标点
const offset = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
params.value.tz
);
// 4. 计算两个坐标点的差值,得到高度偏移向量
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
// 5. 将偏移向量转为平移矩阵,赋值给模型
window.tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
// 6. 调用矩阵合成方法,同步更新模型整体姿态
update3dtilesMaxtrix();
};
6. 辅助功能方法
封装参数重置、相机复位两个辅助方法,方便开发者快速调试。
ini
// 一键重置所有参数,恢复模型初始状态
const restart = () => {
heightVal.value = 0;
rxVal.value = 0;
ryVal.value = 0;
rzVal.value = 0
tLon.value = 0
tLat.value = 0
changeModel();
};
// 相机复位至模型最佳观测视角
const flyTo = () => {
window.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(121.43908371916447, 31.349189256570035, 2.5532527089816814),
orientation: {
heading: Cesium.Math.toRadians(25.51463348115896),
pitch: Cesium.Math.toRadians(6.338469775354732),
roll: Cesium.Math.toRadians(359.99999787825675)
},
duration: 6 // 飞行动画时长
});
};
css样式代码
css
* {
margin: 0;
padding: 0;
}
.main {
width: 100%;
height: 100vh;
position: relative;
}
.content {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
}
.map-control {
width: 300px;
height: calc(100vh - 60px);
position: absolute;
right: 20px;
top: 20px;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: start;
align-items: start;
box-sizing: border-box;
}
.map-btn {
width: 100%;
display: flex;
justify-content: end;
align-items: center;
}
.model-black {
width: 100%;
flex: 1;
min-height: 0;
margin-top: 30px;
background-color: rgba(31, 31, 31, 0.8);
box-sizing: border-box;
padding: 10px;
}
.model-tt {
width: 100%;
font-size: 18px;
color: #FFF;
height: 50px;
line-height: 50px;
box-sizing: border-box;
padding-left: 14px;
}
.model-row-tt {
width: 100%;
color: #FFF;
margin-top: 20px;
font-size: 16px;
padding-left: 14px;
box-sizing: border-box;
}
.model-number-input {
margin-left: 14px;
margin-top: 14px;
}
参数功能详细解析

模型说明: 文中 3D 城市模型来源于 Sketchfab免费共享库,本人仅作技术演示使用。