一、3D Tiles 基础说明
3D Tiles 是 Cesium 推出的三维瓦片规范,专门用于海量实景倾斜模型、BIM、点云。
- 格式:瓦片文件夹包含
tileset.json作为入口文件 - 核心对象:
Cesium.Cesium3DTileset - 常用能力:加载实景模型、样式修改、压平(clippingPlane / 3DTilesClipping)、选中高亮、透明度、显示隐藏
⚠️ 注意:倾斜模型资源一般很大,本地开发建议用 nginx 静态代理,不能直接本地 file 协议打开 tileset.json,会跨域报错。
二、完整组件代码 Cesium3DTiles.vue
<template>
<div class="cesium-page">
<div ref="cesiumRef" id="cesiumContainer"></div>
<!-- 简易控制面板 -->
<div class="control-panel">
<button @click="flyToTileset">定位到模型</button>
<button @click="setOpacity(0.5)">半透明</button>
<button @click="setOpacity(1)">不透明</button>
<button @click="openFlat">开启压平</button>
<button @click="closeFlat">关闭压平</button>
<button @click="highlightAll">全部高亮</button>
<button @click="clearStyle">清除样式</button>
</div>
</div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue'
import * as Cesium from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'
const cesiumRef = ref(null)
let viewer = null
let tileset = null
let clippingPlaneCollection = null
window.CESIUM_BASE_URL = '/cesium/'
onMounted(() => {
initCesium()
})
onBeforeUnmount(() => {
// 销毁瓦片集,释放显存
if (tileset) {
tileset.destroy()
tileset = null
}
if (clippingPlaneCollection) {
clippingPlaneCollection.destroy()
clippingPlaneCollection = null
}
if (viewer) {
viewer.destroy()
viewer = null
}
})
function initCesium() {
viewer = new Cesium.Viewer('cesiumContainer', {
animation: false,
timeline: false,
baseLayerPicker: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
fullscreenButton: false,
infoBox: false
})
viewer.creditDisplay.container.style.display = 'none'
// 开启深度检测,倾斜模型必开,防止闪烁(z-fighting)
viewer.scene.globe.depthTestAgainstTerrain = true
loadTileset()
}
/**
* 加载3D Tiles倾斜摄影
*/
function loadTileset() {
tileset = new Cesium.Cesium3DTileset({
// 替换成你的 tileset.json 地址,本地静态服务或者后端服务地址
url: 'http://localhost:8080/tiles/tileset.json',
// 预加载优化
maximumScreenSpaceError: 16,
skipLevelOfDetail: true
})
tileset.readyPromise.then((tiles) => {
console.log('3D Tiles 加载成功', tiles)
// 模型加载完成自动飞到模型范围
viewer.zoomTo(tiles, new Cesium.HeadingPitchRange(0, -0.3, 0))
}).catch(err => {
console.error('3D Tiles加载失败:', err)
})
viewer.scene.primitives.add(tileset)
}
/**
* 相机定位到倾斜模型
*/
function flyToTileset() {
if (!tileset || !tileset.ready) return
viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0, -0.3, 0))
}
/**
* 设置模型透明度
* @param {number} val 0~1
*/
function setOpacity(val) {
if (!tileset || !tileset.ready) return
tileset.style = new Cesium.Cesium3DTileStyle({
color: {
evaluateColor: function (feature) {
const c = feature.getProperty('color') || Cesium.Color.WHITE
return new Cesium.Color(c.red, c.green, c.blue, val)
}
}
})
}
/**
* 全部模型高亮(红色)
*/
function highlightAll() {
if (!tileset || !tileset.ready) return
tileset.style = new Cesium.Cesium3DTileStyle({
color: 'color("red",0.6)'
})
}
/**
* 清除自定义样式,恢复原始纹理
*/
function clearStyle() {
if (!tileset || !tileset.ready) return
tileset.style = undefined
}
/**
* 开启压平(平面裁切,常用开挖、压平地面)
* 原理:ClippingPlane 裁切平面,平面一侧的模型被隐藏
*/
function openFlat() {
if (!tileset || !tileset.ready) return
clippingPlaneCollection = new Cesium.ClippingPlaneCollection({
planes: [
// 法向量 + 距离,这里示例向下裁切,压平模型上部
new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, -1), 30)
],
unionClippingRegions: false,
edgeColor: Cesium.Color.fromCssColorString('#00ffff')
})
tileset.clippingPlanes = clippingPlaneCollection
}
/**
* 关闭压平裁切
*/
function closeFlat() {
if (!tileset) return
tileset.clippingPlanes = null
if (clippingPlaneCollection) {
clippingPlaneCollection.destroy()
clippingPlaneCollection = null
}
}
</script>
<style scoped>
.cesium-page {
width: 100%;
height: 100vh;
position: relative;
}
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.control-panel {
position: absolute;
top: 12px;
left: 12px;
z-index: 999;
background: rgba(0, 20, 40, 0.75);
padding: 10px;
border-radius: 6px;
}
.control-panel button {
margin: 4px;
padding: 4px 8px;
cursor: pointer;
}
</style>
三、核心知识点详解
1. Cesium3DTileset 常用参数
url:tileset.json 服务地址,重点:必须 http/https 静态服务maximumScreenSpaceError:屏幕空间误差,越小模型越精细,性能越低,一般 16~32skipLevelOfDetail:跳过细节层级,提升加载速度readyPromise:异步加载完成回调,很多操作必须等 ready 之后执行
加载后添加到场景:
viewer.scene.primitives.add(tileset)
2. Cesium3DTileStyle 样式系统(重点)
样式支持表达式,可以根据模型自带属性动态着色,不修改原始模型:
// 简单写法
tileset.style = new Cesium.Cesium3DTileStyle({
color: 'color("#069BFD",0.7)'
})
进阶:根据属性条件着色,比如楼层、构件类型
tileset.style = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
['${name} === "楼房"', 'color("#ff3333")'],
['${name} === "道路"', 'color("#aaaaaa")'],
['true', 'color("#ffffff")']
]
}
})
color:颜色 + 透明度show:布尔表达式,控制要素是否显示- 清除样式直接赋值
tileset.style = undefined
3. 模型压平 / 裁切 ClippingPlane
很多人叫 "压平、开挖、剖切",底层是
ClippingPlaneCollection
Cartesian3(0,0,-1):法向量,代表平面朝向- 第二个参数:距离,正数 / 负数控制哪一侧保留、哪一侧裁掉
edgeColor:裁切边缘描边颜色,剖切的时候非常好看
局限:单平面只能一刀切;如果需要矩形框开挖、多边形压平,需要多个 ClippingPlane 组合成包围盒
4. 深度冲突 z-fighting(模型闪烁)
倾斜摄影最常见坑:模型和地形重叠闪烁
✅ 解决方案:
viewer.scene.globe.depthTestAgainstTerrain = true
如果模型还是闪烁,可以抬高 3Dtileset 高度,或者关闭地形。
四、3D Tiles 拾取选中高亮(扩展常用功能)
在上面代码基础上增加点击拾取,选中单个构件高亮:
function registerPick() {
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction(function (click) {
const pick = viewer.scene.pick(click.position)
if (Cesium.defined(pick) && Cesium.defined(pick.primitive) && pick.primitive instanceof Cesium.Cesium3DTileset) {
const feature = pick.id
// 按属性高亮
tileset.style = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
['${id} === ' + feature.getProperty('id'), 'color(#00ff00,0.6)'],
['true', 'color(#ffffff)']
]
}
})
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
五、高频踩坑汇总
- ❌ 直接本地打开 tileset.json,跨域报错
✅ 使用 nginx、vite-plugin-static-copy、后端静态资源服务托管瓦片 - ❌ 模型加载成功但是黑屏 / 看不到
✅ 检查坐标系!倾斜摄影一般是 EPSG:4326 / EPSG:3857,和 Cesium 一致;另外检查maximumScreenSpaceError - ❌ 压平裁切不生效
✅ 必须等 tileset.ready,法向量方向和距离值要调整,正负控制裁切方向 - ❌ 样式设置之后一直不生效
✅ 样式是Cesium3DTileStyle对象,不能直接赋值字符串,要 new;清除样式赋值 undefined - ❌ 页面切换路由显存暴涨,浏览器卡死
✅ 组件销毁时tileset.destroy(),瓦片会占用大量 GPU 显存,必须销毁 - ❌ 模型和地形重叠疯狂闪烁
✅depthTestAgainstTerrain = true