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 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>
相关推荐
gis_rc5 天前
python下shp转3dtiles
python·3d·cesium·3dtiles·数字孪生模型
grasperp6 天前
3DTiles数据切片工具,支持LAS、OBJ、FBX 3DTiles怎么切片?3DTiles切片
cesium·3dtiles·三维gis·3dtiles切片·数据切片
duansamve8 天前
Cesium中实现在地图上移动/旋转点、线、面
cesium
冥界摄政王9 天前
CesiumJS学习第四章 替换指定3D建筑模型
3d·vue·html·webgl·js·cesium
冥界摄政王11 天前
Cesium学习第二章 camera 相机
node.js·html·vue3·js·cesium
冥界摄政王12 天前
Cesium学习第一章 安装下载 基于vue3引入Cesium项目开发
vue·vue3·html5·webgl·cesium
你们瞎搞14 天前
Cesium加载20GB航测影像.tif
前端·cesium·gdal·地图切片
闲云一鹤15 天前
Cesium 使用 Turf 实现坐标点移动(偏移)
前端·gis·cesium
二狗哈15 天前
Cesium快速入门34:3dTile高级样式设置
前端·javascript·算法·3d·webgl·cesium·地图可视化
二狗哈16 天前
Cesium快速入门33:tile3d设置样式
3d·状态模式·webgl·cesium·地图可视化