Cesium快速入门到精通系列教程二添加影像添加地形与添加自定义地形相机控制
Cesium快速入门到精通系列教程二:添加影像、添加地形与添加自定义地形、相机控制
一、添加影像:
在Cesium中添加影像图层主要通过ImageryProvider及其子类实现,以下是具体方法和步骤:
1、核心类与流程
ImageryProvider
抽象基类,定义影像数据加载的通用接口,其子类支持多种数据源(如WMS、WMTS、TMS等)。
ImageryLayer
表示单个影像图层,控制显示属性(透明度、亮度等)。
ImageryLayerCollection
管理多个图层的叠加顺序和可见性。
2、添加影像的常用方法
2.1 使用UrlTemplateImageryProvider(适用于TMS/WMTS)
const viewer = new Cesium.Viewer('cesiumContainer');
// 在某些情况下需要添加如下代码,否则地球可能不会显示
viewer.imageryLayers.removeAll();
const provider = new Cesium.UrlTemplateImageryProvider({
url: 'http://内网IP:端口/tiles/{z}/{x}/{y}.png', // 瓦片URL模板
tilingScheme: new Cesium.WebMercatorTilingScheme(), // 投影方案(Web墨卡托)
minimumLevel: 0, // 最小层级
maximumLevel: 18, // 最大层级
rectangle: Cesium.Rectangle.fromDegrees(-180, -90, 180, 90) // 覆盖范围
});
viewer.imageryLayers.addImageryProvider(provider); // 添加图层[6,8](@ref)
适用场景:加载内网瓦片、高德/腾讯地图等。
注意:若瓦片为TMS标准,需将{y}替换为{reverseY}。
2.2 使用WebMapServiceImageryProvider(适用于WMS服务)
const wmsProvider = new Cesium.WebMapServiceImageryProvider({
url: 'https://example.com/wms',
layers: 'layer1,layer2', // 服务中的图层名称
parameters: {
format: 'image/png',
transparent: true
}
});
viewer.imageryLayers.addImageryProvider(wmsProvider)[3,4](@ref);
适用场景:加载ArcGIS、GeoServer等发布的WMS服务。
2.3 使用预置服务(如Bing Maps、Cesium Ion)
// Bing Maps(需API Key)
const bingProvider = await Cesium.BingMapsImageryProvider.fromUrl(
"https://dev.virtualearth.net", {
key: "your-api-key",
mapStyle: Cesium.BingMapsStyle.AERIAL
}
);
viewer.imageryLayers.addImageryProvider(bingProvider)[6](@ref);
// Cesium Ion(需Asset ID)
const ionProvider = await Cesium.IonImageryProvider.fromAssetId(3812);
viewer.imageryLayers.addImageryProvider(ionProvider)[6,5](@ref)。
3、高级配置
图层控制
- 透明度:imageryLayer.alpha = 0.5。
- 叠加顺序:通过zIndex或raiseToTop()调整。
坐标系纠偏
- 高德/百度地图需自定义TilingScheme以解决GCJ02偏移问题。
性能优化
- 限制minimumLevel/maximumLevel减少加载量。
- 使用proxy解决跨域问题。
二、添加地形与添加自定义地形
在 Cesium 1.93 中添加地形可以通过配置terrainProvider实现。Cesium 支持多种地形数据源,包括 Cesium Ion 提供的全球地形、自定义地形服务以及开源地形数据。下面介绍几种常见的添加地形的方法:
使用 Cesium Ion 全球地形服务
这是最简单的方式,需要一个 Cesium Ion 账户和访问令牌:
// 设置Cesium Ion访问令牌
Cesium.Ion.defaultAccessToken = '你的Cesium Ion令牌';
// 初始化Viewer并启用全球地形
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain({
requestVertexNormals: true, // 启用地形光照
requestWaterMask: true // 启用水面效果
}),
baseLayerPicker: false, // 可选:禁用默认图层选择器
});
添加自定义地形
1、从 下载数据:
数据资源->公开数据->DEM 数字高程数据
2、从 下载工具进行数据转换:
安装下载的工具,比如当前版本cesiumlab4_4.0.8.exe;
打开工具,安装以下方式设置提交即可:
将以上生成的瓦片本地部署,部署的方式很多种,只要保证能通过url在线访问即可:
在代码中加载:
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: new Cesium.CesiumTerrainProvider({
url: 'http://localhost:3000', // 替换为你的服务器地址
requestVertexNormals: true, // 请求法线以支持地形光照
requestWaterMask: true // 请求水掩码以支持水面效果
})
});
// 配置自定义地形服务
const customTerrainProvider = new Cesium.CesiumTerrainProvider({
url: 'http://localhost:3000', // 替换为你的服务器地址
requestVertexNormals: true,
requestWaterMask: true,
isSct: true // 若为 SuperMap iServer 服务需设为 true [6](@ref)
});
// 应用自定义地形
viewer.terrainProvider = customTerrainProvider;
常见问题排查
问题现象 | 解决方案 |
---|---|
地形加载失败 | 检查网络连接和 Cesium Ion 令牌 |
水体效果未显示 | 确认 requestWaterMask: true |
地形贴图模糊 | 增大 viewer.scene.maximumScreenSpaceError 值 |
内存泄漏 | 限制 viewer.scene.globe.tileCacheSize |
解决局域网中加载自定义地形时出现有接口请求401的问题,配置如下:
const viewer = new Cesium.Viewer("cesiumContainer", {
// 1. 强制指定自定义地形服务
terrainProvider: new Cesium.CesiumTerrainProvider({
url: 'http://localhost:3000/',
requestVertexNormals: true, // ✅ 启用地形光照细节
requestWaterMask: false // 关闭水面(除非数据包含水体)
}),
// 2. 禁用所有在线服务依赖
baseLayerPicker: false,
geocoder: false,
homeButton: false,
// 3. 用透明底图替代默认影像
imageryProvider: new Cesium.SingleTileImageryProvider({
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
rectangle: Cesium.Rectangle.MAX_VALUE
})
});
// 4. 调整渲染参数增强地形细节
viewer.scene.globe.maxScreenSpaceError = 1.5; // 降低细节误差阈值
viewer.scene.globe.depthTestAgainstTerrain = true; // 启用深度检测
验证方式:
<template>
<div id="cesiumContainer"></div>
</template>
<script setup>
import { onMounted } from "vue";
import * as Cesium from "cesium";
import "./Widgets/widgets.css";
window.CESIUM_BASE_URL = "/"; // 设置Cesium静态资源路径(public目录)
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
89.5, // 西边经度
20.4, // 南边维度
110.4, // 东边经度
61.2) // 北边维度
onMounted(async () => {
const viewer = new Cesium.Viewer("cesiumContainer", {
// 显式禁用内置依赖 Ion 的组件
baseLayerPicker: false, // 关闭图层选择器
geocoder: false, // 关闭地理搜索
homeButton: false, // 关闭主页按钮
sceneModePicker: false, // 关闭场景模式切换
navigationHelpButton: false, // 关闭导航帮助
// 使用纯色背景替代默认影像
imageryProvider: new Cesium.SingleTileImageryProvider({
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
rectangle: Cesium.Rectangle.MAX_VALUE
})
});
const gaodeVector = new Cesium.UrlTemplateImageryProvider({
url: 'http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
subdomains: ['1', '2', '3', '4'],
maximumLevel: 19,
credit: '高德地图'
});
// 添加到地图
viewer.imageryLayers.addImageryProvider(gaodeVector);
// 4. 调整渲染参数增强地形细节
viewer.scene.globe.maxScreenSpaceError = 1.5; // 降低细节误差阈值
viewer.scene.globe.depthTestAgainstTerrain = true; // 启用深度检测
const guangzhouTowerPosition = {
longitude: 113.3244,
latitude: 23.1049,
height: 600 // 广州塔实际高度约600米
};
// 广州塔视角
const guangzhouTowerView = {
destination: Cesium.Cartesian3.fromDegrees(
guangzhouTowerPosition.longitude,
guangzhouTowerPosition.latitude,
2000
),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-30.0),
roll: 0.0
},
duration: 3 // 飞行时间(秒)
};
viewer.camera.flyTo(guangzhouTowerView);
})
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
#cesiumContainer {
width: 100wh;
height: 100vh;
}
</style>
三、 影像与地形的区别:
1. 功能目标不同
影像:
通过UrlTemplateImageryProvider加载自定义影像瓦片(如卫星图、地图底图),属于ImageryLayer的范畴,用于覆盖地球表面。影像数据通常以二维瓦片形式呈现,不涉及地形高程变化。
地形:
通过CesiumTerrainProvider加载自定义地形数据(如DEM、地形网格),属于TerrainProvider的范畴,用于定义地球表面的三维起伏(山脉、峡谷等)。地形数据直接影响场景的高程和几何形状。
2. 数据来源与结构差异
影像数据
数据格式:通常为PNG/JPEG等图片瓦片,按{z}/{x}/{y}目录结构组织。
坐标系:需明确指定tilingScheme(如WebMercatorTilingScheme或GeographicTilingScheme)以匹配瓦片投影。
层级控制:通过minimumLevel和maximumLevel限制加载范围,优化性能。
地形数据
数据格式:需为Cesium支持的量化网格格式(如Quantized Mesh),通常由地形服务(如Cesium Ion、SuperMap iServer)生成。
附加效果:通过requestVertexNormals启用光照计算,requestWaterMask支持水面特效。
服务协议:若使用第三方服务(如SuperMap),需配置isSct等参数适配服务端规范。
3. 应用场景
影像适用场景:
替换默认底图(如Bing Maps)为内网发布的影像服务。
叠加多图层(如矢量图+注记层)。
离线环境下加载本地瓦片。
地形适用场景:
实现高精度地形渲染(如山区、城市三维建模)。
结合地形分析功能(如坡度计算、洪水模拟)。
与3D Tiles模型配合,增强场景真实感。
4. 初始化与性能影响
影像的初始化:
需通过viewer.imageryLayers.addImageryProvider()动态添加,若未禁用默认底图(imageryProvider: false),可能导致图层冲突或渲染异常。
性能优化:限制瓦片层级、启用requestRenderMode减少渲染负载。
地形的初始化:
直接替换viewer.terrainProvider,地形数据加载可能较耗时,需注意网络请求优化(如CDN、缓存)。
性能优化:启用requestWaterMask和requestVertexNormals需权衡效果与性能。
总结
影像与地形的关系:影像图层是地球表面的“皮肤”,地形数据是地球的“骨架”,二者可独立配置(如使用自定义影像+默认地形,或反之)。
关键区别:影像处理平面视觉效果,地形处理三维几何结构。实际项目中常需同时定制两者以实现完整的三维场景。
四、相机的方向和位置
在Cesium 1.93中,相机的方向和位置控制是三维场景交互的核心。
1、相机坐标系与关键概念
1.1 相机坐标系基础
将相机比喻成直立行走的人,镜头好比人的视野。
- 位置(Position):相机在三维空间中的笛卡尔坐标(Cartesian3),以地球质心为原点。
- 方向(Direction):相机的朝向,由视线向量(View Vector)表示,指向场景中的目标点。
- 上方向(Up Vector):相机的 “上方” 方向,默认与地球表面垂直(Z 轴正方向)。
- heading:绕Y轴旋转(正北为0°,向东为正方向)。
- pitch:绕X轴旋转(-90°为俯视地面,0°为平视,正值为仰视)。
- roll:绕Z轴旋转(默认0°,正值为右倾)。
- 参考系(Reference Frame):相机运动的参考坐标系,通常为ENU(东 - 北 - 上)或ECF(地心地固坐标系)。
const orientation = {
heading: Cesium.Math.toRadians(0), // 正北
pitch: Cesium.Math.toRadians(-90), // 俯视地面
roll: 0.0
};
2、相机控制的核心方法
2.1 设置默认视角
// 设置Cesium默认视角
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
89.5, // 西边经度
20.4, // 南边维度
110.4, // 东边经度
61.2) // 北边维度
2.2 setView:直接设置视角
特点:无动画,立即切换到目标位置和方向。
viewer.camera.setView({
destination: position, // 目标位置(Cartesian3)
orientation: orientation // 方向参数
});
const position = Cesium.Cartesian3.fromDegrees(116.3907917, 39.9158389, 500); // 故宫
const orientation = {
heading: Cesium.Math.toRadians(0), // 正北
pitch: Cesium.Math.toRadians(-90), // 俯视地面
roll: 0.0
};
viewer.camera.setView({
destination: position,
orientation
});
2.3 flyTo:动画飞行至目标
特点:支持平滑过渡,可设置飞行时长、视角偏移等。
关键参数:
- duration:动画时间(秒)。
- pitchAdjustHeight:高度超过此值时自动调整俯仰角。
viewer.camera.flyTo({
destination: position,
orientation: orientation,
duration: 5, // 5秒动画
pitchAdjustHeight: -90 // 强制俯视地面
});
2.4 lookAt:视角锁定目标点
特点:相机位置固定,始终朝向目标点。
参数:target(目标点)和offset(偏移量,支持HeadingPitchRange)。
const center = Cesium.Cartesian3.fromDegrees(116.4, 39.9);
viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(0, -Math.PI/2, 1000));
2.5 viewBoundingSphere:环绕目标区域
适用场景:室内或小范围模型浏览。
const boundingSphere = new Cesium.BoundingSphere(center, radius);
viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0, 0, 0));
2.6 方向控制的进阶应用
2.6.1 局部坐标系转换
使用Transforms.eastNorthUpToFixedFrame将局部坐标转换为全局坐标系:
const localPosition = new Cesium.Cartesian3(10, 20, 0);
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(localPosition);
const globalPosition = Cesium.Matrix4.multiplyByPoint(transform, localPosition);
2.6.2 动态方向控制
通过事件监听实时更新相机方向:
viewer.scene.preRender.addEventListener(() => {
const heading = viewer.camera.heading;
const pitch = viewer.camera.pitch;
console.log(`当前航向:${Cesium.Math.toDegrees(heading).toFixed(2)}°`);
});
2.6.3 实体跟随模式
使用trackedEntity让相机自动跟随移动目标:
viewer.trackedEntity = entity; // 实体ID或对象
2.6.4 多阶段飞行
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.90, 1000000),
duration: 3,
orientation: { heading: 0, pitch: -Math.PI/2, roll: 0 },
complete: () => {
// 第一阶段完成后触发第二阶段
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.40, 39.91, 500000),
duration: 2,
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT
});
}
});
效果:分阶段飞行,首阶段俯冲至地面,第二阶段缓升至目标点。
2.7 常见问题与注意事项
- 坐标系一致性
确保位置和方向参数在同一坐标系下(如WGS84)。若使用局部坐标,需通过变换矩阵转换。
- 俯仰角限制
默认俯仰角范围为[-π/2, π/2],超出可能导致视角异常。可通过viewer.camera.pitchLimits调整。
- 性能优化
频繁调用flyTo或setView时,建议合并连续操作,避免卡顿。
2.8 完整示例:相机环绕目标点
// 定义目标点(北京天安门)
const target = Cesium.Cartesian3.fromDegrees(116.397, 39.908, 50);
// 设置相机初始位置和方向
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.397, 39.908, 1000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-30),
roll: 0
}
});
// 启动环绕动画(每5秒绕目标一圈)
viewer.clock.onTick.addEventListener(() => {
const time = Cesium.JulianDate.now(viewer.clock.currentTime);
const angle = (time.secondsOfDay * 360) / 5; // 每5秒旋转360°
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(
116.397 + 10 * Math.cos(Cesium.Math.toRadians(angle)),
39.908 + 10 * Math.sin(Cesium.Math.toRadians(angle)),
1000
),
orientation: {
heading: Cesium.Math.toRadians(angle),
pitch: Cesium.Math.toRadians(-30),
roll: 0
}
});
});
2.9 相机动画与相机动态交互
Cesium 1.93 实现镜头飞向故宫的完整示例,包含了基础的场景设置、相机飞行动画以及简单的交互控制。
<template>
<div id="cesiumContainer"></div>
<div class="controls">
<button id="flyToPalaceBtn">飞向故宫</button>
<button id="flyToGreatWallBtn">飞向长城</button>
<button id="resetViewBtn">重置视角</button>
</div>
</template>
<script setup>
Cesium.Ion.defaultAccessToken = 'Cesium defaultAccessToken'
import { onMounted } from "vue";
import * as Cesium from "cesium";
import "./Widgets/widgets.css";
window.CESIUM_BASE_URL = "/"; // 设置Cesium静态资源路径(public目录)
onMounted(() => {
// 初始化Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
geocoder: false, //设置搜索框是否可见
homeButton: false, // 返回初始位置键是否可见
sceneModePicker: false, // 查看器选择模式选择键是否可见
baseLayerPicker: false, // 图层选择键是否可见
navigationHelpButton: false, // 帮助按钮是否可见
animation: false, // 播放控制按钮是否可见
timeline: false, // 时间轴是否可见
fullscreenButton: false, // 全屏按钮是否可见
terrainProvider: Cesium.createWorldTerrain()
});
// 故宫位置(经纬度和高度)
const palacePosition = {
destination: Cesium.Cartesian3.fromDegrees(116.3907917, 39.9158389, 500), // 经度、纬度、高度(米)
orientation: {
heading: Cesium.Math.toRadians(0.0), // 偏航角(向东)
pitch: Cesium.Math.toRadians(-30.0), // 俯仰角(向下倾斜)
roll: 0.0 // 翻滚角
},
duration: 5, // 飞行持续时间(秒)
maximumHeight: 2000, // 飞行过程中最大高度(米)
curveAmount: 0.5 // 飞行曲线弯曲程度(0-1)
};
// 长城位置(慕田峪段)
const greatWallPosition = {
destination: Cesium.Cartesian3.fromDegrees(116.6558, 40.4139, 500),
orientation: {
heading: Cesium.Math.toRadians(90.0),
pitch: Cesium.Math.toRadians(-20.0),
roll: 0.0
},
duration: 5,
maximumHeight: 3000
};
// 初始视角
const initialView = {
destination: Cesium.Cartesian3.fromDegrees(116.3907917, 39.9158389, 15000),
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-30.0),
roll: 0.0
}
};
// 设置初始视角
viewer.camera.setView(initialView);
// 飞向故宫按钮事件
document.getElementById('flyToPalaceBtn').addEventListener('click', function () {
viewer.camera.flyTo(palacePosition);
});
// 飞向长城按钮事件
document.getElementById('flyToGreatWallBtn').addEventListener('click', function () {
viewer.camera.flyTo(greatWallPosition);
});
// 重置视角按钮事件
document.getElementById('resetViewBtn').addEventListener('click', function () {
viewer.camera.setView(initialView);
});
})
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
#cesiumContainer {
width: 100wh;
height: 100vh;
}
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 100;
}
button {
padding: 8px 16px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #0056b3;
}
</style>