经纬志
首页 / 实战手册 / 3D 与 Cesium

地形瓦片加载慢,首屏转了十几秒

先看结论

影像早就铺满了,地形却一直有锯齿状的空洞,进度条停在一半,请求队列常年两百多条。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

初始化后 globe.tilesLoaded 持续为 false 超过 12 秒。 网络面板里 terrain/... 请求大量处于 pending, RequestScheduler 的队列长度可以这样读:

console.log(Cesium.RequestScheduler.statistics);
// { numberOfAttemptedRequests: 412,
//   numberOfActiveRequests: 6,
//   numberOfCancelledRequests: 0,
//   numberOfPendingRequests: 231 }

活跃请求只有 6,待处理却有 231,说明瓶颈在并发上限,而不是带宽。

原因

配置影响
requestWaterMask: true每个地形瓦片额外带一张水面掩膜,请求数翻倍
requestVertexNormals: true额外拉取法线数据,瓦片体积增加约 30%
maximumScreenSpaceError 默认 2细分层级过高,远处也加载高精度瓦片
HTTP/1.1同域最多 6 个并发连接
无强缓存二次进入仍全部重新请求

排查过程

  1. 读 RequestScheduler.statistics,区分是并发受限还是服务端响应慢。
  2. 看单个地形瓦片的响应时间与体积,正常应在 10–30 ms、几十 KB。
  3. 对比关闭 requestWaterMask 前后的请求总量。
  4. 检查响应的 Cache-Control,没有 max-age 就会反复回源。

解决方案

只保留业务真正需要的能力。内陆项目通常不需要水面掩膜与顶点法线:

var terrain = await Cesium.CesiumTerrainProvider.fromUrl(
  'https://terrain.example.cn/tiles/',
  {
    requestWaterMask: false,
    requestVertexNormals: false
  }
);
viewer.terrainProvider = terrain;

把屏幕空间误差放宽。默认 2 对大多数城市展示过细,4 在视觉上几乎看不出差别:

viewer.scene.globe.maximumScreenSpaceError = 4;
viewer.scene.globe.tileCacheSize = 200;        // 默认 100,来回拖动时减少重复请求

服务端开 HTTP/2 与长缓存。地形瓦片内容不变,可以直接给一年:

location /tiles/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Access-Control-Allow-Origin *;
    gzip on;
    gzip_types application/octet-stream application/json;
}

首屏只需要一个粗精度预览时,先用低层级瓦片占位,等相机稳定后再加载精细层级:

viewer.scene.globe.maximumScreenSpaceError = 8;   // 初始粗
setTimeout(function () {
  viewer.scene.globe.maximumScreenSpaceError = 3;
}, 2500);

验证

重新加载后 numberOfPendingRequests 应从 231 降到 60 以内, tilesLoaded 在 3 秒内变为 true。 关闭网络限速后,第一次与第二次进入的瓦片请求数应有明显差异,说明缓存生效。

先分清瓶颈:如果活跃请求数远低于 6,且单瓦片响应超过 200 ms, 问题在服务端或网络,调前端参数效果有限。先测一个瓦片的 TTFB,再决定改哪一侧。

引用本文

经纬志(2026)。《地形瓦片加载慢,首屏转了十几秒》。https://www.jingweizhi.com/cookbook/cesium-terrain-slow.html(访问日期 2026-11-03)。