迪图
发布于 2026-10-20 / 29 阅读
0
0

Cesium 大场景性能优化:从参数调优到浏览器缓存的完整实践

Cesium 大场景性能优化:从参数调优到浏览器缓存的完整实践

让三维场景流畅稳定——面向数字孪生、无人机与 GIS 应用。这套优化清单在多个港口级实景三维项目里验证过。

优化的三条基本原则

在动手调参数之前,先建立判断标准。任何优化如果说不清它减少了什么开销、增加了什么成本,就不该做。

1. 减少渲染工作量 —— 更少的顶点、更简单的材质、更少的后处理

2. 降低资源体积 —— 压缩、纹理降采样、按需传输

3. 按需加载与批处理 —— 不该渲染的不渲染,不该单独渲染的不单独渲染

监控和度量贯穿始终。没有度量,优化就是盲改。

快速优化清单(按投入产出比排序)

措施

成本

收益

说明

requestRenderMode: true

极低

极高

消除无意义的逐帧渲染

scene3DOnly: true

极低

高

剔除 2D / Columbus 模式开销

关闭阴影

极低

高

阴影 pass 是双倍几何开销

3D Tiles 参数调优

低

高

见下节

Primitive 批渲染替代 Entity

中

极高

上万要素时是数量级差异

IndexedDB 缓存

中

高(二次访问)

分钟级 → 秒级

Draco / KTX2 压缩

中

中

影响首屏加载

交互节流

低

中

减少高频事件重绘

前三项通常五分钟做完,能解决大部分"卡顿"投诉。

一、按需渲染:收益最大的单点优化

const viewer = new Cesium.Viewer('container', {
  scene3DOnly: true,          // 剔除 2D / Columbus 模式相关开销
  shadows: false,             // 根据场景需要决定是否开阴影
  terrainProvider: Cesium.createWorldTerrain({ requestVertexNormals: true })
});

// 只有场景有变化时才渲染
viewer.scene.requestRenderMode = true;
viewer.scene.maximumRenderTimeChange = 0.0;

// 需要更新时手动触发
function trigger() { viewer.requestRender(); }

这是整个 Cesium 优化里性价比最高的一项。 Cesium 默认逐帧重绘,即使相机完全静止、没有任何数据变化。如果你有一个"加载完就不动"的展示大屏,开启 requestRenderMode 后 GPU 占用可以从 40% 降到接近 0。

配合 maximumRenderTimeChange = 0.0,只要有任何微小变化(鼠标移动、动画、图层切换)都会触发一次渲染,且不会漏掉。

注意:开启后,如果你有 CallbackProperty 或定时器驱动的动画,必须手动调用 viewer.requestRender(),否则动画会"卡住不动"。这是使用按需渲染最常见的副作用。

二、3D Tiles 调优:LOD 的三根旋钮

const tileset = await Cesium.Cesium3DTileset.fromUrl('https://host/tileset.json');
viewer.scene.primitives.add(tileset);

tileset.maximumScreenSpaceError = 2;    // 默认 16,调小更精细但更吃性能
tileset.maximumNumberOfLoadedTiles = 256; // 控制内存与并发上限
tileset.skipLevelOfDetail = true;       // 先加载低 LOD,加速进入可视状态
tileset.baseScreenSpaceError = 1024;
tileset.skipLevels = 1;
tileset.dynamicScreenSpaceError = true; // 随相机距离动态调整精度

maximumScreenSpaceError:控制细节换性能

数值越小越精细,也越吃性能。在某个港口实景三维项目中的实践是设为 2(默认 16),适当牺牲远处模型精度,帧率提升非常明显。

调整的一般区间:

  • 24–64:性能优先的视图

  • 8–16:用性能换更高的视觉保真度

  • 2:适合数据量大、追求流畅的场景

最优值取决于数据集规模、相机距离和目标硬件。

调试 LOD 细化

Cesium 提供明确的调试标志:

tileset.debugShowBoundingVolume = true;        // 每个可见瓦片的边界体积
tileset.debugShowContentBoundingVolume = true; // 内容边界体积

观察要点:放大时注意边界体积在什么时候细分。体积越小越密集 = 细化越早 = 视觉保真度越高但开销越大;体积越大越稀疏 = 细化越晚 = 更流畅但更粗糙。

3D Tiles 检查器

viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin);
if (tileset && viewer.cesium3DTilesInspector?.viewModel) {
  viewer.cesium3DTilesInspector.viewModel.tileset = tileset;
}

这是调优时最该常驻的工具。

三、Entity vs Primitive:数量级的差异

这是新手最容易犯的错误:用 Entity 渲染上万个要素。

Entity 是面向"少量、需交互、需属性面板"的对象。它内部维护场景图节点、有独立的样式对象和事件系统。渲染一万个 Entity 的开销,远大于渲染同等数量的几何。

正确做法是用 Primitive 批量渲染:

const instances = polygons.map(positions => new Cesium.GeometryInstance({
  geometry: new Cesium.PolygonGeometry({
    polygonHierarchy: new Cesium.PolygonHierarchy(positions)
  })
}));

const primitive = new Cesium.Primitive({
  geometryInstances: instances,
  appearance: new Cesium.PerInstanceColorAppearance({ flat: true }),
  asynchronous: true          // 异步构建,不阻塞 UI
});
viewer.scene.primitives.add(primitive);

经验阈值:

数据量

方案

几百 ~ 几千点

Entity + Billboard / Point 可以接受

几万 ~ 几十万

Primitive + BillboardCollection / PointPrimitiveCollection

百万级 +

转 3D Tiles(点云格式)

海量矢量线 / 面

转 3D Tiles,不要用 Entity

如果你正打算在 Cesium 里画一百万个点,问题不在 Cesium,在你的数据加载方式。

四、IndexedDB 前端缓存:二次访问的关键

在一个大范围港口场景项目里,首次加载三维瓦片和纹理需要分钟级。引入 IndexedDB 缓存后,二次访问降到秒级。

// 首次加载后持久化静态资源
async function cacheResources(tilesetUrl) {
  const db = await openDB('cesium-cache', 1);
  // 遍历已加载的瓦片,将 b3dm / glb / 纹理写入 IndexedDB
  // 下次访问优先从本地读取,完全避免重复网络请求与解析
}

为什么收益这么大:三维瓦片的解析(解包 b3dm、构建几何、上传 GPU)是纯 CPU 开销。即使数据已在 HTTP 缓存里,每次会话仍要重新解析一遍。IndexedDB 缓存的是已解析或可快速复用的结构,省掉的正是这块。

这个优化对"用户频繁访问"的业务场景(每天使用的运维平台)价值远大于一次性演示。

五、资源压缩:Draco 与 KTX2

编码

压缩对象

效果

Draco

几何(顶点位置、法线、UV)

通常 4:1 以上

KTX2 / Basis

纹理贴图

显存占用大幅下降,同时支持 GPU 压缩格式

// 在 tileset 转换阶段应用 Draco 压缩
// Cesium 自动识别 draco_compression 扩展

需要注意:Draco 会引入解压开销,如果你的瓶颈是 CPU 而不是带宽,需要实测。

六、交互节流

高频交互事件(鼠标移动、实时数据推送)如果不节流,会不断触发重绘和重新计算。

let pending = false;
viewer.canvas.addEventListener('mousemove', () => {
  if (pending) return;
  pending = true;
  requestAnimationFrame(() => {
    pending = false;
    handleMouseMove();
  });
});

对实时轨迹、告警点位这类高频更新数据,把更新合并到动画帧内,而不是每次推送都立即渲染。

七、常见故障排查

瓦片加载失败

按顺序检查:服务器配置、网络连接、CORS 响应头、瓦片 URL 是否可访问。

帧率骤降

用 Cesium Inspector 定位瓶颈:是瓦片数量过多、几何太复杂、还是纹理太大。不要凭感觉猜。

内存泄漏

用 Chrome DevTools 定位。要点:及时销毁不再需要的 Entity。Cesium 的 Entity 不会自动回收。

viewer.entities.remove(entity);   // 必须显式移除

数据格式兼容

确认服务器输出的格式与 Cesium 版本兼容,特别是 3D Tiles 1.0 与 1.1 的差异。

八、排查流程

遇到性能问题时,按这个顺序走:

1. 开 Cesium Inspector,看统计面板
   ↓
2. 帧率低?
   ├─ 检查瓦片数量是否失控 → 调 SSE / maximumNumberOfLoadedTiles
   ├─ 检查 Entity 数量 → 改 Primitive 批渲染
   └─ 检查是否为按需渲染 → 开 requestRenderMode
   ↓
3. 首屏慢?
   ├─ 网络瓶颈 → 压缩 + CDN + 缓存头
   └─ 解析瓶颈 → Draco + IndexedDB 缓存
   ↓
4. 特定操作卡顿?
   → 交互节流 / 计算移出主线程

先测量,再优化。 凭感觉调参数是这个领域最常见的时间浪费。

进阶方向

隐式瓦片(Implicit Tiling)

3D Tiles 1.1 的隐式瓦片用四叉树/八叉树描述层级,配合子树文件,避免了超大 tileset.json。对于超大规模数据集,这是必要的优化。

新格式:Gaussian Splatting

点云泼溅(Splat)在三维场景重建中表现突出。在 CesiumJS 中已有支持方案,可以加载上亿级的泼溅点 LOD 瓦片。但注意它的屏幕空间误差调优逻辑与传统 3D Tiles 不同:

  • 较高的 SSE 让粗糙瓦片保持更久可见 → 性能更好

  • 较低的 SSE 导致更早细化 → 视觉细节更多但开销更大

参考资料

  • CesiumJS 官方文档 — https://cesium.com/learn/cesiumjs/ref-doc/

  • 3D Tiles 官方规范 — https://github.com/CesiumGS/3d-tiles

  • Cesium Inspector 使用 — https://cesium.com/learn/cesiumjs/ref-doc/CesiumInspector.html

  • glTF 压缩规范(Draco / KTX2)— https://github.com/KhronosGroup/glTF


评论