经纬志
首页 / 实战手册 / three.js 渲染

两千万点点云,打开就卡死

先看结论

PLY 文件 1.8 GB,加载后浏览器内存涨到 3 GB,帧率 3 fps,动一下鼠标要等两秒。

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

现象

点云一次性全部载入,首屏白屏 40 秒后出现点阵。 帧率稳定在 3 fps,Chrome 任务管理器显示标签页内存 3.1 GB。 缩放时画面卡住,偶尔弹出 Array buffer allocation failed。

原因

两千万个点,每个点位置 12 字节、颜色 3 字节,仅顶点数据就 300 MB; 加上解析阶段的中间对象,很容易超过 2 GB。 THREE.Points 一次 draw call 绘制全部顶点,GPU 顶点处理阶段完全饱和。

如果把 PointsMaterial.size 设为固定像素并关闭 sizeAttenuation,近处点会大到糊满屏幕,overdraw 再叠加一层压力。 移动端 devicePixelRatio 为 3 时,填充率还要再乘九倍。

排查过程

  1. 先算清顶点数:geometry.attributes.position.count。
  2. 用 renderer.info 看 points 与 calls 的数量,确认是单次大调用还是多次调用。
  3. 把 renderer.setPixelRatio(1) 试一下,帧率若明显提升,瓶颈在填充率。
console.log(points.geometry.attributes.position.count);  // 19874321
console.log(THREE.WebGLRenderer.info ? '' : '');
console.log(renderer.info.render.points, renderer.info.render.calls);

解决方案

方案一:降低绘制成本。点大小固定为 2 像素,关闭尺寸衰减,像素比封顶 2:

var mat = new THREE.PointsMaterial({
  size: 2,
  sizeAttenuation: false,
  vertexColors: true,
  transparent: false,
  depthWrite: true
});

renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

方案二:按空间分块,只加载视野内的块。把点云切成 500 米的瓦片, 每块一个 Points 对象,配合 frustumCulled 自动裁剪:

var tiles = {};
function loadTile(key, positions, colors) {
  var geo = new THREE.BufferGeometry();
  geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));

  var pts = new THREE.Points(geo, mat);
  pts.frustumCulled = true;
  pts.computeBoundingSphere();
  scene.add(pts);
  tiles[key] = pts;
}

// 相机移动后只保留半径 2 公里内的块
function updateTiles(center) {
  Object.keys(tiles).forEach(function (k) {
    tiles[k].visible = tiles[k].position.distanceTo(center) < 2000;
  });
}

方案三:换用自带 LOD 的点云渲染方案。Potree 按八叉树分块, 每块有多级精度,千万级点云也能保持 30 fps 以上:

var potree = new Potree.loadPointCloud('/pointcloud/metadata.json', function (e) {
  scene.add(e.pointcloud);
  e.pointcloud.material.size = 1.5;
  e.pointcloud.material.pointSizeType = Potree.PointSizeType.ADAPTIVE;
});

验证

首屏内存在 800 MB 以内,加载时间降到 5 秒级。 在 1080p 分辨率下拖动视角,帧率保持在 30 fps 以上。 把相机移到视野外再移回,只有附近的块被绘制, renderer.info.render.points 数值随视野变化。

先降像素比再谈分块:很多“点云卡死”其实是高分屏填充率问题, setPixelRatio(Math.min(dpr, 1.5)) 一行就能让帧率翻倍, 代价只是轻微变糊,成本远低于重构数据。

引用本文

经纬志(2026)。《两千万点点云,打开就卡死》。https://www.jingweizhi.com/cookbook/three-pointcloud-lag.html(访问日期 2026-11-03)。