6.Cesium 学习
Cesium 是 “专注 GIS(地理信息系统)的 3D 库”,核心是 “在地球 / 地图上加载 3D 数据”,需额外补充 GIS 基础,但整体逻辑与 Three.js 相似(场景 - 相机 - 实体)。
1.Cesium 基础 + GIS 前置
1.1 GIS 基础:坐标系(WGS84)、地图投影(Web Mercator)、影像数据(天地图 / 高德)、地形数据
要理解 GIS(地理信息系统)的核心基础,我们可以把它类比成 “绘制和使用数字地球地图的一套规则与材料”—— 坐标系是 “定位工具”,地图投影是 “把地球变平面的方法”,影像 / 地形数据则是 “地图的内容素材”。
坐标系:给地球表面每一点发 “唯一地址”
坐标系的核心作用是给地球表面的任何一个位置分配一组独一无二的数字,让计算机能精准定位、计算距离。
WGS84,就是目前全球最通用的 “地址标准”。
为什么需要 WGS84?
地球不是完美的球体,而是一个 “有点扁的球”(专业叫 “椭球体”)。不同国家曾根据自己的区域,定义过不同的 “椭球模型”(比如中国过去用的 “北京 54”、“西安 80”),但这些模型只在局部准,跨区域就会出错(比如用 “西安 80” 算中美距离,误差会很大)。
WGS84(World Geodetic System 1984,1984 年世界大地坐标系)是全球统一的椭球模型—— 它基于卫星观测数据,把地球的 “扁率、半径” 等参数校准到能覆盖全球,无论你在北极还是南极,用 WGS84 定位都能保证精度。
WGS84 的 “地址格式”:经纬度
WGS84 最常用的表达是经纬度(度 °、分′、秒″),就像给每个点发了 “两串数字门牌号”:
经度:表示 “东西方向的位置”,范围是
-180°(西经)~ 180°(东经)
,以英国格林尼治天文台为 0°(本初子午线),向东数到 180°,向西也数到 180°(比如北京经度约 116°E,纽约约 74°W)。纬度:表示 “南北方向的位置”,范围是
-90°(南极)~ 90°(北极)
,以赤道为 0°,向北到北极是 90°N,向南到南极是 90°S(比如北京纬度约 39°N,赤道上的城市是 0°)。
地图投影:把 “球形地球” 压成 “平面地图” 的技巧
WGS84 的经纬度是 “球面坐标”,但我们看的地图(手机地图、纸质地图)都是平面的 —— 这就需要 “地图投影”:把球面上的点 “拉伸、变形” 到平面上,就像把一个橙子皮剥开、压平(过程中一定会有部分区域变形)。
Web Mercator(Web 墨卡托投影) 是目前所有在线地图(百度、高德、谷歌地图)最常用的投影方式,没有之一。
影像数据:GIS 地图的 “高清背景图”
如果说 “坐标系 + 投影” 是地图的 “骨架”,那影像数据就是地图的 “皮肤”—— 它是从天上(卫星、飞机)拍的地球表面照片,能让你看到真实的地形、建筑、道路。
我们常用的 “天地图”“高德地图” 的卫星图层,就是典型的影像数据。
影像数据从哪来?
卫星影像(比如天地图的核心数据):由绕地球的卫星(如中国的 “高分卫星”、美国的 “ Landsat 卫星”)拍摄,覆盖范围广(能拍整个国家),但清晰度中等(普通卫星影像 1 像素对应地面 10 米~100 米,高清卫星能到 0.5 米 / 像素,能看清地面的汽车);
航空影像(比如高德 / 百度的城市细节):由飞机低空飞行拍摄,覆盖范围小(一般是单个城市或区域),但清晰度极高(1 像素对应地面 0.1 米~1 米,能看清小区的阳台、路边的路灯)。
天地图 vs 高德:影像数据的 “差异定位”
两者的核心区别是 “数据来源和用途侧重”:
对比维度 天地图(国家地理信息公共服务平台) 高德地图(商业在线地图) 数据性质 国家官方数据,覆盖全国(包括偏远地区) 商业数据,重点覆盖城市及交通密集区域 清晰度 卫星影像为主,城市区域有航空影像,整体均衡 城市核心区用高清航空影像(能看清楼栋),偏远地区用卫星影像 用途 政府规划、科研(比如监测耕地变化) 日常导航、生活服务(比如找商场、看小区外观)
地形数据:让地图 “立起来” 的 “高度信息”
影像数据是 “平面照片”,只能看到 “地面长什么样”;而地形数据是 “高度数据”,能告诉你 “地面有多高”—— 比如这座山海拔 1000 米,那条河的河谷海拔 200 米。
有了地形数据,GIS 就能生成 “3D 地图”(比如你在高德地图上看 “山脉 3D 视图”),或者计算 “坡度”(比如修公路时判断这段路陡不陡)。
地形数据的核心:DEM(数字高程模型)
可以把 DEM 理解成 “给地球表面画的等高线网格”:
把地球表面分成无数个 “小正方形”(比如每个正方形代表地面 10 米 ×10 米的区域);
给每个小正方形标注一个 “高度值”(比如这个区域的平均海拔是 500 米);
把这些 “小正方形 + 高度值” 组合起来,就形成了一个 “数字沙盘”—— 计算机能通过这个沙盘,算出任意点的高度、两点之间的坡度。
地形数据的用途:不止 “看山高”
地形数据是很多领域的 “基础工具”:
导航:开车走山路时,导航能根据地形数据判断 “上坡需要多耗油”,或者避开 “坡度超过 15° 的危险路段”;
灾害预警:通过地形数据模拟 “洪水漫延路径”(比如海拔低于 100 米的区域会被淹),提前预警;
工程建设:修水库时,用地形数据算 “水库能装多少水”(取决于水库区域的海拔差);
游戏 / 影视:制作《原神》里的 “山地场景”,或电影《阿凡达》的悬浮山,都需要基于真实地形数据调整高度,让画面更真实。
GIS 基础的 “逻辑关系”
先用 WGS84 坐标系 给每个景点(比如山顶、湖泊)标注经纬度,确定 “它们在地球的准确位置”;
用 Web Mercator 投影 把球形的地球坐标,压成能印在纸上 / 显示在手机上的平面地图;
贴一张 影像数据(卫星拍的照片)作为地图背景,让你能看到景点的真实样子;
叠加 地形数据,在地图上标注 “山顶海拔 1200 米”“山路坡度 10°”,帮你判断爬山难度。
1.2 Cesium 核心:Viewer
实例(封装地球场景)、Entity
(绘制点 / 线 / 面 / 模型)
要理解 Cesium 的核心概念,我们可以把它类比成 “数字地球搭建工具包”——Viewer 是 “地球舞台” 的基础框架,Entity 则是舞台上的 “各种道具”。
Viewer 实例:Cesium 的 “地球舞台”(基础框架)
Viewer 是 Cesium 最核心的 “容器”,你可以把它理解成:一个已经封装好的 “数字地球操作台”—— 打开它,就自带了地球、视角控制、时间轴、比例尺等基础功能,不用你从零造地球。
Viewer 到底包含了什么?
核心:数字地球模型:默认加载 Cesium 官方的全球地形和影像(相当于 “地球的底图”),能看到山脉、海洋、城市的大致轮廓。
视角控制器:鼠标拖拽能旋转地球、滚轮缩放、右键平移(不用你写代码控制视角,开箱即用)。
辅助工具条:右上角默认有 “视角切换”(比如切换到卫星视角、地形视角)、“测量距离 / 面积”、“全屏” 等按钮。
时间轴:底部的时间滑块(Cesium 支持 “时空数据”,比如模拟卫星轨迹随时间变化,时间轴就是控制 “时间” 的)。
信息面板:左下角显示经纬度、高度、帧率等信息(相当于 “仪表盘”)。
怎么创建一个 Viewer?
// 1. 引入 Cesium(前提:项目已引入 Cesium 库) import * as Cesium from 'cesium';// 2. 在 HTML 中准备一个“容器”(比如一个 div,用来放地球) // <div id="cesiumContainer" style="width: 100%; height: 800px;"></div>// 3. 创建 Viewer 实例(把地球放进容器里) const viewer = new Cesium.Viewer('cesiumContainer', {// 可选:关闭不需要的默认功能(按需配置,比如关闭时间轴)timeline: false, // 关闭底部时间轴infoBox: false, // 关闭点击元素时的信息弹窗navigationHelpButton: false, // 关闭“视角帮助”按钮 });// 4. 可选:调整初始视角(比如定位到北京) viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.4074, // 北京经度39.9042, // 北京纬度10000 // 高度(10000米,即10公里高空)),duration: 3 // 飞行时间(3秒,慢慢飞到北京) });
Viewer 的核心作用
“搭舞台”:提供地球场景的基础框架,包含渲染、视角、交互的底层逻辑。
“减工作量”:默认集成常用工具,避免重复开发(比如不用自己写 “鼠标旋转地球” 的代码)。
“管资源”:后续添加的 Entity(点、线、模型)、影像、地形,都由 Viewer 统一管理。
Entity:Cesium 的 “数字道具”(绘制各种元素)
如果说 Viewer 是 “地球舞台”,那 Entity 就是舞台上的 “道具”—— 用来在地球上画点、线、面、3D 模型、文字等各种元素。
Entity 能画什么?
Entity 类型 通俗理解 实际场景举例 Point(点) 地球表面的 “标记点” 标记一个事故地点、一个传感器位置 Polyline(线) 地球表面的 “连线” 画一条公路、一条航线、一条边界 Polygon(面) 地球表面的 “闭合区域” 画一个城市范围、一个湖泊、一块农田 Model(3D 模型) 地球表面的 “3D 物体” 放一个建筑模型、一个无人机模型 Label(文字) 地球表面的 “标签” 在标记点旁写 “北京天安门” Billboard(图片) 地球表面的 “图片标记” 用自定义图标(比如摄像头图标)标记监控点
怎么用 Entity 画元素?(代码示例:从点到模型)
Entity 的使用逻辑很统一:通过 viewer.entities.add () 方法,传入一个 “配置对象”(告诉 Cesium:你要画什么、在哪画、长什么样)。
示例 1:画一个 “红色标记点”(标记北京天安门)
// 给 Viewer 加一个“点 Entity” viewer.entities.add({name: "北京天安门", // 给这个 Entity 起个名字(方便后续管理)position: Cesium.Cartesian3.fromDegrees(116.39748, 39.90882), // 位置(经纬度)point: { // 配置“点”的样式pixelSize: 10, // 点的大小(像素)color: Cesium.Color.RED, // 颜色(红色)outlineColor: Cesium.Color.WHITE, // 边框颜色(白色)outlineWidth: 2 // 边框宽度} });
示例 2:画一条 “蓝色航线”(北京到上海)
// 给 Viewer 加一个“线 Entity” viewer.entities.add({name: "北京-上海航线",polyline: { // 配置“线”的样式positions: [// 航线的两个端点(北京、上海的经纬度+高度)Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 10000),Cesium.Cartesian3.fromDegrees(121.4737, 31.2304, 10000)],width: 5, // 线的宽度(像素)material: Cesium.Color.BLUE, // 线的颜色(蓝色)clampToGround: false // 是否贴地(这里设为 false,航线在10公里高空)} });
示例 3:加载一个 “3D 模型”(比如一个建筑)
假设你有一个建筑的 glTF 模型文件(Cesium 支持 glTF/glb 格式,3D 模型的常用格式):
// 给 Viewer 加一个“3D模型 Entity” viewer.entities.add({name: "自定义建筑模型",position: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 0), // 模型位置(北京,高度0贴地)model: { // 配置“模型”的参数uri: './models/building.glb', // 模型文件路径scale: 10, // 模型缩放比例(根据模型大小调整)minimumPixelSize: 64, // 模型最小显示大小(避免缩放时消失)maximumScale: 200 // 模型最大缩放比例} });
Entity 的核心优势(为什么不用直接写 3D 渲染?)
“傻瓜式配置”:不用懂 WebGL(3D 渲染底层技术),只要填参数就能画元素,降低门槛。
“自动贴合地球”:Entity 会自动处理 “地球是球体” 的问题 —— 比如你画一条线,它会沿着地球曲面走,而不是画一条直线穿过地球内部。
“绑定时间”:支持 “时空数据”—— 比如让一个模型(如无人机)随时间移动,只要给 Entity 加 “时间参数” 就能实现(这是 Cesium 比普通 3D 库强的地方)。
Viewer 和 Entity 的关系
Viewer 是 “地球舞台”,Entity 是 “舞台上的道具”——所有 Entity 都必须 “放在” Viewer 里才能显示,Viewer 负责管理这些 Entity 的渲染、交互(比如点击 Entity 显示信息)。
1.3 数据来源:ImageryProvider
(影像图层)、TerrainProvider
(地形图层)
ImageryProvider
(影像图层提供者)和TerrainProvider
(地形图层提供者)是两个核心概念,它们分别负责处理地图的 “地表图像” 和 “地表起伏”,共同构建出真实的三维地球效果。
ImageryProvider(影像图层提供者)
概念
用于加载和管理二维影像数据的组件,这些数据通常是卫星遥感图像、航拍照片、地图切片(如 Google 地图、高德地图的底图)等,通过坐标映射贴到地球表面,让三维地球显示出真实的地表纹理。
想象你给一个球体(地球模型)贴一张高清照片,这张照片就是 “影像”。
ImageryProvider
的作用就是提供这张(或多张)照片,并告诉程序 “照片的哪一部分对应地球的哪一块区域”。图片来源:
卫星影像(如 NASA、高德、百度的卫星地图);
航拍图片(无人机或飞机拍摄的区域照片);
电子地图切片(如 OSM 开源地图的街道底图);
自定义图片(比如某个区域的规划图、历史地图)。
实例:加载卫星照片、电子地图等 2D 图像,作为地球表面的 “皮肤”
// 初始化Cesium地球 const viewer = new Cesium.Viewer('cesiumContainer', {// 配置影像图层(默认是Bing地图影像)imageryProvider: new Cesium.BingMapsImageryProvider({url: 'https://dev.virtualearth.net',key: '你的Bing地图密钥', // 需要申请mapStyle: Cesium.BingMapsStyle.AERIAL // 卫星影像风格}),terrainProvider: Cesium.createWorldTerrain() // 暂时先启用默认地形 });// 额外添加一个影像图层(如高德地图) const gaodeImagery = new Cesium.UrlTemplateImageryProvider({url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}' }); viewer.imageryLayers.addImageryProvider(gaodeImagery);
TerrainProvider(地形图层提供者)
概念
用于加载和管理地形高程数据的组件,这些数据记录了地球表面每个点的海拔高度,通过计算相邻点的高度差,构建出三维地形的起伏形态。
如果说
ImageryProvider
是给地球贴 “皮肤”,那TerrainProvider
就是给地球做 “骨架”—— 它定义了地球表面的高低起伏(比如山脉、峡谷、平原的高度差),让地球不再是一个光滑的球体,而是符合真实地形的凹凸表面。常见来源:
高程数据集(如 SRTM 全球高程数据,精度约 90 米;ASTER GDEM,精度约 30 米);
高精度地形数据(如国家测绘局的区域地形数据,精度可达米级);
自定义地形数据(比如某个矿山、景区的精细地形模型)。
实例:提供地表的高程数据,让平面地图变成有山脉、峡谷的立体地形。
const viewer = new Cesium.Viewer('cesiumContainer', {imageryProvider: new Cesium.BingMapsImageryProvider({url: 'https://dev.virtualearth.net',key: '你的Bing地图密钥',mapStyle: Cesium.BingMapsStyle.AERIAL}),// 配置地形图层(使用Cesium默认的全球地形)terrainProvider: Cesium.createWorldTerrain({requestWaterMask: true, // 启用水面效果(如海洋、湖泊)requestVertexNormals: true // 启用光照阴影(让地形更真实)}) });// 飞到青藏高原(地形起伏明显的区域) viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(85, 30, 1000000) // 经度、纬度、高度 });
1.4 实践
初始化一个 “带高德影像 + 全球地形的地球”
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cesium - 高德影像+全球地形</title><!-- 引入Cesium库 --><script src="https://cesium.com/downloads/cesiumjs/releases/1.113/Build/Cesium/Cesium.js"></script><!-- 引入Cesium样式 --><link href="https://cesium.com/downloads/cesiumjs/releases/1.113/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><style>/* 让地球占满整个屏幕 */html, body, #cesiumContainer {width: 100%;height: 100%;margin: 0;padding: 0;overflow: hidden;}</style>
</head>
<body><!-- Cesium地球容器 --><div id="cesiumContainer"></div><script>// 配置Cesium的默认AccessToken(如果有自己的token可以替换)// 可以去Cesium官网申请免费token:https://cesium.com/ion/Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2YjRjZTk2Ny1jY2E1LTQ5Y2QtODRjMy0yNWRjZTQyYjZhYTYiLCJpZCI6MTQ3MjQ5LCJpYXQiOjE2OTM0Nzg2NDR9.1D7XQK_1lVrJ9t0d1G3Xqg17Y2E4HlJZtJz3KQZ5c7E';// 初始化Cesium Viewer,并配置高德影像和全球地形const viewer = new Cesium.Viewer('cesiumContainer', {// 配置高德影像图层作为底图imageryProvider: new Cesium.UrlTemplateImageryProvider({// 高德影像瓦片地址模板url: 'http://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',// 高德影像的坐标系是Web Mercator,与Cesium默认一致projectionTransforms: Cesium.Transforms.wgs84ToMercator,// 最大层级maximumLevel: 18,// 瓦片宽度和高度tileWidth: 256,tileHeight: 256}),// 配置全球地形terrainProvider: Cesium.createWorldTerrain({// 启用水面效果(海洋、湖泊等)requestWaterMask: true,// 启用顶点法线,用于更真实的光照效果requestVertexNormals: true}),// 隐藏不需要的控件,保持界面简洁baseLayerPicker: false, // 隐藏底图切换控件homeButton: true, // 保留首页按钮sceneModePicker: false, // 隐藏场景模式切换控件navigationHelpButton: false, // 隐藏帮助按钮infoBox: true, // 保留信息框fullscreenButton: true // 保留全屏按钮});// 调整相机位置,飞到中国区域viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(105.0, // 经度35.0, // 纬度3000000 // 高度(米)),duration: 3 // 飞行时间(秒)});// 可以添加一些额外的功能,例如显示帧率viewer.scene.debugShowFramesPerSecond = true;</script>
</body>
</html>
在指定经纬度(如学校坐标)添加一个 3D 模型(如教学楼)。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cesium添加3D模型示例</title><!-- 引入Cesium库 --><script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><style>/* 设置容器样式,让地球占满整个屏幕 */#cesiumContainer {width: 100vw;height: 100vh;margin: 0;padding: 0;overflow: hidden;}/* 信息面板样式 */.info-panel {position: absolute;top: 20px;left: 20px;background: rgba(0, 0, 0, 0.7);color: white;padding: 15px;border-radius: 8px;max-width: 300px;z-index: 100;}.info-panel h3 {margin-top: 0;color: #4CAF50;}</style>
</head>
<body><!-- Cesium地球容器 --><div id="cesiumContainer"></div><!-- 信息面板 --><div class="info-panel"><h3>3D教学楼模型示例</h3><p>该示例在指定经纬度位置添加了一个3D教学楼模型</p><p><strong>学校坐标:</strong>116.3904°E, 39.9061°N(示例坐标)</p></div><script>// 配置Cesium ion访问令牌(需要自己注册获取)// 可以去https://cesium.com/ion/免费注册获取令牌Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4Y2RlYTUyNy1iMjk5LTQ2YTQtYjQ0NS1hMzQ0ZDI5MzA1Y2UiLCJpZCI6MTM5Njc5LCJpYXQiOjE2ODk1MjA4NDR9.Jc5zF8QJz9qZ8G6UcVhO39qFj3c7j2v0Z8Q5X9q3J7A';// 初始化Cesium视图器const viewer = new Cesium.Viewer('cesiumContainer', {// 配置地形,让模型能够贴合地面terrainProvider: Cesium.createWorldTerrain({requestWaterMask: true, // 启用水面效果requestVertexNormals: true // 启用地形光照效果}),// 配置影像图层(使用微软Bing卫星影像)imageryProvider: new Cesium.BingMapsImageryProvider({url: 'https://dev.virtualearth.net',mapStyle: Cesium.BingMapsStyle.AERIAL_WITH_LABELS,key: 'AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf'}),// 隐藏一些不需要的控件timeline: false,animation: false,homeButton: true,fullscreenButton: true,baseLayerPicker: true});// 定义学校的经纬度坐标(这里使用示例坐标,可替换为实际学校坐标)// 格式:[经度, 纬度, 高度]const schoolCoordinates = [116.3904, 39.9061, 0]; // 北京某学校示例坐标// 转换经纬度坐标为Cesium的笛卡尔坐标(世界坐标)const position = Cesium.Cartesian3.fromDegrees(schoolCoordinates[0], // 经度schoolCoordinates[1], // 纬度schoolCoordinates[2] // 高度(相对于地面));// 添加3D模型到场景中const教学楼模型 = viewer.scene.primitives.add(new Cesium.ModelPrimitive({// 模型URL(这里使用Cesium ion上的一个教学楼模型示例)// 可以替换为自己的模型URL(支持glTF和glb格式)url: Cesium.IonResource.fromAssetId(96188),// 模型位置modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(position),// 模型缩放比例scale: 10.0,// 模型是否可见show: true,// 启用模型阴影shadows: Cesium.ShadowMode.CAST_AND_RECEIVE}));// 当模型加载完成后执行的操作教学楼模型.readyPromise.then(function(model) {// 调整相机位置,聚焦到模型viewer.camera.flyTo({// 目标位置:模型位置上方500米destination: Cesium.Cartesian3.fromDegrees(schoolCoordinates[0], schoolCoordinates[1], 500 // 相机高度),// 飞行时间:3秒duration: 3});console.log('模型加载完成!');}).catch(function(error) {console.error('模型加载失败:', error);});// 添加一个标记点,显示学校位置(可选)const schoolMarker = viewer.entities.add({position: position,point: {pixelSize: 10,color: Cesium.Color.RED,outlineColor: Cesium.Color.WHITE,outlineWidth: 2},label: {text: '学校位置',font: '14px sans-serif',pixelOffset: new Cesium.Cartesian2(0, 20),fillColor: Cesium.Color.YELLOW}});</script>
</body>
</html>
2.Cesium 核心能力
2.1 模型加载:加载 3D Tiles(GIS 专用 3D 模型格式,如校园建筑)
什么是 3D Tiles?
3D Tiles 是一种专为地理信息(GIS)设计的 3D 模型格式,由 Cesium 团队主导开发。它就像 "3D 世界的地图切片",能高效加载大规模 3D 场景(如整个校园、城市建筑群)。
核心优势:
按需加载:只加载当前视野内的模型细节
层级缩放:远处显示简化模型,近处显示精细模型
高效渲染:支持海量建筑同时展示(成千上万个模型)
实践
加载校园建筑群的 3D Tiles 模型:
<!DOCTYPE html>
<html>
<head><title>3D Tiles 校园模型加载</title><script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><style>#cesiumContainer { width: 100vw; height: 100vh; margin: 0; }</style>
</head>
<body><div id="cesiumContainer"></div><script>// 配置访问令牌(需自行注册获取)Cesium.Ion.defaultAccessToken = '你的令牌';// 初始化地球const viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain() // 加载地形});// 加载校园3D Tiles模型const campusTileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({// 模型地址(可以是在线资源或本地文件)url: Cesium.IonResource.fromAssetId(96188), // 示例校园模型// 初始缩放比例scale: 1.0,// 模型样式(可选)style: new Cesium.Cesium3DTileStyle({color: {conditions: [["${name} === '教学楼'", "color('#ff0000')"], // 教学楼红色["${name} === '图书馆'", "color('#00ff00')"], // 图书馆绿色["true", "color('#ffffff')"] // 其他建筑白色]}})}));// 模型加载完成后,飞行到模型位置campusTileset.readyPromise.then(function(tileset) {// 自动定位到模型中心点viewer.camera.flyToBoundingSphere(tileset.boundingSphere, {duration: 3, // 飞行时间3秒offset: new Cesium.HeadingPitchRange(0.5, -0.2, tileset.boundingSphere.radius * 2)});}).catch(function(error) {console.error('模型加载失败:', error);});</script>
</body>
</html>
关键代码解析
核心类:
Cesium3DTileset
- 专门用于加载 3D Tiles 格式的模型
- 通过
url
指定模型地址(支持在线资源或本地文件)按需加载机制
- 代码无需手动控制加载范围,Cesium 会自动根据相机位置加载所需模型
- 缩放或旋转视角时,会动态加载 / 卸载模型细节
模型样式设置
- 通过
Cesium3DTileStyle
可以根据模型属性(如建筑名称、高度)设置不同颜色- 类似给不同建筑 "贴不同颜色的标签",方便区分
定位到模型
flyToBoundingSphere
方法可自动飞行到模型区域- 避免手动调整相机寻找模型
实际应用场景
- 校园数字化:加载整个校园的 3D 模型,用于虚拟校园游览
- 城市规划:展示城市建筑群,支持缩放查看不同区域细节
- 应急指挥:叠加实时数据(如人流、火灾点)在 3D 城市模型上
2.2 空间分析:距离测量(DistanceMeasurer
)、范围选择(RectangleSelector
)
核心概念
功能 | 作用 | 通俗理解 | 应用场景举例 |
---|---|---|---|
距离测量 | 计算两点或多点之间的实际地表距离 | 相当于在地图上 “拉尺子” 量距离 | 测量两地直线距离、道路长度 |
范围选择 | 用矩形框选地图上的一片区域并获取区域内要素 | 相当于在地图上 “画方框” 圈出一块地 | 筛选特定区域内的建筑物、POI |
示例
距离测量(DistanceMeasurer)
功能:通过点击地图上的点,实时计算并显示点之间的距离(支持多点连续测量)。
原理:通过将屏幕点击位置转换为地球表面坐标(经纬度→三维笛卡尔坐标),再用空间距离公式(如两点间直线距离)计算实际长度,最后用图形和标签展示结果。
// 初始化Cesium地球 const viewer = new Cesium.Viewer("cesiumContainer");// 存储测量点和线 let measurementPoints = []; let distanceLine = null; let distanceLabel = null;// 开启测量模式 function startDistanceMeasurement() {// 移除之前的测量结果resetMeasurement();// 鼠标点击事件:添加测量点const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction((click) => {// 将点击位置转换为地球表面坐标const cartesian = viewer.camera.pickEllipsoid(click.position,viewer.scene.globe.ellipsoid);if (cartesian) {measurementPoints.push(cartesian);// 添加点标记viewer.entities.add({position: cartesian,point: {pixelSize: 8,color: Cesium.Color.RED,outlineColor: Cesium.Color.WHITE}});// 计算并显示距离(至少2个点才计算)if (measurementPoints.length >= 2) {updateDistanceDisplay();}}}, Cesium.ScreenSpaceEventType.LEFT_CLICK);// 右键点击:结束测量handler.setInputAction(() => {handler.destroy(); // 销毁事件监听console.log("测量结束,总距离:" + getTotalDistance().toFixed(2) + "米");}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }// 更新距离显示 function updateDistanceDisplay() {// 移除之前的线和标签if (distanceLine) viewer.entities.remove(distanceLine);if (distanceLabel) viewer.entities.remove(distanceLabel);// 绘制连接线distanceLine = viewer.entities.add({polyline: {positions: measurementPoints,width: 3,material: new Cesium.PolylineGlowMaterialProperty({glowPower: 0.2,color: Cesium.Color.YELLOW})}});// 计算总距离(米)const totalDistance = getTotalDistance();// 添加距离标签(显示在最后一个点上方)distanceLabel = viewer.entities.add({position: measurementPoints[measurementPoints.length - 1],label: {text: "总距离:" + totalDistance.toFixed(2) + "米",font: "16px sans-serif",pixelOffset: new Cesium.Cartesian2(0, -30),fillColor: Cesium.Color.YELLOW}}); }// 计算所有点之间的总距离 function getTotalDistance() {let total = 0;for (let i = 1; i < measurementPoints.length; i++) {total += Cesium.Cartesian3.distance(measurementPoints[i - 1],measurementPoints[i]);}return total; }// 重置测量 function resetMeasurement() {measurementPoints = [];viewer.entities.removeAll(); // 清除所有标记 }// 启动测量(可通过按钮调用) startDistanceMeasurement();
范围选择(RectangleSelector)
功能:通过鼠标拖拽绘制矩形,选择矩形范围内的地理要素(如建筑物、POI 等)。
原理:通过鼠标拖拽确定矩形的对角点,计算矩形的经纬度范围,再逐一判断地图要素是否在该范围内(点是否在矩形内),最终筛选并高亮符合条件的要素。
// 初始化Cesium地球 const viewer = new Cesium.Viewer("cesiumContainer");// 添加一些示例要素(模拟地图上的建筑物) addSampleBuildings();// 范围选择函数 function startRectangleSelection() {let isSelecting = false;let startPosition = null;let rectangleEntity = null;const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);// 鼠标按下:开始绘制矩形handler.setInputAction((down) => {isSelecting = true;startPosition = down.position;// 创建矩形实体(初始为点)rectangleEntity = viewer.entities.add({rectangle: {coordinates: new Cesium.CallbackProperty(() => {if (!startPosition || !down.position) return null;// 计算矩形范围return Cesium.Rectangle.fromCartesianArray([viewer.camera.pickEllipsoid(startPosition),viewer.camera.pickEllipsoid(down.position)]);}, false),material: new Cesium.ColorMaterialProperty(Cesium.Color.BLUE.withAlpha(0.2)),outline: true,outlineColor: Cesium.Color.BLUE}});}, Cesium.ScreenSpaceEventType.LEFT_DOWN);// 鼠标移动:更新矩形大小handler.setInputAction((move) => {if (!isSelecting) return;// 矩形会随鼠标移动实时更新(依赖上面的CallbackProperty)}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);// 鼠标松开:完成选择并筛选要素handler.setInputAction((up) => {isSelecting = false;handler.destroy(); // 销毁事件监听// 获取矩形范围(经纬度)const rectangle = Cesium.Rectangle.fromCartesianArray([viewer.camera.pickEllipsoid(startPosition),viewer.camera.pickEllipsoid(up.position)]);// 筛选矩形内的要素const selectedEntities = [];viewer.entities.values.forEach(entity => {if (entity.position) {const cartographic = Cesium.Cartographic.fromCartesian(entity.position._value);// 判断要素是否在矩形内if (Cesium.Rectangle.contains(rectangle, cartographic)) {selectedEntities.push(entity);// 高亮选中要素entity.point.color = Cesium.Color.GREEN;}}});console.log(`选中了${selectedEntities.length}个要素`);}, Cesium.ScreenSpaceEventType.LEFT_UP); }// 添加示例建筑物(模拟数据) function addSampleBuildings() {// 在随机位置添加10个点(模拟建筑物)for (let i = 0; i < 10; i++) {viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.3 + Math.random() * 0.2, // 随机经度39.9 + Math.random() * 0.2, // 随机纬度0),point: {pixelSize: 10,color: Cesium.Color.RED},label: {text: `建筑物${i + 1}`,pixelOffset: new Cesium.Cartesian2(0, 20)}});} }// 启动范围选择(可通过按钮调用) startRectangleSelection();
2.3 时间动态:TimeDynamicObject
实现 “随时间变化的实体”(如车辆轨迹)
TimeDynamicObject
(时间动态对象)是处理随时间变化的实体的核心机制,尤其适合展示车辆轨迹、人员移动、天气变化等具有时间属性的动态数据。它能让实体的位置、状态等属性随时间自动更新,从而呈现出流畅的动态效果。
什么是 “随时间变化的实体”?
例如:
车辆在不同时间点行驶到不同位置,形成轨迹;
快递包裹在不同时间点处于不同运输状态(运输中 / 已签收);
天气系统随时间移动(台风路径)。
关键技术点
时间点(Timestamp):记录实体状态的具体时间(如
2023-10-01T08:00:00
)。插值(Interpolation):当时间处于两个记录点之间时,自动计算中间状态(如两点之间的位置)。
时间范围(TimeInterval):实体有效存在的时间区间(超出范围则隐藏)
示例:车辆轨迹动态展示
<!DOCTYPE html>
<html>
<head><title>时间动态实体示例</title><script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><style>#cesiumContainer { width: 100vw; height: 100vh; margin: 0; }</style>
</head>
<body><div id="cesiumContainer"></div><script>// 初始化Cesium(需替换为自己的令牌)Cesium.Ion.defaultAccessToken = '你的Cesium令牌';const viewer = new Cesium.Viewer('cesiumContainer', {timeline: true, // 显示时间轴(用于手动控制时间)animation: true // 显示动画控制器(播放/暂停时间)});// 1. 定义车辆的时间-位置数据(轨迹点)// 格式:[{时间, 经度, 纬度}, ...]const carPositions = [{ time: "2023-10-01T08:00:00", lon: 116.3, lat: 39.9 }, // 8:00位置{ time: "2023-10-01T08:05:00", lon: 116.4, lat: 39.9 }, // 8:05位置{ time: "2023-10-01T08:10:00", lon: 116.5, lat: 39.8 }, // 8:10位置{ time: "2023-10-01T08:15:00", lon: 116.6, lat: 39.8 } // 8:15位置];// 2. 创建时间动态对象(TimeDynamicObject)const car = new Cesium.Entity({// 车辆模型(可选,用点代替)point: {pixelSize: 10,color: Cesium.Color.RED},// 车辆标签label: {text: "测试车辆",pixelOffset: new Cesium.Cartesian2(0, 20)},// 位置随时间变化position: new Cesium.SampledPositionProperty(),// 轨迹线(显示历史路径)path: {resolution: 1, // 路径精度material: new Cesium.PolylineGlowMaterialProperty({glowPower: 0.2,color: Cesium.Color.YELLOW}),width: 5 // 线宽}});// 3. 给动态对象添加时间-位置数据carPositions.forEach(data => {// 将时间字符串转为Cesium时间对象const time = Cesium.JulianDate.fromIso8601(data.time);// 将经纬度转为Cesium坐标const position = Cesium.Cartesian3.fromDegrees(data.lon, data.lat);// 添加到动态位置属性中car.position.addSample(time, position);});// 4. 将动态对象添加到场景viewer.entities.add(car);// 5. 调整视图:聚焦到轨迹起点viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.3, 39.9, 10000)});// 6. 设置时间轴范围(自动匹配数据的时间范围)const start = Cesium.JulianDate.fromIso8601(carPositions[0].time);const end = Cesium.JulianDate.fromIso8601(carPositions[carPositions.length - 1].time);viewer.timeline.zoomTo(start, end); // 时间轴自适应范围viewer.clock.startTime = start.clone(); // 时钟起始时间viewer.clock.endTime = end.clone(); // 时钟结束时间viewer.clock.currentTime = start.clone(); // 当前时间设为起点viewer.clock.multiplier = 30; // 时间流速(30倍速播放)viewer.clock.shouldAnimate = true; // 自动播放</script>
</body>
</html>
2.4 交互:相机控制(缩放 / 旋转 / 定位到指定位置)
相机控制是用户与 3D 环境交互的核心方式,相当于现实中 “人眼” 的移动和调整。通过控制相机,我们可以从不同角度、距离观察场景中的物体,实现缩放、旋转、平移等操作,让 3D 场景更具沉浸感。
概念
相机(Camera):三维场景中虚拟的 “观察点”,决定了用户能看到什么内容(类似人眼的位置和视角)。
核心操作:缩放(拉近 / 拉远)、旋转(改变观察角度)、定位(跳转到指定位置)。
相机控制
缩放(Zoom)
调整相机与目标物体的距离,实现 “拉近看细节” 或 “拉远看全局”
// 初始化Cesium地球 const viewer = new Cesium.Viewer('cesiumContainer');// 方法1:通过鼠标滚轮(Cesium默认支持,无需额外代码) // 向前滚轮:相机靠近目标(放大) // 向后滚轮:相机远离目标(缩小)// 方法2:通过代码控制缩放(设置相机高度) function zoomToHeight(height) {// 获取当前相机位置的经纬度const cartographic = Cesium.Cartographic.fromCartesian(viewer.camera.position);// 保持经纬度不变,只修改高度(单位:米)viewer.camera.setView({destination: Cesium.Cartesian3.fromRadians(cartographic.longitude, // 经度cartographic.latitude, // 纬度height // 新高度(值越小越近,越大越远))}); }// 调用:缩放到高度1000米(靠近地面) zoomToHeight(1000); // 调用:缩放到高度10000米(拉远视角) zoomToHeight(10000);
旋转(Rotate)
围绕目标物体转动相机,从不同角度观察(如正面、侧面、顶部)
// 初始化Cesium地球 const viewer = new Cesium.Viewer('cesiumContainer');// 方法1:通过鼠标拖拽(Cesium默认支持) // 左键拖拽:围绕目标旋转(水平/垂直转动) // 右键拖拽:平移相机(平行移动视角)// 方法2:通过代码控制旋转(围绕指定点旋转) function rotateAroundPoint(longitude, latitude, angle) {// 目标点坐标(如北京:116.4°E, 39.9°N)const target = Cesium.Cartesian3.fromDegrees(longitude, latitude, 0);// 旋转相机:围绕目标点顺时针转angle度viewer.camera.orbit(target, Cesium.Math.toRadians(angle), 0); }// 调用:围绕北京顺时针旋转90度 rotateAroundPoint(116.4, 39.9, 90);
定位到指定位置(Fly To)
让相机快速 “跳” 到指定位置并聚焦,常用于场景切换。
// 初始化Cesium地球 const viewer = new Cesium.Viewer('cesiumContainer');// 方法1:定位到经纬度(瞬间切换) viewer.camera.setView({destination: Cesium.Cartesian3.fromDegrees(121.47, 31.23, 5000 // 上海坐标:121.47°E, 31.23°N,高度5000米) });// 方法2:平滑飞行到目标位置(带动画效果) viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 5000 // 北京坐标:116.4°E, 39.9°N,高度5000米),duration: 3 // 飞行时间(秒),值越大越慢 });// 方法3:定位到3D模型(如之前添加的教学楼) function focusOnModel(model) {// 获取模型的包围盒中心const center = model.boundingSphere.center;// 飞行到模型上方300米处viewer.camera.flyTo({destination: Cesium.Cartesian3.add(center, new Cesium.Cartesian3(0, 0, 300), new Cesium.Cartesian3()),duration: 2}); }
实际应用场景
操作 | 常见使用场景 | 示例效果 |
---|---|---|
缩放 | 查看建筑细节(拉近)或城市全景(拉远) | 从 1000 米高度放大到 100 米,看清屋顶 |
旋转 | 360° 观察雕塑、建筑外观 | 围绕教学楼旋转,查看前后左右设计 |
定位 | 从 A 地快速切换到 B 地,或聚焦到指定物体 | 从北京 “飞” 到上海,或聚焦到校园操场 |
2.5 实践
加载 “校园 3D Tiles 模型”,实现 “点击建筑显示名称”
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>校园3D模型查看器</title><!-- 引入Cesium库 --><script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><!-- 引入Tailwind CSS --><script src="https://cdn.tailwindcss.com"></script><!-- 引入Font Awesome --><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><style>/* 设置Cesium容器样式 */#cesiumContainer {width: 100%;height: 100vh;margin: 0;padding: 0;overflow: hidden;}/* 信息弹窗样式 */.info-box {position: absolute;background-color: rgba(255, 255, 255, 0.9);border-radius: 8px;padding: 12px 16px;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;transform: translate(-50%, -100%);pointer-events: auto;transition: opacity 0.3s ease;z-index: 100;}.info-box h3 {margin: 0 0 6px 0;color: #2c3e50;font-size: 16px;}.info-box p {margin: 0;color: #34495e;font-size: 14px;}/* 加载指示器样式 */.loading-indicator {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: rgba(0, 0, 0, 0.7);color: white;padding: 15px 30px;border-radius: 5px;z-index: 1000;display: flex;align-items: center;gap: 10px;}/* 顶部控制栏样式 */.control-bar {position: absolute;top: 20px;left: 50%;transform: translateX(-50%);background-color: rgba(255, 255, 255, 0.9);border-radius: 8px;padding: 10px 20px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);z-index: 99;display: flex;align-items: center;gap: 15px;}</style>
</head>
<body><!-- Cesium容器 --><div id="cesiumContainer"></div><!-- 加载指示器 --><div id="loadingIndicator" class="loading-indicator"><i class="fa fa-circle-o-notch fa-spin fa-lg"></i><span>正在加载校园模型...</span></div><!-- 控制栏 --><div class="control-bar"><div class="flex items-center gap-2"><i class="fa fa-info-circle text-blue-600"></i><span>点击建筑查看名称</span></div><button id="resetView" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition duration-200"><i class="fa fa-refresh mr-1"></i> 重置视角</button></div><script>// 初始化CesiumCesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1NzdiYzQwOS1hYzQ2LTRhYTctYjI1YS1hNTk0M2ZjM2Y5YzciLCJpZCI6MTA1MTM5LCJpYXQiOjE2Nzg1MDc4NDR9.37X3bKc67tQ9o1LrW4b6vDn93fE7d5y4J4a1QrBZfV0';// 创建Cesium Viewer实例const viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain(),// 隐藏一些不需要的控件animation: false,baseLayerPicker: true,fullscreenButton: true,geocoder: false,homeButton: false,infoBox: false, // 禁用默认信息框sceneModePicker: true,selectionIndicator: false, // 禁用默认选择指示器timeline: false,navigationHelpButton: true,// 使用天地图作为底图imageryProvider: new Cesium.WebMapTileServiceImageryProvider({url: "http://t0.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=您的天地图密钥",layer: "img",style: "default",format: "image/jpeg",tileMatrixSetID: "w",maximumLevel: 18,credit: "天地图全球影像"})});// 隐藏Cesium标志viewer._cesiumWidget._creditContainer.style.display = "none";// 加载校园3D Tiles模型// 注意:这里使用的是示例URL,实际应用中需要替换为您自己的3D Tiles模型URLconst campusTileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({url: Cesium.IonResource.fromAssetId(96188), // 示例3D Tiles模型// 模型样式 - 突出显示可点击的建筑style: new Cesium.Cesium3DTileStyle({color: {conditions: [["${feature['name']} !== undefined", "color('#ffffff')"],["true", "color('#cccccc')"]]},show: "${feature['name']} !== undefined"})}));// 当模型加载完成后campusTileset.readyPromise.then(function(tileset) {// 隐藏加载指示器document.getElementById('loadingIndicator').style.display = 'none';// 调整相机视角以完整显示模型viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, 1000));console.log('校园模型加载完成');}).catch(function(error) {console.error('加载校园模型时出错:', error);const loadingIndicator = document.getElementById('loadingIndicator');loadingIndicator.innerHTML = '<i class="fa fa-exclamation-triangle text-yellow-400"></i><span>模型加载失败,请重试</span>';});// 创建信息弹窗元素const infoBox = document.createElement('div');infoBox.className = 'info-box';infoBox.style.display = 'none';document.body.appendChild(infoBox);// 处理鼠标点击事件let selectedFeature = null;viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {// 清除之前的选择if (selectedFeature) {selectedFeature = null;infoBox.style.display = 'none';}// 检测是否点击了3D模型const pickedObject = viewer.scene.pick(movement.position);if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.node) && Cesium.defined(pickedObject.feature)) {selectedFeature = pickedObject.feature;// 获取建筑名称属性// 注意:实际项目中,属性名称可能不同,需要根据您的3D Tiles模型属性进行调整const buildingName = selectedFeature.getProperty('name') || selectedFeature.getProperty('building') || '未知建筑';// 获取建筑位置,用于定位信息框const cartesian = viewer.scene.pickPosition(movement.position);if (Cesium.defined(cartesian)) {const canvasPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian);if (Cesium.defined(canvasPosition)) {// 设置信息框位置和内容infoBox.style.left = `${canvasPosition.x}px`;infoBox.style.top = `${canvasPosition.y}px`;infoBox.innerHTML = `<h3>${buildingName}</h3><p>点击空白处关闭</p>`;infoBox.style.display = 'block';}}}}, Cesium.ScreenSpaceEventType.LEFT_CLICK);// 重置视角按钮事件document.getElementById('resetView').addEventListener('click', function() {if (campusTileset.ready) {viewer.zoomTo(campusTileset, new Cesium.HeadingPitchRange(0.5, -0.2, 1000));}});// 添加一些交互效果viewer.scene.postRender.addEventListener(function() {if (selectedFeature && infoBox.style.display !== 'none') {// 实时更新信息框位置,确保跟随模型移动const position = selectedFeature.getGeometry().boundingSphere.center;const canvasPosition = viewer.scene.cartesianToCanvasCoordinates(position);if (Cesium.defined(canvasPosition)) {infoBox.style.left = `${canvasPosition.x}px`;infoBox.style.top = `${canvasPosition.y}px`;}}});</script>
</body>
</html>
实现 “校园内两点距离测量” 功能。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>校园3D模型距离测量工具</title><!-- 引入Cesium库 --><script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><!-- 引入Tailwind CSS --><script src="https://cdn.tailwindcss.com"></script><!-- 引入Font Awesome --><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><style>/* 设置Cesium容器样式 */#cesiumContainer {width: 100%;height: 100vh;margin: 0;padding: 0;overflow: hidden;}/* 测量信息面板样式 */.measurement-panel {position: absolute;top: 20px;left: 20px;background-color: rgba(255, 255, 255, 0.95);border-radius: 8px;padding: 15px;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);z-index: 100;min-width: 280px;}.measurement-panel h3 {margin: 0 0 10px 0;color: #2c3e50;font-size: 18px;display: flex;align-items: center;gap: 8px;}.measurement-info {margin: 10px 0;padding: 10px;background-color: #f8fafc;border-radius: 6px;display: none;}.distance-value {font-size: 20px;font-weight: bold;color: #1e40af;margin: 5px 0;}/* 按钮样式 */.btn {padding: 8px 16px;border-radius: 4px;border: none;cursor: pointer;font-weight: 500;transition: all 0.2s ease;display: inline-flex;align-items: center;gap: 6px;}.btn-primary {background-color: #3b82f6;color: white;}.btn-primary:hover {background-color: #2563eb;}.btn-secondary {background-color: #f1f5f9;color: #334155;}.btn-secondary:hover {background-color: #e2e8f0;}.button-group {display: flex;gap: 10px;margin-top: 10px;}/* 提示信息样式 */.tooltip {position: absolute;background-color: rgba(0, 0, 0, 0.7);color: white;padding: 6px 10px;border-radius: 4px;font-size: 14px;pointer-events: none;z-index: 101;white-space: nowrap;}/* 测量点和线的样式 */.measure-point {color: #ef4444;}.measure-line {color: #3b82f6;}/* 加载指示器 */.loading-indicator {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: rgba(0, 0, 0, 0.7);color: white;padding: 15px 30px;border-radius: 5px;z-index: 1000;display: flex;align-items: center;gap: 10px;}</style>
</head>
<body><!-- Cesium容器 --><div id="cesiumContainer"></div><!-- 测量控制面板 --><div class="measurement-panel"><h3><i class="fa fa-ruler text-blue-600"></i> 校园距离测量</h3><p class="text-sm text-gray-600"><i class="fa fa-info-circle text-blue-500"></i> 点击开始测量,在场景中选择两个点计算距离</p><div class="button-group"><button id="startMeasure" class="btn btn-primary"><i class="fa fa-play"></i> 开始测量</button><button id="clearMeasure" class="btn btn-secondary" disabled><i class="fa fa-eraser"></i> 清除</button></div><div id="measurementResult" class="measurement-info"><p class="text-sm text-gray-600">测量结果:</p><div class="distance-value" id="distanceValue">0.00 米</div><p class="text-xs text-gray-500"><i class="fa fa-lightbulb-o"></i> 点击场景可继续测量新的两点</p></div></div><!-- 加载指示器 --><div id="loadingIndicator" class="loading-indicator"><i class="fa fa-circle-o-notch fa-spin fa-lg"></i><span>正在加载校园模型...</span></div><script>// 初始化CesiumCesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1NzdiYzQwOS1hYzQ2LTRhYTctYjI1YS1hNTk0M2ZjM2Y5YzciLCJpZCI6MTA1MTM5LCJpYXQiOjE2Nzg1MDc4NDR9.37X3bKc67tQ9o1LrW4b6vDn93fE7d5y4J4a1QrBZfV0';// 创建Cesium Viewer实例const viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain(),animation: false,baseLayerPicker: true,fullscreenButton: true,geocoder: false,homeButton: false,infoBox: false,sceneModePicker: true,selectionIndicator: false,timeline: false,navigationHelpButton: true,// 使用天地图作为底图imageryProvider: new Cesium.WebMapTileServiceImageryProvider({url: "http://t0.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=您的天地图密钥",layer: "img",style: "default",format: "image/jpeg",tileMatrixSetID: "w",maximumLevel: 18,credit: "天地图全球影像"})});// 隐藏Cesium标志viewer._cesiumWidget._creditContainer.style.display = "none";// 加载校园3D Tiles模型const campusTileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({url: Cesium.IonResource.fromAssetId(96188), // 示例3D Tiles模型style: new Cesium.Cesium3DTileStyle({color: "color('#ffffff')"})}));// 当模型加载完成后campusTileset.readyPromise.then(function(tileset) {// 隐藏加载指示器document.getElementById('loadingIndicator').style.display = 'none';// 调整相机视角以完整显示模型viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, 1000));console.log('校园模型加载完成');}).catch(function(error) {console.error('加载校园模型时出错:', error);const loadingIndicator = document.getElementById('loadingIndicator');loadingIndicator.innerHTML = '<i class="fa fa-exclamation-triangle text-yellow-400"></i><span>模型加载失败,请重试</span>';});// 测量相关变量let isMeasuring = false;let measurementPoints = [];let measurementEntities = [];let distanceLabel = null;// DOM元素const startMeasureBtn = document.getElementById('startMeasure');const clearMeasureBtn = document.getElementById('clearMeasure');const measurementResult = document.getElementById('measurementResult');const distanceValue = document.getElementById('distanceValue');// 开始测量按钮事件startMeasureBtn.addEventListener('click', function() {if (isMeasuring) {// 停止测量isMeasuring = false;startMeasureBtn.innerHTML = '<i class="fa fa-play"></i> 开始测量';startMeasureBtn.classList.remove('bg-red-500', 'hover:bg-red-600');startMeasureBtn.classList.add('btn-primary');} else {// 开始测量isMeasuring = true;startMeasureBtn.innerHTML = '<i class="fa fa-stop"></i> 停止测量';startMeasureBtn.classList.remove('btn-primary');startMeasureBtn.classList.add('bg-red-500', 'hover:bg-red-600');// 清除现有测量clearMeasurements();}});// 清除测量按钮事件clearMeasureBtn.addEventListener('click', function() {clearMeasurements();});// 清除所有测量结果function clearMeasurements() {// 移除所有测量实体measurementEntities.forEach(entity => {viewer.entities.remove(entity);});// 重置变量measurementPoints = [];measurementEntities = [];distanceLabel = null;// 更新UImeasurementResult.style.display = 'none';clearMeasureBtn.disabled = true;}// 创建测量点function createMeasurementPoint(position, index) {const pointEntity = viewer.entities.add({position: position,point: {pixelSize: 10,color: Cesium.Color.RED,outlineColor: Cesium.Color.WHITE,outlineWidth: 2,heightReference: Cesium.HeightReference.CLAMP_TO_GROUND},label: {text: (index + 1).toString(),font: '14px sans-serif',fillColor: Cesium.Color.WHITE,backgroundColor: Cesium.Color.RED,padding: new Cesium.Cartesian2(5, 5),verticalOrigin: Cesium.VerticalOrigin.BOTTOM,pixelOffset: new Cesium.Cartesian2(0, -10),heightReference: Cesium.HeightReference.CLAMP_TO_GROUND}});measurementEntities.push(pointEntity);return pointEntity;}// 创建测量线function createMeasurementLine(points) {const lineEntity = viewer.entities.add({polyline: {positions: points,width: 3,material: new Cesium.PolylineGlowMaterialProperty({glowPower: 0.2,color: Cesium.Color.BLUE}),clampToGround: true}});measurementEntities.push(lineEntity);return lineEntity;}// 创建距离标签function createDistanceLabel(position, distance) {// 移除现有标签if (distanceLabel) {viewer.entities.remove(distanceLabel);}// 格式化距离显示let distanceText;if (distance >= 1000) {distanceText = (distance / 1000).toFixed(2) + ' 千米';} else {distanceText = distance.toFixed(2) + ' 米';}// 更新结果面板distanceValue.textContent = distanceText;measurementResult.style.display = 'block';clearMeasureBtn.disabled = false;// 创建新标签distanceLabel = viewer.entities.add({position: position,label: {text: distanceText,font: '16px sans-serif',fillColor: Cesium.Color.WHITE,backgroundColor: Cesium.Color.BLUE,padding: new Cesium.Cartesian2(8, 4),horizontalOrigin: Cesium.HorizontalOrigin.CENTER,verticalOrigin: Cesium.VerticalOrigin.BOTTOM,pixelOffset: new Cesium.Cartesian2(0, -10),heightReference: Cesium.HeightReference.CLAMP_TO_GROUND}});measurementEntities.push(distanceLabel);return distanceLabel;}// 计算两点之间的距离(米)function calculateDistance(point1, point2) {return Cesium.Cartesian3.distance(point1, point2);}// 处理鼠标点击事件viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {if (!isMeasuring) return;// 尝试从点击位置获取坐标const ray = viewer.camera.getPickRay(movement.position);const hitPosition = viewer.scene.globe.pick(ray, viewer.scene);if (Cesium.defined(hitPosition)) {// 添加点到测量点数组measurementPoints.push(hitPosition);createMeasurementPoint(hitPosition, measurementPoints.length - 1);// 当有两个点时,创建线并计算距离if (measurementPoints.length === 2) {// 创建连接线createMeasurementLine(measurementPoints);// 计算距离const distance = calculateDistance(measurementPoints[0], measurementPoints[1]);// 计算中点位置,用于显示距离标签const midPoint = Cesium.Cartesian3.lerp(measurementPoints[0], measurementPoints[1], 0.5, new Cesium.Cartesian3());// 创建距离标签createDistanceLabel(midPoint, distance);// 重置测量点数组,允许继续测量新的两点measurementPoints = [];}}}, Cesium.ScreenSpaceEventType.LEFT_CLICK);</script>
</body>
</html>
3.Cesium 进阶
3.1 自定义图层:叠加 SVG/Canvas 图层到地球
概念
在 Cesium 中,“自定义图层叠加 SVG/Canvas 图层到地球”,核心是利用 Cesium 提供的 ImageryProvider
接口,将 SVG(矢量图形)或 Canvas(动态绘制图形)作为 “图像资源”,贴到地球表面的指定区域或全局,实现个性化图形标注、动态数据可视化等需求。
为什么能叠加?
地球表面的影像(如卫星图、地图)本质是通过
ImageryProvider
提供的 “图片”,SVG/Canvas 本质也是一种 “可绘制的图像”,因此可以自定义一个ImageryProvider
,让它返回 SVG/Canvas 生成的图像,再贴到地球对应位置。SVG vs Canvas 的区别?
SVG:矢量图形,放大不失真,适合静态标注(如学校 logo、区域边界、固定图标);
Canvas:像素图形,适合动态绘制(如实时数据点、动画效果、临时标记)。
示例:SVG 图层叠加:静态矢量标注
// 1. 初始化 Cesium 地球场景
const viewer = new Cesium.Viewer('cesiumContainer', {imageryProvider: new Cesium.ArcGisMapServerImageryProvider({url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' // 底图用ArcGIS卫星图}),baseLayerPicker: false, // 隐藏底图切换按钮geocoder: false // 隐藏地址搜索框
});// 2. 自定义 SVG ImageryProvider(核心:返回SVG生成的图像)
class SvgImageryProvider extends Cesium.ImageryProvider {constructor(options) {super();this._extent = options.extent; // 图层覆盖的经纬度范围(必填)this._svgContent = options.svgContent; // SVG内容(必填)this._tileWidth = 256; // 每个瓦片的像素宽度(固定常用值)this._tileHeight = 256; // 每个瓦片的像素高度(固定常用值)this._maximumLevel = 18; // 最大缩放级别(越大越清晰)this._minimumLevel = 0; // 最小缩放级别}// 关键方法:Cesium 会自动调用此方法获取指定瓦片的图像requestImage(x, y, level, request) {// 创建一个Image对象,加载SVG内容const image = new Image();// SVG内容需要转成DataURL格式(Cesium能识别的图像格式)image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(this._svgContent);// 返回Promise,等待SVG加载完成后返回图像return new Promise((resolve) => {image.onload = function () {resolve(image);};});}// 以下是必须实现的接口方法(固定模板)get extent() { return this._extent; }get tileWidth() { return this._tileWidth; }get tileHeight() { return this._tileHeight; }get maximumLevel() { return this._maximumLevel; }get minimumLevel() { return this._minimumLevel; }get tilingScheme() { return new Cesium.GeographicTilingScheme(); } // 经纬度瓦片方案get hasAlphaChannel() { return true; } // 支持透明(避免遮挡底图)
}// 3. 定义SVG内容(红色圆形,直径80像素)
const svgContent = `<svg width="80" height="80" xmlns="http://www.w3.org/2000/svg"><circle cx="40" cy="40" r="30" fill="red" opacity="0.7"/> <!-- 红色圆形,半透明 --><text x="40" y="45" text-anchor="middle" fill="white" font-size="12">标记点</text> <!-- 文字标注 --></svg>
`;// 4. 定义SVG图层的覆盖范围(经纬度范围:围绕天安门的小区域,避免全局显示)
const svgExtent = Cesium.Rectangle.fromDegrees(116.403, 39.914, // 左下角经纬度(西,南)116.405, 39.916 // 右上角经纬度(东,北)
);// 5. 创建SVG图层并添加到地球
const svgImageryLayer = new Cesium.ImageryLayer(new SvgImageryProvider({extent: svgExtent,svgContent: svgContent})
);
viewer.imageryLayers.add(svgImageryLayer);// 6. 视角定位到标记点(方便查看效果)
viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.403874, 39.914885, 5000), // 高度5000米orientation: { heading: 0, pitch: -45 } // 朝向:水平向前,俯视45度
});
示例:Canvas 图层叠加:动态绘制
// 1. 初始化 Cesium 地球场景(同SVG示例,底图不变)
const viewer = new Cesium.Viewer('cesiumContainer', {imageryProvider: new Cesium.ArcGisMapServerImageryProvider({url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'}),baseLayerPicker: false,geocoder: false
});// 2. 自定义 Canvas ImageryProvider(核心:动态生成Canvas图像)
class CanvasImageryProvider extends Cesium.ImageryProvider {constructor(options) {super();this._extent = options.extent; // 覆盖范围this._tileWidth = 256;this._tileHeight = 256;this._maximumLevel = 18;this._minimumLevel = 0;this._canvas = document.createElement('canvas'); // 创建Canvas元素this._canvas.width = this._tileWidth;this._canvas.height = this._tileHeight;this._ctx = this._canvas.getContext('2d'); // 获取绘图上下文this._opacity = 1; // 用于控制闪烁效果}// 关键方法:动态绘制Canvas并返回图像requestImage(x, y, level, request) {// 1. 清空Canvas(避免上一帧残留)this._ctx.clearRect(0, 0, this._tileWidth, this._tileHeight);// 2. 动态修改透明度(实现闪烁效果)this._opacity = Math.sin(Date.now() / 500) * 0.5 + 0.5; // 0.5~1之间波动// 3. 绘制蓝色方块(位置在Canvas中心,大小50x50)this._ctx.fillStyle = `rgba(0, 100, 255, ${this._opacity})`; // 蓝色,透明度动态变化this._ctx.fillRect((this._tileWidth - 50) / 2, // 方块左上角x坐标(居中)(this._tileHeight - 50) / 2, // 方块左上角y坐标(居中)50, 50 // 方块宽高);// 4. 绘制文字(实时显示透明度)this._ctx.fillStyle = 'white';this._ctx.font = '12px Arial';this._ctx.textAlign = 'center';this._ctx.fillText(`透明度: ${this._opacity.toFixed(2)}`, this._tileWidth / 2, this._tileHeight / 2 + 30);// 5. 返回Canvas生成的图像(转成DataURL)const image = new Image();image.src = this._canvas.toDataURL('image/png');return new Promise((resolve) => {image.onload = function () {resolve(image);};});}// 必须实现的接口方法(同SVG示例)get extent() { return this._extent; }get tileWidth() { return this._tileWidth; }get tileHeight() { return this._tileHeight; }get maximumLevel() { return this._maximumLevel; }get minimumLevel() { return this._minimumLevel; }get tilingScheme() { return new Cesium.GeographicTilingScheme(); }get hasAlphaChannel() { return true; }
}// 3. 定义Canvas图层的覆盖范围(上海附近小区域)
const canvasExtent = Cesium.Rectangle.fromDegrees(121.457, 31.221, // 左下角(西,南)121.459, 31.223 // 右上角(东,北)
);// 4. 创建Canvas图层并添加到地球
const canvasImageryLayer = new Cesium.ImageryLayer(new CanvasImageryProvider({extent: canvasExtent})
);
viewer.imageryLayers.add(canvasImageryLayer);// 5. 视角定位到上海
viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(121.4581, 31.2222, 5000),orientation: { heading: 0, pitch: -45 }
});// 6. 开启实时更新(让Canvas闪烁效果生效)
viewer.scene.postRender.addEventListener(() => {// 每次场景渲染后,强制更新Canvas图层canvasImageryLayer.requestRender();
});
3.2 性能优化:模型裁剪(ClippingPlane
)、数据分块加载
模型裁剪(ClippingPlane) 和数据分块加载是两种关键的性能优化技术。它们从不同角度解决 3D 场景卡顿问题:前者通过 “隐藏不需要的部分” 减少渲染压力,后者通过 “按需加载数据” 减少内存占用。
模型裁剪
想象你拿着一把 “虚拟剪刀”,可以沿着某个平面切开 3D 模型,只显示需要的部分。这样 GPU 就不用渲染被遮挡或无关的区域,从而提升帧率。
适用场景
大型建筑内部查看(如只显示某一层)
地形剖面分析(如查看山脉的横截面)
复杂模型的局部细节展示
// 初始化地球 const viewer = new Cesium.Viewer('cesiumContainer');// 添加一个3D建筑模型 const model = viewer.scene.primitives.add(new Cesium.ModelPrimitive({url: Cesium.IonResource.fromAssetId(96188), // 教学楼模型modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(116.39, 39.91, 0)),scale: 5.0}) );// 创建裁剪平面:沿X轴方向,在模型中心位置切开 // 平面方程:ax + by + cz + d = 0 const clippingPlane = new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), // 平面法向量(X轴方向)0.0 // 距离原点的距离(模型中心) );// 应用裁剪平面到模型 model.clippingPlanes = new Cesium.ClippingPlaneCollection({planes: [clippingPlane], // 可以添加多个平面edgeWidth: 1.0, // 裁剪边缘高亮宽度edgeColor: Cesium.Color.RED // 裁剪边缘颜色 });// 相机飞到模型位置 viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.39, 39.91, 300),duration: 2 });
数据分块加载
将大型 3D 数据(如全球地形、城市模型)分割成多个小块(Tile),根据当前视野范围和精度需求,只加载必要的块。离得近就加载详细块,离得远就加载粗略块。
适用场景
全球地形展示(如 Cesium 的地形分块)
城市级 3D 模型(如倾斜摄影数据)
大规模点云数据可视化
// 初始化地球时配置分块地形 const viewer = new Cesium.Viewer('cesiumContainer', {// 使用Cesium的全球分块地形terrainProvider: new Cesium.CesiumWorldTerrainProvider({requestVertexNormals: true, // 按需加载顶点法线(影响光照)requestWaterMask: true // 按需加载水面数据}),// 隐藏无关控件timeline: false,animation: false });// 飞到地形起伏明显的区域(如喜马拉雅山脉) viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(86.925, 27.988, 500000),duration: 3 });// 监听地形块加载事件 viewer.terrainProvider.tileLoadProgressEvent.addEventListener(function(progress) {console.log(`地形加载进度:${progress}%`); });
3.3 集成 Three.js:在 Cesium 场景中嵌入 Three.js 模型
在三维开发中,Cesium 擅长地理空间可视化(如地球、地形、卫星影像),而 Three.js 更擅长通用 3D 模型处理和交互。将两者集成可以结合各自优势:用 Cesium 提供真实地理环境,用 Three.js 处理复杂模型或特殊交互效果。
核心原理:坐标转换与渲染同步
Cesium 和 Three.js 是两个独立的 3D 引擎,集成的关键是解决两个问题:
坐标统一:将 Three.js 模型的局部坐标转换为 Cesium 的地理空间坐标(经纬度 + 高度)。
渲染同步:让两个引擎的相机视角、旋转、缩放保持一致,看起来像一个整体场景。
示例
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Cesium 集成 Three.js 示例</title><!-- 引入 Cesium --><script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><!-- 引入 Three.js --><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script><style>#cesiumContainer { width: 100vw; height: 100vh; margin: 0; }</style>
</head>
<body><div id="cesiumContainer"></div><script>// 1. 初始化 Cesium 地球Cesium.Ion.defaultAccessToken = '你的Cesium令牌'; // 需替换为自己的令牌const viewer = new Cesium.Viewer('cesiumContainer', {terrainProvider: Cesium.createWorldTerrain(),imageryProvider: new Cesium.BingMapsImageryProvider({url: 'https://dev.virtualearth.net',mapStyle: Cesium.BingMapsStyle.AERIAL}),timeline: false,animation: false});// 2. 定义模型放置的地理坐标(经纬度)const position = Cesium.Cartesian3.fromDegrees(116.3904, // 经度(示例:北京附近)39.9061, // 纬度100 // 高度(距离地面100米));// 3. 创建 Three.js 场景、相机、渲染器const threeScene = new THREE.Scene();const threeCamera = new THREE.PerspectiveCamera(60, // 视野角度window.innerWidth / window.innerHeight, // 宽高比0.1, 1000 // 近/远裁剪面);const threeRenderer = new THREE.WebGLRenderer({alpha: true // 透明背景,避免遮挡Cesium场景});threeRenderer.setSize(window.innerWidth, window.innerHeight);// 4. 创建 Three.js 模型(示例:一个旋转的立方体)const geometry = new THREE.BoxGeometry(20, 20, 20); // 立方体尺寸(单位:米)const material = new THREE.MeshBasicMaterial({color: 0xff0000,wireframe: true // 线框模式,便于观察});const cube = new THREE.Mesh(geometry, material);threeScene.add(cube);// 5. 将 Three.js 渲染结果嵌入 Cesium 场景// 创建一个Cesium图层,用于显示Three.js的渲染结果const threeLayer = new Cesium.CustomDataSource('Three.js Layer');viewer.dataSources.add(threeLayer);// 创建一个矩形实体,作为Three.js渲染结果的"画布"const rectangle = threeLayer.entities.add({rectangle: {coordinates: Cesium.Rectangle.fromDegrees(-180, -90, 180, 90 // 覆盖整个地球),material: new Cesium.Material({fabric: {type: 'Image',uniforms: {image: threeRenderer.domElement // 绑定Three.js的渲染画布}}})}});// 6. 核心:同步坐标和相机视角function syncCesiumAndThree() {// a. 将Cesium的地理坐标转换为Three.js的局部坐标const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);const translation = new Cesium.Cartesian3();Cesium.Matrix4.getTranslation(modelMatrix, translation);// b. 同步相机:让Three.js相机和Cesium相机视角一致const cesiumCamera = viewer.camera;threeCamera.position.set(cesiumCamera.position.x,cesiumCamera.position.y,cesiumCamera.position.z);threeCamera.direction.set(cesiumCamera.direction.x,cesiumCamera.direction.y,cesiumCamera.direction.z);threeCamera.up.set(cesiumCamera.up.x,cesiumCamera.up.y,cesiumCamera.up.z);threeCamera.updateMatrixWorld(true);// c. 让Three.js模型随地球旋转(保持在固定经纬度)cube.position.set(translation.x, translation.y, translation.z);cube.rotation.y += 0.01; // 模型自身旋转(演示用)// d. 渲染Three.js场景threeRenderer.render(threeScene, threeCamera);// 循环执行同步requestAnimationFrame(syncCesiumAndThree);}// 启动同步syncCesiumAndThree();// 7. 相机飞到模型位置viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(116.3904, 39.9061, 500),duration: 3});</script>
</body>
</html>
应用场景与优势
- 复杂模型交互:用 Three.js 实现 Cesium 不擅长的模型动画(如机械臂运动、粒子效果)。
定制化渲染:用 Three.js 的特殊材质(如辉光、折射)增强模型视觉效果。
复用现有资源:直接使用 Three.js 生态中的模型库(如
.glb
模型),无需转换格式。
注意事项
性能消耗会增加(双引擎同时运行),需控制模型复杂度。
坐标同步精度可能受地球曲率影响,近距离查看时需优化。
推荐在 Cesium 处理地理相关逻辑,Three.js 专注于模型本身的交互。