返回 导航

Vue.js

hangge.com

Vue.js - 集成Cesium构建三维可视化场景教程8(上传模型制作3D Tiles)

作者:hangge | 2026-07-14 08:48
    Cesium 作为面向海量三维地理数据的可视化引擎,其核心优势之一就在于 3D Tiles 这一面向流式加载的开放规范。通过将原始三维模型切片为 3D Tiles,不仅可以显著提升大规模场景的加载效率,还能根据视距动态加载不同层级的细节,为 Web 端三维应用提供良好的性能与体验。
    前文我演示了如何加载使用 Cesium ion 上提供的各种 3D Tiles 数据(点击查看)。但在实际项目中,我们常常需要将自己的摄影测量 / 建模 / 点云数据变成可以在 Cesium 中流式加载的 3D Tiles,下面演示如何上传模型到 Cesium ion 生成 3D Tiles

八、上传模型到 Cesium ion 生成 3D Tiles

1,制作切片(3D Tiles)

(1)假设我们有一个科技馆的 glb 模型,需要制作成 3D Tiles 并在 Cesium 中进行加载显示。

(2)登录 Cesium ion,进入“My Assets” 页面后点击“Add data”按钮。

(3)接着选择“Add files...”,然后在弹出框中选择我们需要上传的模型数据,包括:
  • 倾斜摄影(OSGB / OBJ / FBX
  • BIMIFC
  • GLB / Gltf
  • 点云(LAS / LAZ

(4)文件选择完毕后出现参数配置页面,类型选择“3D Modeltile as 3D Tiles)”后点击“Upload”按钮。
  • 如果 obj + mtl 格式的模型数据,我们也可以直接将整个文件夹拖进来。
  • 可以看到文件夹内所有的文件都添加了进来。同样将类型选择“3D Modeltile as 3D Tiles)”后点击“Upload”按钮。

(5)随后模型文件就会开始上传,并且自动将其转换为 3D Tiles

2,调整切片角度

(1)切片生成完毕后我们可能会发现预览框里面的模型是立起来的,需要调整角度。点击“Adjust Tileset Location”按钮

(2)然后在编辑页面中点击“Next”按钮。

(3)接着调整下旋转角度是模型平躺下来,然后点击“Save”按钮进行保存。
提示:如果需要让模型与平面地图进行融合,可以再设置一下经纬度和海拔高度。

(4)保存后在预览框可以看到模型角度已经摆正了。

3,加载切片

(1)当切片制作完毕后,我们在资源表格中找到该资源的 Asset ID

(2)最后我们代码中通过 Asset ID 加载进来即可。
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(4396993)
viewer.scene.primitives.add(tileset)
viewer.zoomTo(tileset)

(3)下面是完整的使用样例(这里我关闭了底图和地形的显示,只展示 3D Tiles):
<template>
    <div class="layer-container">
        <div ref="cesiumContainer" class="cesium-container"></div>

        <!-- 加载状态 -->
        <div v-if="loading" class="loading-overlay">
            <div class="loading-spinner"></div>
            <p>加载三维场景中...</p>
        </div>
    </div>
</template>

<script>
// 引入 Cesium 核心类
import * as Cesium from "cesium";

export default {
    name: 'CesiumViewer',

    data() {
        return {
            viewer: null,
            loading: true,
            terrainEnabled: true,
        }
    },

    mounted() {
        // 确保 Cesium 已加载
        if (typeof Cesium === 'undefined') {
            console.error('Cesium 未加载,请检查配置')
            this.loading = false
            return
        }

        // 初始化 Cesium 场景
        this.initCesium()
    },

    beforeDestroy() {
        // 组件销毁时清理资源
        this.destroyCesium()
    },

    methods: {
        // 初始化 Cesium
        async initCesium() {
            try {
                // 配置 Cesium 访问令牌
                Cesium.Ion.defaultAccessToken = '你的token'

                // 创建 Viewer 实例
                this.viewer = new Cesium.Viewer(this.$refs.cesiumContainer, {
                    animation: false, // 是否显示动画控件
                    baseLayerPicker: false, // 是否显示图层选择控件
                    fullscreenButton: false, // 是否显示全屏按钮
                    geocoder: false, // 是否显示地理编码搜索控件
                    homeButton: false, // 是否显示Home按钮
                    infoBox: false, // 是否显示信息框
                    sceneModePicker: false, // 是否显示3D/2D选择器
                    selectionIndicator: false, // 是否显示选取指示器组件
                    timeline: false, // 是否显示时间轴
                    navigationHelpButton: false, // 是否显示帮助按钮
                    scene3DOnly: true, // 每个几何实例仅以3D渲染以节省GPU内存
                    shouldAnimate: true, // 自动播放动画
                    imageryProvider: false, // 不加载影像 
                    terrainProvider: new Cesium.EllipsoidTerrainProvider(), //数学椭球不请求地形数据
                })

                const scene = this.viewer.scene
                // 彻底关闭天空盒(有些版本必须用 undefined)
                scene.skyBox = undefined
                // 彻底关闭大气
                scene.skyAtmosphere = undefined
                // 关闭雾效(否则仍可能偏蓝)
                scene.fog.enabled = false
                // 如果 globe 存在,也关掉
                scene.globe && (scene.globe.show = false)
                // 设置背景色(最后设置)
                scene.backgroundColor = Cesium.Color.fromCssColorString('#EDF2FA')

                // 强制重新计算画布尺寸
                this.viewer.canvas.width = this.viewer.container.clientWidth;
                this.viewer.canvas.height = this.viewer.container.clientHeight;
                this.viewer.resize(); // 核心方法:告知 Cesium 容器大小已变

                // 加载自定义的切片数据
                const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(4396993)
                this.viewer.scene.primitives.add(tileset)
                this.viewer.zoomTo(tileset)

                // 加载完成
                this.loading = false

                console.log('Cesium 场景初始化完成')
            } catch (error) {
                console.error('初始化 Cesium 失败:', error)
                this.loading = false
            }
        },

        // 设置初始视图
        setInitialView() {
            // 设置初始位置(例如:北京天安门)
            const initialPosition = Cesium.Cartesian3.fromDegrees(116.3974, 39.9093, 1500)
            this.viewer.camera.setView({
                destination: initialPosition,
                orientation: {
                    heading: Cesium.Math.toRadians(0),
                    pitch: Cesium.Math.toRadians(-90),
                    roll: 0
                }
            })
        },

        // 销毁 Cesium 资源
        destroyCesium() {
            if (this.viewer) {
                this.viewer.destroy()
                this.viewer = null
            }
        }
    }
}
</script>

<style scoped>
.layer-container {
    position: relative;
    width: 100%;
    height: 100%;
}


.cesium-container {
    width: 100%;
    height: 100%;
}

.loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 1000;
    color: white;
}

.loading-spinner {
    border: 4px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top: 4px solid #42b983;
    width: 50px;
    height: 50px;
    animation: spin 1s linear infinite;
    margin-bottom: 15px;
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}
</style>

(4)运行效果如下:
评论

全部评论(0)

回到顶部