场景里要放一万个一样的物体,最直接的写法是循环 new 一万个 Mesh,一个个 add 进场景。几十个没问题,上万之后帧率会掉下来一大截。
原因不在几何体:一万个立方体加起来也才 12 万个三角形,对 GPU 来说这点量很小。问题出在 DrawCall。
场景里要放一万个一样的物体,最直接的写法是循环 new 一万个 Mesh,一个个 add 进场景。几十个没问题,上万之后帧率会掉下来一大截。
原因不在几何体:一万个立方体加起来也才 12 万个三角形,对 GPU 来说这点量很小。问题出在 DrawCall。
Vue 解决 LLM 对话过长问题:使用 markstream
先看一张截图。

之前见过这个vue如何解决LLM对话过长问题,那条回复说的是答案:
LLM 聊天很容易变成长会话。节点数量会持续膨胀。虚拟滚动,只渲染当前屏幕看到的内容。其它只占位。
—— 版本答案是 使用 markstream,此题应 @simon_he1995
「虚拟滚动」这四个字在列表场景里是老生常谈,但把它搬到 LLM 聊天里,有一个前提条件会被打破。这篇就把这两句话中间的推理补上,再说怎么落地。
用到的组件是 markstream-vue,文档在 markstream.simonhe.me。示例项目是一个能跑起来的最小聊天 Demo:Vue 3 + Vite,消息列表交给 MarkstreamVirtualTimeline,正文交给 MarkdownRender。
背景: 我国正在发展低空经济,我们做的是飞行服务(飞服)项目,要把低空空域划成能计算、能查询、能下发的单元。选的是北斗网格:按经纬度和高度把地理空间编码成一个个三维盒子,一个编码对应一块确定的空间范围。在 Cesium 里加载 L5、L6、L7 三档网格,给一个多边形和一段高度范围,就能把落在里面的格子填出来,得到一块禁飞区或管控区。三档的水平格边长依次约 123.7、61.8、7.73 米:比 L5 粗的看不出区域形状,比 L7 细的在这个业务尺度上没有意义。
原因: 麻烦来自多出来的那个方向。二维地图只要横向铺满,三维还要按高度一层层堆;而且高度步长不是独立参数,它由水平步长推出来,水平细一倍、高度也跟着细一倍。L6 到 L7 每边 8 倍,三个方向乘起来就是 512 倍 —— 换一档不是画得密一点,是同一片空间里的格子数量直接乘 512。
压测示例用了六个真实的天津、北京管控区,它们在 L7 下的理论总量是 70,986,328 个格子;而视口配的活动预算上限是 180,000,也就是任何一帧最多允许约 0.25% 的格子存在。剩下那 99.75% 必须永远不落地 —— 因为瓶颈不在光栅化,在生成:这些格子还没开始画,光实例矩阵和颜色就是 7000 万 × 80 字节 = 5.68 GB。
LOD(Level of Detail,细节层次)决定网格画多细;视锥剔除判断哪些空间块可能出现在画面里;活动预算限制同时参与渲染的数据规模。 三者分别减少无法分辨的细节、视野外的工作,以及设备暂时承担不起的负载。
这套北斗网格的做法是:用相机到 Tile 中心的三维距离选层级,用区域 OBB 和 Tile 包围体做两级剔除,再按距离和预算安排加载;相机移动期间暂缓新增 L7,停下来约 120 毫秒后补细节。
代价是细节分阶段出现,Tile 级筛选会保留少量实际看不见的数据,而且视锥剔除不处理遮挡 —— 楼房和地形挡住的块仍在视锥内,仍然要生成。
这篇从一个常见问题展开:已经用了 GPU Instancing,为什么大规模网格还是会卡?
北斗网格性能优化(四):Web Worker 与 Transferable
Web Worker 是浏览器里独立于页面主线程的 JavaScript 执行环境,主线程通过消息派任务、收结果。Worker 不能访问 DOM,也拿不到 Cesium 的 WebGL 上下文 —— 它只能算,不能画。
Transferable 用来在线程之间转移 ArrayBuffer 的所有权:缓冲区不复制,发送方失去访问权,接收方直接拿到这块内存。这也是唯一一个能把大数组跨线程搬过去、又不付出一次完整拷贝的办法。
这套北斗网格的做法是:一个 Worker,主线程前面配一个按距离排序的队列,同一时刻只跑一个任务;Worker 把实例矩阵、颜色、网格索引、占用标记四组类型化数组用 Transferable 交回主线程。
它把生成搬出主线程,不减少计算量。GPU 上传仍然在主线程完成,Transferable 只免掉线程之间的复制,不会把数据直接变成显存。
空间分块把一个大区域划成若干可以独立生成、绘制、隐藏和回收的空间块。 切得好,一块就是一次 Worker 任务、一次实例化绘制、一个缓存条目;切得不好,包围体太粗、单次任务太重,前面所有优化都会被抵消。
L7 微块是 L7 层级专用的水平分组,高度切片是在它之上再按采样层切的一刀。 两者合起来把一根根竖直的格子柱拆成可以单独调度的小块。
这套北斗网格的做法是:一个水平微块每边跨 2 个 L6 步长,也就是 16×16 = 256 根格子柱;每根柱子按最多 32 个采样层分成一片,满片 8192 个实例。 微块的列号行号取自全局经纬度,不取自区域边界,所以相邻的两个基础块看到同一片空间会算出同一个编号。
这篇只讲 L7。L6 的切法完全不同 —— 它按区域预估工作量反推块大小,放在下一篇和缓存一起说。
在自动驾驶、点云可视化、目标检测等场景中,经常需要把算法输出的 3D 检测框叠加到点云上。
在数字孪生、WebGIS、激光雷达、自动驾驶、测绘等场景中,点云数据很容易达到千万、上亿甚至更大的规模。
把经纬度转到墨卡托,再映射到 Three.js 世界坐标,才能把行政区轮廓挤出到地图上。
Cesium 3D Tiles 缓存技术:使用 IndexedDB 和 localforage 提升加载性能
在 Cesium 开发过程中,3D Tiles 模型加载是一个常见的性能瓶颈。尤其是当 3D 模型较大或网络条件不佳时,重复加载相同的模型会浪费带宽并降低用户体验。本文介绍如何利用浏览器的 IndexedDB 和 localforage 库来实现 Cesium 3D Tiles 的本地缓存,显著提升加载性能和用户体验。
我们将通过以下技术和方法实现 3D Tiles 缓存: