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

发布于:2025-06-03 ⋅ 阅读:(25) ⋅ 点赞:(0)

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

在 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 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.2 flyTo:动画飞行至目标​​

特点​​:支持平滑过渡,可设置飞行时长、视角偏移等。

关键参数​​:

  • duration:动画时间(秒)。
  • pitchAdjustHeight:高度超过此值时自动调整俯仰角。
viewer.camera.flyTo({
    destination: position,
    orientation: orientation,
    duration: 5,  // 5秒动画
    pitchAdjustHeight: -90  // 强制俯视地面
});

2.3 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.4 viewBoundingSphere:环绕目标区域​​

适用场景​​:室内或小范围模型浏览。

const boundingSphere = new Cesium.BoundingSphere(center, radius);
viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0, 0, 0));

2.5 方向控制的进阶应用

2.5.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.5.2 动态方向控制​​

通过事件监听实时更新相机方向: 

viewer.scene.preRender.addEventListener(() => {
    const heading = viewer.camera.heading;
    const pitch = viewer.camera.pitch;
    console.log(`当前航向:${Cesium.Math.toDegrees(heading).toFixed(2)}°`);
});
2.5.3 实体跟随模式​​

使用trackedEntity让相机自动跟随移动目标: 

viewer.trackedEntity = entity;  // 实体ID或对象
2.5.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.6 常见问题与注意事项

  • 坐标系一致性​​

确保位置和方向参数在同一坐标系下(如WGS84)。若使用局部坐标,需通过变换矩阵转换。

  • 俯仰角限制​​

默认俯仰角范围为[-π/2, π/2],超出可能导致视角异常。可通过viewer.camera.pitchLimits调整。

  • ​​性能优化​​

频繁调用flyTo或setView时,建议合并连续操作,避免卡顿。

2.7 完整示例:相机环绕目标点

// 定义目标点(北京天安门)
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.8 相机动画与相机动态交互

 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>


网站公告

今日签到

点亮在社区的每一天
去签到