共计 1899 个字符,预计需要花费 5 分钟才能阅读完成。
问题场景:当 Cesium 遇上十万级点云
上周处理一个智慧城市项目时,首次加载包含 12 万个 POI 点的数据集后,页面直接卡顿到 3 -4FPS。Chrome 性能分析显示:

- 内存占用从 200MB 飙升至 1.2GB
- 主线程被
EntityCluster的暴力计算完全阻塞 - 每次相机移动触发全量重聚类
Cesium 内置的 EntityCluster 采用简单网格空间划分,存在两个致命缺陷:
- 聚类计算完全在 CPU 端进行
- 未考虑 LOD(Level of Detail)级别和视锥体裁剪
技术方案选型对比
方案 A:Cesium.Entity 聚合
- 优点:原生 API 无需额外依赖
- 缺点:
- 仍然基于 CPU 计算
- 聚合策略单一(仅支持按距离阈值)
- 实测 10 万点性能提升仅 15%
方案 B:SuperCluster 第三方库
import SuperCluster from 'supercluster';
const index = new SuperCluster({radius: 60, maxZoom: 18});
index.load(points); // 需要预先转换 GeoJSON 格式
- 优点:
- 支持 KD-Tree 空间索引
- 动态聚合层级控制
- 缺点:
- 数据转换开销大(Cesium 坐标↔GeoJSON)
- 与 WebGL 渲染管线割裂
方案 C:自定义 WebGL 着色器(最终选择)
通过四项关键技术突破:
- GPU 端并行计算聚类
- 实例化渲染(Instanced Rendering)
- 视锥体 + 距离双重剔除
- 动态 LOD 控制
核心实现细节
1. 构建 KD-Tree 空间索引
// KD-Tree 节点结构
struct KDNode {
vec2 position; // 经纬度(简化示例)int leftChild;
int rightChild;
int pointCount;
};
构建过程分三步:
- 预处理:将 WGS84 坐标转换为 Web 墨卡托投影
- 递归分割:选择方差最大的维度进行划分
- 内存优化:使用 Float32Array 存储而非对象
2. GLSL 实例化着色器
// 顶点着色器关键代码
uniform sampler2D positionTexture; // 点位数据纹理
uniform float clusterRadius; // 像素单位聚合半径
void main() {
// 从纹理读取坐标(替代 attribute 传值)vec2 pos = texelFetch(positionTexture, ivec2(gl_InstanceID, 0), 0).xy;
// 屏幕空间距离计算
vec4 clipPos = czm_modelViewProjection * vec4(pos, 0.0, 1.0);
vec2 screenPos = (clipPos.xy / clipPos.w) * 0.5 + 0.5;
// 动态聚合半径(随视角高度变化)float dynamicRadius = clusterRadius / czm_metersPerPixel;
// 聚合逻辑...
}
3. 动态 LOD 控制算法
function updateLOD() {
const height = viewer.camera.positionCartographic.height;
// 高度 >10km 时显示聚合结果,否则显示原始点
clusterMaterial.uniforms.lodThreshold = height > 10000 ? 1.0 : 0.0;
}
性能对比数据
| 指标 | 原始方案 | 优化方案 |
|---|---|---|
| 初始加载时间 | 4.2s | 0.8s |
| 平均 FPS | 7 | 55 |
| GPU 内存占用 | 1.1GB | 320MB |
| CPU 计算耗时 | 420ms | <1ms |
WebGL Stats 显示:
- Draw Calls 从 12 万次降至 200 次以内
- 着色器执行时间占比提升至 85%
生产环境注意事项
移动端精度问题
- 使用
highp修饰符避免精度丢失 - 对聚合结果做屏幕坐标四舍五入
瓦片边界裂缝
// 在片段着色器中修正
if (distance(v_pos, tileBorder) < 0.001) {discard;}
内存泄漏检测
- 使用
performance.memory监控 JS 堆 - WebGL 资源释放三部曲:
texture && gl.deleteTexture(texture); program && gl.deleteProgram(program); buffer && gl.deleteBuffer(buffer);
开放性问题思考
- 精度与性能的权衡:
- 近景使用精确的逐点碰撞检测
-
远景改用概率抽样的近似计算
-
WebGPU 迁移可能:
- 计算着色器可替代现有 CPU 预处理
- 原子计数器实现更精确的 GPU 端聚合
这个方案在实际项目中已稳定运行 6 个月,日均加载数据量超过 80 万点。最大的收获是:在 WebGL 中,把计算推到 GPU 永远比优化 CPU 算法来得直接。下一步计划尝试用 SIMD 指令优化 KD-Tree 构建阶段。
正文完
发表至: 未分类
近两天内
