第 2 章:CesiumJS 从入门到精通②:搞懂架构,你就赢了一半
本专栏正在连载中欢迎关注不迷路阅读时间约 20 分钟 | 纯概念干货奠定 Cesium 核心思维基础 文章标签#CesiumJS #WebGIS #三维开发 #前端架构 #GIS 开发写在前面上一篇文章我们搭好了开发环境让一个三维地球跑了起来。说实话代码拷贝粘贴就能跑但你真的理解背后发生什么了吗这篇文章不会写太多代码。我们要做一件更重要的事 ——在脑子里画一张 CesiumJS 的架构地图。我见过太多人直接跳到写代码API 用得飞起但遇到一个莫名其妙的 bug 就卡好几天问题出在哪里—— 他们不知道自己的代码运行在哪个层次上。读完本文你会收获知道 CesiumJS 内部分几层每层职责是什么理解 Viewer → Scene → Globe → Camera 之间的 上下级 关系搞懂 Entity、Primitive、DataSource 三条内容通道的区别知道渲染循环是什么数据是怎么变成屏幕上的像素的在脑子里有一张 出了问题知道去哪找 的排查地图一、先看一张图如果用一句话概括 CesiumJS 的架构那就是分层、解耦、各司其职。我画了一张简化版的结构图你先把这张图印在脑子里后面我会逐层拆开讲┌─────────────────────────────────────────────────┐ │ Viewer │ │ (顶层容器你唯一需要 new 的东西) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ │ │ UI控件 │ │ Scene │ │ DataSources │ │ │ │animation │ │ (场景) │ │ (数据源) │ │ │ │timeline │ │ │ │ Czml/GeoJson │ │ │ │geocoder │ │ │ │ /Kml/Custom │ │ │ └──────────┘ └────┬─────┘ └───────┬───────┘ │ │ │ │ │ │ ┌──────────┼───────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ Globe │ │ Primitives│ │ Entities │ │ │ │ (地球表面)│ │ (底层图元)│ │ (高层实体) │ │ │ │ │ │ │ │ │ │ │ │ 影像图层 │ │ Geometry │ │ Point │ │ │ │ 地形 │ │ Appearance│ │ Billboard │ │ │ │ │ │ Material │ │ Polygon ... │ │ │ └──────────┘ └──────────┘ └──────┬───────┘ │ │ │ │ │ DataSource 加载后 │ │ 自动转为 Entity │ └─────────────────────────────────────────────────┘这张图里有一个非常重要的信息DataSource 加载的数据最终都会变成 Entity。你后面用 GeoJSON、CZML、KML 的时候记住这一点很多事就通了。二、五层架构从底层到顶层CesiumJS 源码目录本身就透露了它的分层设计。源码在packages/下分成这几个目录packages/ ├── engine/ ← 最底层渲染引擎、WebGL 封装 ├── widgets/ ← UI 控件层 ├── scene/ ← 场景层地球、相机、图元、影像 └── datasources/ ← 数据源层GeoJSON / CZML / KML 解析用表格来看更清楚层级源码目录职责你日常打交道多吗渲染引擎层engine/WebGL 封装、着色器、纹理、几何体计算很少除非写自定义 Shader场景层scene/Globe、Camera、Primitive、影像图层、3D Tiles非常频繁数据源层datasources/把 GeoJSON/CZML/KML 解析成 Entity经常加载数据时UI 控件层widgets/时间轴、搜索框、全屏按钮等 UI 组件偶尔配置开关顶层Viewer把所有东西组装到一起每行代码都跟它有关关键结论绝大多数时间你都在和Scene 层和Viewer打交道。Engine 层对你是透明的除非你写自定义着色器。三、核心对象关系谁管谁3.1 Viewer —— 唯一的入口const viewer new Cesium.Viewer(container);Viewer是你创建的唯一顶层对象。它内部自动创建了viewer.scene // Scene 对象 —— 管理所有渲染内容 viewer.camera // Camera 对象 —— 控制视角 viewer.entities // EntityCollection —— 高层实体集合 viewer.dataSources // DataSourceCollection —— 数据源集合 viewer.clock // Clock 对象 —— 控制时间和动画 viewer.imageryLayers // ImageryLayerCollection —— 影像图层集合记住这个心智模型Viewer 是一个大管家它下面管着六个部门 —— 场景部、相机部、实体部、数据部、时间部、影像部。3.2 Scene —— 渲染总指挥viewer.scene.primitives // 底层图元集合 viewer.scene.globe // 地球表面影像地形 viewer.scene.skyBox // 天空盒星空 viewer.scene.skyAtmosphere // 大气层效果 viewer.scene.sun // 太阳光源 viewer.scene.moon // 月亮 viewer.scene.postProcessStages // 后期处理效果Scene 负责把所有东西画到屏幕上。它是一个渲染引擎的抽象你在 Scene 上添加的内容Primitive、3D Tiles、粒子最终都会被 WebGL 渲染。3.3 Globe —— 地球的 皮肤viewer.scene.globe.imageryLayers // 影像图层卫星图、路网图等 viewer.scene.globe.terrainProvider // 地形数据源Globe 只负责地球表面的显示—— 底图贴什么影像地形有没有高程除此之外的东西比如飘在空中的线和点不归它管。3.4 Camera —— 你的眼睛viewer.camera.position // 相机位置Cartesian3 viewer.camera.direction // 视线方向 viewer.camera.up // 上方向Camera 决定了你从哪里看地球。flyTo()、zoomTo()、lookAt()本质都是在改 Camera 的位置和朝向。3.5 一张层级关系速查图viewer ├── .scene │ ├── .globe │ │ ├── .imageryLayers ← 卫星底图等影像 │ │ └── .terrainProvider ← 地形高程 │ ├── .primitives ← Primitive、3D Tiles、粒子 │ ├── .groundPrimitives ← 贴合地面的几何体 │ ├── .skyBox / .skyAtmosphere / .sun / .moon │ ├── .postProcessStages ← 后处理特效 │ └── .camera ← 与 viewer.camera 是同一个引用 ├── .camera ← 相机的快捷引用 ├── .entities ← Entity 集合 ├── .dataSources ← 数据源集合 ├── .imageryLayers ← 影像图层的快捷引用 ├── .clock ← 时钟/动画控制 ├── .animation / .timeline ... ← UI 控件 └── .screenSpaceEventHandler ← 鼠标/触摸事件记忆技巧把 CesiumJS 想象成一间剧院 ——Viewer 剧院经理Scene 舞台Globe 舞台地板Camera 观众的眼睛或者说摄像机Entity / Primitive 台上的演员和道具DataSource 从外部送演员上场的通道Clock 播放 / 暂停按钮四、三条内容通道Entity vs Primitive vs DataSource这就是很多初学者最困惑的地方我想在地球上画东西到底该用哪个答案是三条路都能到罗马但路上风景不一样。4.1 Entity —— 高层、声明式、简单// 画一个点3 行代码 viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.4, 39.9), point: { pixelSize: 10, color: Cesium.Color.RED } });特点面向对象你描述 要什么Cesium 自己决定怎么画自动绑定鼠标事件点击自动高亮适合数量少的场景几百个以内很流畅每个 Entity 有独立的内存开销什么时候用标注点、弹窗、Label、简单的点线面、原型验证4.2 Primitive —— 底层、命令式、高性能// 画一个点需要 10 行代码 const geometry new Cesium.SphereGeometry({ radius: 100000 }); const instance new Cesium.GeometryInstance({ geometry, modelMatrix: ... }); const primitive new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.PerInstanceColorAppearance({ ... }) }); viewer.scene.primitives.add(primitive);特点面向计算你告诉 GPU 怎么画Cesium 直接执行支持批量实例化—— 同一个 Geometry 可以画 1 万个只有一次 GPU 调用没有自动交互点击事件需要自己写pick()学习曲线陡峭但性能上限高什么时候用成千上万个对象、需要自定义着色器、性能敏感场景4.3 DataSource —— 数据驱动、批量加载// 加载一个 GeoJSON 文件 const dataSource await Cesium.GeoJsonDataSource.load(china.geojson); viewer.dataSources.add(dataSource); // dataSource.entities 现在包含所有解析出的 Entity特点数据→Entity 自动转换加载完成后dataSource.entities就是一堆 Entity可以整组开关dataSource.show false适合文件 / API 数据加载什么时候用加载 GeoJSON、CZML、KML 文件4.4 一句话决策表场景选什么少量标注 500 个Entity海量点 / 线 / 面 1000 个Primitive从文件 / API 加载数据DataSource需要点击交互Entity自带或 Primitive scene.pick()首次做原型Entity快先跑起来再说⚠️一个常见误解DataSource 不是独立的渲染方式。GeoJSON 文件加载后本质上还是变成 Entity。所以性能上限和 Entity 一样。如果你真有几万个面要渲染加载成 Entity 之后记得考虑转成 Primitive。五、渲染循环数据怎么变成像素你不需要深入了解 WebGL 管线但理解 CesiumJS 的渲染循环对调试性能很有帮助每一帧~16ms目标是60fps 1. Clock 滴答 → 更新当前时间如果有动画 2. Scene 更新 → 检查哪些 Entity/Primitive 的位置变了 3. 视锥剔除 → 判断哪些物体不在视野内跳过不画 4. LOD 选择 → 根据距离选合适的精细度远了就用粗糙模型 5. Globe 更新 → 加载当前视野内的瓦片影像地形 6. WebGL 绘制 → 把一切丢给 GPU渲染到屏幕上关键理解视锥剔除是自动的 —— 你看不到的东西不会消耗性能LOD也是自动的 —— 离得远的模型会自动降精度瓦片加载是异步的 —— 网络慢的时候你会看到 模糊→清晰 的过程 在控制台输入viewer.scene.debugShowFramesPerSecond true可以打开 Cesium 自带的 FPS 显示器。六、几个你该认识的全局常量打开 Cesium 命名空间这几个常量出现频率最高先混个脸熟CesiumMath —— 数学工具集Cesium.Math.PI // 圆周率 Cesium.Math.toRadians(degrees) // 角度 → 弧度 Cesium.Math.toDegrees(radians) // 弧度 → 角度 Cesium.Math.EPSILON1 // 一个非常小的数用于浮点数比较Cartesian3 —— 三维坐标// 从经纬度创建坐标最常用 Cesium.Cartesian3.fromDegrees(lng, lat, height) // 原点 (0,0,0) — 地心 Cesium.Cartesian3.ZERO // 单位向量分别指向 X/Y/Z 轴方向 Cesium.Cartesian3.UNIT_X Cesium.Cartesian3.UNIT_Y Cesium.Cartesian3.UNIT_ZEllipsoid —— 地球椭球体Cesium.Ellipsoid.WGS84 // 地球的 WGS84 椭球体模型最常用Color —— 颜色Cesium.Color.RED // 预定义颜色 Cesium.Color.fromCssColorString(#ff6600) // 从 CSS 颜色创建 Cesium.Color.fromBytes(255, 128, 0, 255) // 从 RGBA 字节创建 new Cesium.Color(1.0, 0.5, 0.0, 1.0) // 从 RGBA 浮点数创建 这些常量和类会在后续文章中频繁出现现在不需要记住参数先混个眼熟。七、本篇总结这篇没有写什么代码但如果你认真读到这你已经拥有了一个查阅 CesiumJS 任何 API 都能快速定位的心智模型。简单回顾概念一句话说明Viewer大管家你唯一 new 的东西Scene舞台管所有渲染Globe地球的皮肤底图 地形Camera你的眼睛Entity高层 API简单但消耗大Primitive底层 API复杂但性能好DataSource数据加载通道最终变成 Entity渲染循环删掉不可见、选精度、加载瓦片、画出来下一篇文章我们将深入 Viewer 的每一个配置选项看看这个 大管家 到底有多少可以调的参数。课后动手打开上一篇建的 Cesium 项目在控制台输入viewer展开对象树对照本文第三节的层级图找到每一个属性在控制台输入viewer.scene.debugShowFramesPerSecond true观察左上角的 FPS分别用 Entity 和 Primitive 两种方式画一个红色的圆感受代码量的差异不会写没关系先试试在控制台输入viewer.scene.primitives和viewer.entities看看当前场景中分别有哪些内容 下一篇预告CesiumJS 从入门到精通③—— 万法归宗我们会把 Viewer 的几十个配置参数逐一拆解2D/3D 切换、底图替换、按需渲染、阴影开关、选中事件…… 敬请期待