游乐游手机版
首页/前端开发/文章详情

Vue3+Cesium实时调节3DTiles模型离地高度、XYZ旋转与经纬度偏移

时间:2026-06-15 06:59
基于Vue3与Cesium开发的可视化控制面板,通过矩阵运算实时调节3DTiles模型的离地面高度、XYZ轴旋转角度及经纬度偏移,实现所见即所得的参数调整,解决了3DTiles无法直接修改位置与旋转属性的痛点。

从事 Cesium 开发的同行都清楚,一旦涉及数字孪生、古建筑三维展示、智慧城市等场景,3DTiles 瓦片模型几乎是必备的数据格式。相比于常规的 glTF 或 GLB,3DTiles 在大规模场景、高精度历史建筑、城市级园区等应用中具备天然优势,目前已成为三维 GIS 领域的主流选择。

然而,大多数开发者在实际调试时,往往会卡在一个关键难题上:

普通的 glTF/GLB 模型在 Cesium 中可以轻松调整位置与姿态,但 3DTiles 瓦片模型并未直接暴露 position / rotation 这类属性,尝试直接修改位置与旋转参数的方法根本行不通。

针对这一痛点,本教程将手把手带你从零搭建一套可视化控制面板。面板上的所有参数均支持实时调整:模型的离地高度、绕 XYZ 三个轴向的旋转角度、经纬度微偏移,全部所见即所得,调整后立即生效,彻底告别反复刷新页面的繁琐流程。

在线演示地址:3d.xiazhi.tech/3d-demos/#/…

最终实现效果

本案例基于 Vue3 + ElementPlus + Cesium 技术栈开发,右侧独立控制面板集成了 6 项核心调参控件,并额外提供两个辅助功能按钮:

  • 模型相对地面高度:正值向上浮动,负值向下沉降,支持输入正负数
  • 绕 X 轴旋转:控制模型前后俯仰,修正前倾或后仰问题
  • 绕 Y 轴旋转:控制模型左右倾斜,校正侧向歪斜
  • 绕 Z 轴旋转:水平面内旋转,调整模型的朝向
  • 经度偏移:东西方向精细微调
  • 纬度偏移:南北方向精细微调
  • 辅助功能:一键恢复所有参数至初始值,以及相机复位至模型最佳观测视角

矩阵变换核心原理

Cesium 中所有 3DTiles 模型的姿态控制,底层均依赖矩阵运算。整体执行流程可归纳为五个步骤:

  1. 模型初次加载时,获取原始中心点的经纬度与高度,作为后续所有调整的基准值;
  2. 监听控制面板的参数变化,实时收集离地高度、XYZ 三轴旋转角度、经纬度偏移量等数据;
  3. 基于 ENU(东-北-上)地球坐标系,分别构建模型的平移矩阵与三轴独立的旋转矩阵;
  4. 将多个矩阵进行级联运算,得出最终的合成变换矩阵;
  5. 将该合成矩阵赋值给瓦片根节点的 _root.transform,模型姿态随即更新。

Demo 整体结构

整个页面拆分为三大模块,职责明确,易于后续维护:

  1. HTML 结构:包含 Cesium 三维渲染容器、功能按钮区域、参数控制面板;
  2. CSS 样式:负责全局布局、地球容器样式、右侧悬浮控制面板的视觉呈现;
  3. Script 逻辑:涵盖 Cesium 初始化、3DTiles 加载、矩阵运算逻辑、参数监听与辅助功能实现。

分步编码实现

编写 HTML 结构

页面基础布局采用 ElementPlus 的数字输入框,通过双向绑定关联各个参数,参数值变化时自动触发更新方法。所有输入框均支持正负数输入,满足模型多角度、多方向的灵活调节需求。

复制代码

Script 逻辑实现(逐模块讲解)

1. 初始化响应式变量

定义双向绑定的参数变量与全局实例变量,用于存储模型状态和 Cesium 引用。

复制代码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);
let ryVal = ref(0);
let rzVal = ref(0);
let tLon = ref(0);
let tLat = ref(0);
let params = ref({});

let longitude = ref(0);
let latitude = ref(0);

2. 初始化 Cesium 场景

初始化 Viewer 实例,关闭多余控件、启用高性能渲染,同时将默认视野范围限制在中国区域,以减少资源开销。

复制代码onMounted(() => {
  nextTick(() => {
    initMap();
  });
});

const initMap = () => {
    Cesium.Ion.defaultAccessToken = token;
    Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(89.5, 20.4, 110.4, 61.2);
    window.viewer = new Cesium.Viewer('earth', {
      animation: false,
      timeline: false,
      infoBox: false,
      geocoder: false,
      homeButton: false,
      sceneModePicker: false,
      baseLayerPicker: false,
      na vigationHelpButton: false,
      fullscreenButton: false,
      selectionIndicator: false,
      shouldAnimate: false,
      contextOptions: {
        webgl: {
          powerPreference: "high-performance",
          preserveDrawingBuffer: false
        }
      }
    });
    let utc = Cesium.JulianDate.fromDate(new Date('2026/05/02 15:00:00'));
    addModel();
    flyTo();
};

3. 加载 3DTiles 模型并获取基准坐标

加载本案例中的古建筑 3DTiles 瓦片,提取模型包围球中心点的经纬度,将其保存为基准坐标,后续所有偏移调节都基于该坐标进行计算。

复制代码const addModel = async () => {
    try {
        const tileset = await Cesium.Cesium3DTileset.fromUrl(
            'YOUR_3D_TILES_URL / tileset.json',
            {
                maximumScreenSpaceError: 48,
                maximumSimultaneousTileLoads: 16,
                preloadAncestors: false,
                preloadSiblings: true,
                maximumMemoryUsage: 512,
                skipLevelOfDetail: true,
                baseScreenSpaceError: 1024
            }
        );
        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 轴的旋转矩阵,再结合经纬度与离地高度合成最终的变换矩阵——这是整个案例最关键的环节。

复制代码    const update3dtilesMaxtrix = () => {
        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);
        let position = Cesium.Cartesian3.fromDegrees(params.value.tx, params.value.ty, heightVal.value);
        let m = Cesium.Transforms.eastNorthUpToFixedFrame(position);
        Cesium.Matrix4.multiply(m, rotationX, m);
        Cesium.Matrix4.multiply(m, rotationY, m);
        Cesium.Matrix4.multiply(m, rotationZ, m);
        return m;
    };

5. 参数监听与模型实时更新

监听输入框的数值变化,实时更新偏移量与旋转角度,然后调用矩阵合成方法,即时刷新模型姿态。其中经纬度偏移添加了阻尼系数,避免幅度过大导致模型飞离视野。

复制代码const changeModel = () => {
    params.value.rx = rxVal.value;
    params.value.ry = ryVal.value;
    params.value.rz = rzVal.value;
    params.value.tx = longitude.value + tLon.value / 500;
    params.value.ty = latitude.value + tLat.value / 500;
    window.tileset._root.transform = update3dtilesMaxtrix();
};

const changeHeight = () => {
   const cartographic = Cesium.Cartographic.fromCartesian(
     window.tileset.boundingSphere.center
   );
   const surface = Cesium.Cartesian3.fromRadians(
     cartographic.longitude,
     cartographic.latitude
   );
   const offset = Cesium.Cartesian3.fromRadians(
     cartographic.longitude,
     cartographic.latitude,
     params.value.tz
   );
   const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
   window.tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
   update3dtilesMaxtrix();
};

6. 辅助功能方法

提供参数重置与相机复位两个辅助函数,方便开发者快速恢复初始状态。

复制代码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 样式代码

复制代码* {
    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 免费共享资源库,本文仅用作技术演示。

来源:https://juejin.cn/post/7649591353739788323
上一篇Vue 3 PDF工具站生产环境8个踩坑经验 下一篇Vue 3 Teleport报错解析:从patch时机到defer属性
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Vue应用中异步更新性能问题的优化策略详解
前端开发 · 2026-07-03

Vue应用中异步更新性能问题的优化策略详解

先来看一个令许多开发者感到困惑的场景:明明修改了数据,DOM 却“毫无反应”,无法获取最新的高度,也无法计算正确的坐标。这并非 Vue 的缺陷,反而是它精心设计的性能优化策略。核心在于——你需要学会与它“异步更新”的特性协作,而非硬碰硬。 所谓的“异步更新性能问题”,本质上是一种认知偏差。Vue 的

如何避免原型对象挂载大体积动态数组内存污染
前端开发 · 2026-07-03

如何避免原型对象挂载大体积动态数组内存污染

原型链上的大数组:一个隐蔽的内存冲击波 先给个核心判断:直接在原型对象上挂载一个大体积动态数组,这既不是传统意义上的内存“污染”,也不是安全漏洞那种“污染”,而是一种相当隐蔽但后果严重的内存管理失当。它会导致所有实例共享同一份数据,而且正因为生命周期跟整个原型链绑定得太紧,垃圾回收器(GC)根本看不

利用堆栈信息精准定位显式绑定错误对象致未定义异常
前端开发 · 2026-07-03

利用堆栈信息精准定位显式绑定错误对象致未定义异常

深入追踪:显式绑定传错对象引发的未定义异常 说实话,这类问题在JavaScript开发中相当常见——显式绑定传错了对象,然后方法执行时静默失败、访问undefined、或者抛出TypeError。但真正的难点不在于“报了什么错”,而在于“到底是哪个对象被绑错了”。要解决它,需要跳出堆栈的表层报错信息

ES模块中默认导出和具名导出的执行上下文
前端开发 · 2026-07-03

ES模块中默认导出和具名导出的执行上下文

export default 与具名导出在 ES Module 中的行为机制截然不同,核心差异不在于“值如何传递”,而在于绑定如何建立以及导入时如何使用。先给出总结性结论,再逐一详细拆解。 export default 是一种语法糖,而非真正的变量声明 这种设计容易引起误解。实际上,export d

详解HTML中iframe标签loading=lazy属性实现嵌入内容懒加载方法
前端开发 · 2026-07-03

详解HTML中iframe标签loading=lazy属性实现嵌入内容懒加载方法

先聊聊 loading= "lazy " 这个属性——它本意是让 iframe 实现延迟加载,但实际落地时常常“失效”。这并非程序漏洞,而是浏览器内置的防御机制:只有所有条件同时触发,它才会真正推迟资源请求。比如 src 必须是跨域地址(类似 https: widget example com emb