Cesium快速入门到精通系列教程二:添加地形与添加自定义地形、相机控制 - 指南

一、添加地形与添加自定义地形

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、从cesiumlab下载工具进行数据转换:

安装下载的工具,比如当前版本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

二、相机的方向和位置

在Cesium 1.93中,相机的方向和位置控制是三维场景交互的核心。

1、相机坐标系与关键概念

1.1 相机坐标系基础

将相机比喻成直立行走的人,镜头好比人的视野。

  • 位置(Position):相机在三维空间中的笛卡尔坐标(Cartesian3),以地球质心为原点。
  • 方向(Direction):相机的朝向,由视线向量(View Vector)表示,指向场景中的目标点。
  • 上方向(Up Vector):相机的 “上方” 方向,默认与地球表面垂直(Z 轴正方向)
    1. heading​​:绕Y轴旋转(正北为0°,向东为正方向)。
    2. ​​pitch​​:绕X轴旋转(-90°为俯视地面,0°为平视,正值为仰视)。
    3. ​​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 实现镜头飞向故宫的完整示例,包含了基础的场景设置、相机飞行动画以及简单的交互控制。

                   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);  });})  * {  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;}

posted @ 2025-07-22 09:27  yfceshi  阅读(15)  评论(0)    收藏  举报