Cesium 3D Tiles 即将原生支持矢量数据:点、线、面走进真实三维空间

1 阅读9分钟

一、一个拖了很久的需求

在 Cesium 社区里,"能不能用 3D Tiles 来管矢量数据" 几乎是被提得最多的功能请求之一。过去大家处理点、线、面数据,要么直接丢 GeoJSON 给前端全量渲染(数据量一大就卡),要么走 Mapbox Vector Tiles(MVT)这类 2D 瓦片方案(到了三维场景里各种别扭)。

现在 Cesium 团队正式给出了答案:3D Tiles 2.0 将原生支持三维矢量点、线、面,并且同步在 CesiumJS 和 Cesium for Unreal 里增加 MVT 的运行时支持。这篇文章梳理一下他们的设计思路和技术路线。

二、为什么现有的矢量方案不够用

在动手设计之前,团队先做了一轮全面调研,覆盖了 MVT、MapLibre Tiles(MLT)、GeoJSON 等主流格式,以及各行各业的实际工作流。结论很清晰:当前生态严重偏向 2D。

2D 矢量瓦片针对平面地图做了大量优化,但一旦进入真实三维空间就力不从心。比如这些场景:

  • 城市级道路网:需要厘米级精度,并且要贴合地形起伏,不是简单贴在平面上;
  • 输电塔 / 通信塔:塔体本身是三维结构,矢量标注要挂在精确的三维位置上;
  • AI 提取的全球 POI:计算机视觉从影像里识别出的兴趣点,带有真实高程,动辄几十万上百万个;
  • 建筑工程设计:BIM 模型里的设计线、标注、楼层标签,本质都是三维矢量;
  • 桥梁裂缝检测:无人机摄影测量 + 自动识别出来的裂缝,既分布在桥面也延伸到侧面和底部,2D 矢量根本表达不了。

这些场景的共同诉求是:实时 LOD 流式加载 + 高精度三维渲染 + 保留语义属性可查询可分析。现有 2D 方案满足不了。

不过团队也承认,大量 2D / 2.5D 的场景(比如普通行政区边界、底图标注)用 GeoJSON 和 MVT 已经足够,用户只是希望这些数据能和 3D Tiles(比如 Google photorealistic 3D Tiles、自己的实景三维模型)叠加显示。

所以最终采取了双轨策略:

  1. 在 3D Tiles 2.0 规范中扩展原生矢量支持,基于 glTF 编码,面向真正的三维场景;
  2. 在 CesiumJS 和 Cesium for Unreal 中增加 MVT 运行时支持,和已有的 GeoJSON 方案一起,让用户已有的 2D 矢量瓦片能直接接入 Cesium 生态。

两条路共享同一套渲染管线,性能优化可以互相受益。

三、用户驱动的设计目标

团队和多个行业的用户深入沟通后,提炼出三组核心需求:

基础元素能力

  • 支持点、线、面三种基本几何;
  • 原生支持三维坐标,同时优雅兼容 2D 和 2.5D;
  • 每个元素的属性可查询、可按属性样式化。

灵活的切分策略

  • 支持实时流式加载和多层次细节(LOD);
  • 坐标不绑定地球 —— 火星、月球等非地球坐标系也要能用;
  • 切分方案可插拔:四叉树、八叉树、k-d 树、离散全球网格系统(DGGS)都要支持。原因是 Web 墨卡托投影在极地区域和垂直结构(建筑、大坝)上会严重变形,不能一刀切。

高保真实时渲染

  • 采用运行时就绪的二进制格式和 GPU 友好的压缩,反序列化要快;
  • 精度达到厘米甚至毫米级 —— 矢量数据经常要和点云、实景网格等非矢量数据源对齐,精度差一点就对不上。

四、规范层面:一个 3D Tiles 扩展 + 两个 glTF 扩展

3D Tiles 2.0 的矢量方案复用了现有网格和点云已经验证过的空间索引、属性编码和 LOD 组织方式,矢量要素被编码成 glTF 资源,可选 Meshopt + Gzip 压缩。

具体涉及三个扩展:

表格

扩展归属作用
3DTILES_content_gltf_vector3D Tiles 扩展标记瓦片内容为 "矢量数据",告诉运行时如何解析。先在 3D Tiles 1.1 中落地,2.0 会有对等扩展。
KHR_mesh_primitive_restartglTF 扩展让 LINE_STRIP 和 LINE_LOOP 拓扑的编码更高效,线和面数据都依赖它。
EXT_mesh_polygonglTF 扩展向后兼容地表达多边形拓扑,同时附带预计算好的三角化数据,运行时可以直接送 GPU,不用现场三角化。

此外,EXT_mesh_features 和 EXT_structural_metadata 这两个已有的 glTF 扩展会继续承担 Feature ID 和属性元数据的编码,矢量瓦片和 3D 模型在属性层面走同一套机制。

五、落地路线图:三部分构成完整管线

整个矢量内容管线拆成三块:

1. 切分管线从矢量源数据生成 3D Tiles。首发支持 GeoJSON 输入,和现有管线一样,既可以通过 Cesium ion 云服务使用,也支持私有化部署。GeoPackage、Shapefile 等格式会根据社区反馈陆续加入。

2. glTF 编码层用上面提到的扩展做高效编码、流式传输和渲染。

3. 运行时支持CesiumJS、Cesium Native、Cesium for Unreal 首发支持,Cesium for Unity 等运行时视社区反馈推进。

第一阶段的目标是跑通端到端全链路:GeoJSON 输入 → ion 商业切分 / 托管 / 流式服务 → CesiumJS 和 Cesium for Unreal 渲染。同时 CesiumJS 已经开始支持 MVT,复用 3D Tiles 的渲染管线。

后续几个月的计划包括:扩展样式能力、支持矢量与 3D Tiles / 地形的贴合(clamping)、为所有运行时增加更多源格式、持续的性能和稳定性优化。

六、几个值得关注的原型验证

团队已经做了一些参考实现来验证方案:

  • Robert Street 大桥裂缝:无人机摄影测量 + 自动裂缝检测,绿色线同时标记桥面和桥下的裂缝,只有 3D 原生方案才能完整表达;
  • Coffs Harbour 高速公路设计:Bentley 产出的矢量瓦片,分了 3 级 LOD,几何带真实高程,道路结构位于真实三维空间中;
  • 纽约市建筑轮廓:13 级 LOD,运行时把多边形样式化为蓝色虚线描边;
  • Blyncsy 的 30 万 POI:计算机视觉检测的点要素,全球尺度流畅流式加载。

这些原型的目的不是炫技,而是作为种子实现,邀请社区来测试和反馈,让规范和路线图真正对齐用户的实际场景。

七、写在最后

3D Tiles 从诞生起就走 "规范 + 参考实现" 双轮驱动的路线,矢量支持也不例外。目前规范和实现都还在早期阶段,团队明确表示需要社区的输入来固化规范、验证实现 —— 你的使用场景、遇到的问题、对格式和 API 的期望,都可以到 Cesium 社区论坛反馈。

一、环境准备

所有示例基于 CesiumJS 1.x(CDN 引入),无需构建工具,保存为 .html 直接用浏览器打开即可。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>Cesium 矢量数据实战</title>
  <script src="https://cesium.com/downloads/cesiumjs/releases/1.120/Build/Cesium/Cesium.js"></script>
  <link href="https://cesium.com/downloads/cesiumjs/releases/1.120/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
  <style>
    html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; }
  </style>
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>
    // 如需 Cesium ion 资源,替换为你自己的 token
    Cesium.Ion.defaultAccessToken = '你的_ION_TOKEN';

    const viewer = new Cesium.Viewer('cesiumContainer', {
      terrain: Cesium.Terrain.fromWorldTerrain(), // 启用全球地形,后面贴合用
      animation: false,
      timeline: false,
      baseLayerPicker: false,
      geocoder: false,
      homeButton: false,
      sceneModePicker: false,
      navigationHelpButton: false,
      fullscreenButton: false
    });

    // ===== 以下各节代码依次替换到这里运行 =====
  </script>
</body>
</html>

二、加载 GeoJSON 并按属性动态样式化

GeoJsonDataSource 是 CesiumJS 原生支持的矢量入口,适合万级要素以内的场景。关键点是用 CallbackProperty 实现运行时按属性动态着色,而不是加载时写死。

javascript

运行

/**
 * 加载 GeoJSON 并按属性值动态样式化
 * @param {Cesium.Viewer} viewer
 * @param {string} url - GeoJSON 文件地址(支持本地相对路径或跨域 URL)
 */
async function loadStyledGeoJSON(viewer, url) {
  // 1. 加载数据源,stroke-width 单位是像素,clampToGround 决定是否贴地
  const dataSource = await Cesium.GeoJsonDataSource.load(url, {
    stroke: Cesium.Color.WHITE,
    fill: Cesium.Color.RED.withAlpha(0.4),
    strokeWidth: 2,
    clampToGround: true // 矢量贴合地形,三维场景必开
  });

  viewer.dataSources.add(dataSource);

  // 2. 遍历实体,按属性动态决定颜色和高度
  const entities = dataSource.entities.values;
  for (const entity of entities) {
    const props = entity.properties;
    if (!props) continue;

    // 假设属性中有 population 字段,值越大颜色越红、高度越高
    const population = props.population ? props.population.getValue() : 0;

    // 归一化到 0~1,映射颜色
    const t = Math.min(population / 1000000, 1);
    const color = Cesium.Color.lerp(
      Cesium.Color.YELLOW,
      Cesium.Color.RED,
      t,
      new Cesium.Color()
    );

    if (entity.polygon) {
      entity.polygon.material = color.withAlpha(0.6);
      entity.polygon.outline = true;
      entity.polygon.outlineColor = Cesium.Color.BLACK;
      // extrudedHeight 让多边形"立"起来,形成柱状图效果
      entity.polygon.extrudedHeight = population / 1000; // 单位:米
    }

    if (entity.polyline) {
      entity.polyline.width = 3;
      entity.polyline.material = color;
      entity.polyline.clampToGround = true;
    }

    if (entity.point) {
      entity.point.pixelSize = 8 + t * 8;
      entity.point.color = color;
      entity.point.outlineColor = Cesium.Color.WHITE;
      entity.point.outlineWidth = 1;
    }
  }

  // 3. 自动飞到数据范围
  viewer.flyTo(dataSource);
  return dataSource;
}

// 调用示例(用 Cesium ion 上的样例数据,或替换为你自己的 GeoJSON)
loadStyledGeoJSON(viewer, 'https://your-server.com/data/cities.geojson');

性能提示:GeoJsonDataSource 内部为每个要素创建一个 Entity,单要素有独立的属性和事件回调。要素超过 2~3 万后帧率会明显下降,此时应切换到下面的 Primitive 批量方案。


三、高性能批量渲染:10 万 + 点用 Primitive + GeometryInstance

当数据量达到十万、百万级(比如 AI 提取的全球 POI、传感器点位),Entity 模式扛不住。正确做法是用 Primitive + GeometryInstance,把所有点合并成一次 draw call。

javascript

运行

/**
 * 批量渲染 10 万+ 点,使用 Primitive + PointGeometry 合并 draw call
 * @param {Cesium.Viewer} viewer
 * @param {Array<{lon:number, lat:number, height?:number, value:number}>} points
 */
function renderMassivePoints(viewer, points) {
  const instances = [];

  for (let i = 0; i < points.length; i++) {
    const p = points[i];

    // 按 value 映射颜色,提前算好,避免运行时逐帧计算
    const t = Math.min(p.value / 100, 1);
    const color = Cesium.Color.lerp(
      Cesium.Color.CYAN,
      Cesium.Color.MAGENTA,
      t,
      new Cesium.Color()
    );

    instances.push(new Cesium.GeometryInstance({
      geometry: new Cesium.PointGeometry({
        position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height || 0),
        color: color,
        pixelSize: 6 + t * 6 // 像素大小,随 value 变化
      }),
      // 每个 instance 可以带自定义属性,供拾取时用
      attributes: {
        index: new Cesium.ShowGeometryInstanceAttribute(i)
      },
      id: `point_${i}` // 点击拾取时返回这个 id
    }));
  }

  // 合并成一个 Primitive,一次 draw call 渲染全部
  const primitive = new Cesium.Primitive({
    geometryInstances: instances,
    appearance: new Cesium.PerInstanceColorAppearance({
      flat: true,
      translucent: false
    }),
    asynchronous: false, // 同步创建,大数据量时建议改 true 避免阻塞主线程
    releaseGeometryInstances: true // 上传 GPU 后释放 CPU 内存
  });

  viewer.scene.primitives.add(primitive);

  // 点击拾取:scene.pick 返回的是 GeometryInstance 的 id
  const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  handler.setInputAction((click) => {
    const picked = viewer.scene.pick(click.position);
    if (picked && picked.id) {
      console.log('拾取到点:', picked.id, '原始数据:', points[parseInt(picked.id.split('_')[1])]);
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

  return primitive;
}

// ===== 生成测试数据:北京附近 10 万个随机点 =====
const testPoints = [];
for (let i = 0; i < 100000; i++) {
  testPoints.push({
    lon: 116.0 + Math.random() * 1.0,
    lat: 39.5 + Math.random() * 1.0,
    value: Math.random() * 100
  });
}
renderMassivePoints(viewer, testPoints);
viewer.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(116.5, 39.8, 50000)
});

关键优化点:

  • PointGeometry 比 PointPrimitive 更适合超大规模静态点,因为它走合并 draw call;
  • releaseGeometryInstances: true 在 GPU 上传完成后释放 CPU 端几何数据,内存占用显著降低;
  • 颜色在构建时一次性算好,不要用 CallbackProperty,否则每帧回调十万次;
  • 如果点需要动态更新位置,改用 PointPrimitiveCollection,它支持逐点更新但 draw call 也只有一次。

四、MVT(Mapbox Vector Tiles)接入方案

CesiumJS 目前(1.x 稳定版)没有原生 MVT 矢量图层支持,但 3D Tiles 团队正在开发中。当前有两条可行路径:

方案 A:社区插件 MvtImageryProvider(将 MVT 作为影像图层栅格化渲染)

适合只需要显示、不需要拾取和三维拉伸的场景。

javascript

运行

/**
 * 通过 MvtImageryProvider 加载 MVT 瓦片(栅格化显示)
 * 依赖:https://github.com/xtkhtx/cesium-mvtimageryprovider 或同类社区实现
 * 这里展示核心调用逻辑,实际使用时需引入插件 JS
 */
function loadMVTAsImagery(viewer) {
  // 社区插件通常扩展 Cesium.ImageryProvider 接口
  const mvtProvider = new Cesium.MvtImageryProvider({
    url: 'https://your-tile-server.com/tiles/{z}/{x}/{y}.pbf',
    maximumLevel: 16,
    minimumLevel: 0,
    // 样式配置:按图层名和属性设置颜色、宽度
    layers: {
      roads: {
        lineColor: '#FFA500',
        lineWidth: 2,
        filter: (feature) => feature.properties.type === 'motorway'
      },
      buildings: {
        fillColor: 'rgba(100, 149, 237, 0.6)',
        lineColor: '#4682B4',
        lineWidth: 1
      }
    }
  });

  viewer.imageryLayers.addImageryProvider(mvtProvider);
}

局限:这种方式把矢量栅格化成图片贴在地球表面,无法拾取属性、无法做三维拉伸、无法贴地形。适合作为底图叠加。

方案 B:前端请求 MVT → 解析为 GeoJSON → 用 GeoJsonDataSource 加载

适合小范围、需要交互的场景。用 @mapbox/vector-tile + pbf 解析。

javascript

运行

// 需要 npm 安装:@mapbox/vector-tile pbf
// 浏览器端用打包工具引入,或用 CDN 上的 UMD 版本
import { VectorTile } from '@mapbox/vector-tile';
import Protobuf from 'pbf';

/**
 * 请求单个 MVT 瓦片并解析为 GeoJSON FeatureCollection
 * @param {string} tileUrl - 例如 https://server/tiles/12/3423/1567.pbf
 * @returns {Promise<GeoJSON.FeatureCollection>}
 */
async function fetchMvtTileAsGeoJSON(tileUrl) {
  const response = await fetch(tileUrl);
  const buffer = await response.arrayBuffer();
  const tile = new VectorTile(new Protobuf(buffer));

  const features = [];
  for (const layerName in tile.layers) {
    const layer = tile.layers[layerName];
    for (let i = 0; i < layer.length; i++) {
      // toGeoJSON 会把瓦片坐标转成经纬度(需要传入瓦片的 z/x/y)
      const feature = layer.feature(i).toGeoJSON(
        parseInt(tileUrl.match(//(\d+)/\d+/\d+.pbf/)[1]), // z
        parseInt(tileUrl.match(//\d+/(\d+)/\d+.pbf/)[1]), // x
        parseInt(tileUrl.match(//\d+/\d+/(\d+).pbf/)[1])  // y
      );
      features.push(feature);
    }
  }

  return { type: 'FeatureCollection', features };
}

// 加载单个瓦片并渲染
async function loadSingleMvtTile(viewer, tileUrl) {
  const geojson = await fetchMvtTileAsGeoJSON(tileUrl);
  const dataSource = await Cesium.GeoJsonDataSource.load(geojson, {
    clampToGround: true,
    stroke: Cesium.Color.ORANGE,
    strokeWidth: 2,
    fill: Cesium.Color.ORANGE.withAlpha(0.3)
  });
  viewer.dataSources.add(dataSource);
  viewer.flyTo(dataSource);
}

局限:手动管理瓦片请求、缓存、LOD 很麻烦,只适合演示或小范围。生产环境建议等 CesiumJS 原生 MVT 支持落地。

即将到来的原生 API(预览,非最终)

根据 Cesium 团队的路线图,未来 MVT 将复用 3D Tiles 的渲染管线,API 形态可能类似:

javascript

运行

// ⚠️ 以下为基于路线图的推测性 API,非当前可用
const vectorTileset = await Cesium.VectorTileset.fromUrl({
  url: 'https://server/tiles/style.json', // TileJSON 或 style.json
  style: {
    layers: [{
      id: 'roads',
      source: 'openmaptiles',
      'source-layer': 'transportation',
      type: 'line',
      paint: { 'line-color': '#ffa500', 'line-width': 2 }
    }]
  },
  clampToGround: true
});
viewer.scene.primitives.add(vectorTileset);

五、矢量贴合地形:clampToGround 与高度偏移

三维场景中矢量不贴地形会 "飘" 在空中。Cesium 提供了多种贴合方式,适用场景不同:

javascript

运行

/**
 * 三种矢量贴地方式对比
 */
function vectorClampingDemo(viewer) {
  const positions = Cesium.Cartesian3.fromDegreesArray([
    116.0, 39.5,
    116.5, 39.8,
    117.0, 39.6
  ]);

  // 方式 1:Entity polyline + clampToGround(最简单,适合少量线)
  viewer.entities.add({
    name: '贴地线 - Entity',
    polyline: {
      positions: positions,
      width: 5,
      material: Cesium.Color.RED,
      clampToGround: true, // 关键:自动采样地形高度
      classificationType: Cesium.ClassificationType.BOTH // 同时贴地形和3D Tiles
    }
  });

  // 方式 2:GroundPrimitive(高性能,适合大量多边形/线贴地)
  const groundPolygon = new Cesium.GroundPrimitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: new Cesium.PolygonHierarchy(positions),
        vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
      }),
      attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(
          Cesium.Color.BLUE.withAlpha(0.5)
        )
      }
    }),
    classificationType: Cesium.ClassificationType.TERRAIN // 只贴地形
  });
  viewer.scene.primitives.add(groundPolygon);

  // 方式 3:Corridor 走廊(带宽度的贴地线,适合道路)
  viewer.entities.add({
    name: '贴地走廊 - Corridor',
    corridor: {
      positions: positions,
      width: 100, // 单位:米
      material: Cesium.Color.YELLOW.withAlpha(0.6),
      height: 0,
      extrudedHeight: 5 // 向上挤出 5 米,形成"路沿"效果
    }
  });
}

vectorClampingDemo(viewer);

选型建议:

表格

方式适用场景性能拾取
Entity + clampToGround少量线 / 面,需要动态更新一般支持
GroundPrimitive大量静态多边形贴地好支持
Corridor道路、河流等带宽度的线好支持

六、手动构建三维矢量:带真实高程的线

3D Tiles 2.0 矢量的核心卖点之一是原生三维坐标。在当前 API 下,我们可以手动构建带高程的线来模拟这一能力 —— 比如桥梁、隧道、管线。

javascript

运行

/**
 * 构建一条带真实高程的三维折线(模拟桥梁/管线)
 * 并添加"垂直投影到地面"的辅助线,增强空间感知
 */
function build3DVectorLine(viewer) {
  // 一条从山脚到山顶的路径,每个点带真实高程
  const path = [
    { lon: 116.20, lat: 39.90, height: 50 },    // 起点:海拔50米
    { lon: 116.25, lat: 39.92, height: 200 },
    { lon: 116.30, lat: 39.94, height: 500 },
    { lon: 116.35, lat: 39.96, height: 1200 },   // 高点
    { lon: 116.40, lat: 39.98, height: 800 },
    { lon: 116.45, lat: 40.00, height: 300 }     // 终点
  ];

  // 转为 Cartesian3(带高度)
  const positions3D = path.map(p =>
    Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height)
  );

  // 主线:三维空间中的线,不贴地
  viewer.entities.add({
    name: '三维管线',
    polyline: {
      positions: positions3D,
      width: 4,
      material: new Cesium.PolylineGlowMaterialProperty({
        glowPower: 0.25,
        color: Cesium.Color.CYAN
      }),
      clampToGround: false // 关键:关闭贴地,使用自身高程
    }
  });

  // 每个点的位置标记 + 垂直投影线
  for (let i = 0; i < path.length; i++) {
    const p = path[i];

    // 节点标记
    viewer.entities.add({
      position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height),
      point: {
        pixelSize: 10,
        color: Cesium.Color.CYAN,
        outlineColor: Cesium.Color.WHITE,
        outlineWidth: 2,
        disableDepthTestDistance: Number.POSITIVE_INFINITY // 始终可见,不被遮挡
      },
      label: {
        text: `P${i} ${p.height}m`,
        font: '12px sans-serif',
        fillColor: Cesium.Color.WHITE,
        outlineColor: Cesium.Color.BLACK,
        outlineWidth: 2,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        pixelOffset: new Cesium.Cartesian2(0, -16),
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      }
    });

    // 垂直投影线:从点的位置垂直到地面(height=0)
    viewer.entities.add({
      polyline: {
        positions: [
          Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height),
          Cesium.Cartesian3.fromDegrees(p.lon, p.lat, 0)
        ],
        width: 1,
        material: Cesium.Color.CYAN.withAlpha(0.3),
        clampToGround: false
      }
    });
  }

  // 飞到合适视角
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(116.32, 39.95, 8000),
    orientation: {
      pitch: Cesium.Math.toRadians(-30)
    }
  });
}

build3DVectorLine(viewer);

这段代码展示的正是 3D Tiles 2.0 矢量要标准化的能力:矢量要素自带三维坐标,在真实空间中渲染,而不是被拍平在地球表面。桥梁的裂缝、建筑的标注线、地下管线,都需要这种能力。


七、性能优化清单

最后汇总一份矢量渲染性能优化 checklist,代码中已体现的不再展开:

  1. 数据量分级:<1 万用 Entity / GeoJsonDataSource;1~10 万考虑 Primitive 合并;> 10 万必须走瓦片化(3D Tiles 或 MVT)。
  2. 颜色预计算:静态数据的颜色、大小在构建时算好,不要用 CallbackProperty 逐帧计算。
  3. 释放 CPU 几何:Primitive 设置 releaseGeometryInstances: true。
  4. 贴地开销:clampToGround 需要每帧采样地形深度,线越多越慢;静态数据优先用 GroundPrimitive。
  5. 关闭深度测试的代价:disableDepthTestDistance 会让物体永远画在最上层,大量使用时排序开销增大。
  6. Label / Billboard 合批:Cesium 内部会自动合批,但如果每个 label 用了不同的 font /outline,合批会失效,尽量统一样式。
  7. 瓦片化是终极方案:无论当前用什么技巧,超大规模矢量的最终归宿都是 3D Tiles 2.0 原生矢量瓦片 —— 这也正是 Cesium 团队在推的方向。