把渲染器从 WebGL 换成 WebGPU,不代表同一个大场景会自动变快。WebGPU 提供了更明确的资源管理、更现代的渲染与计算接口,但应用如何组织对象、材质、缓冲区和每帧更新,仍然决定最终性能。
Three.js 官方 issue 中就有一个很直观的例子:场景包含大量未批处理 Mesh 时,WebGPURenderer 的表现可能低于 WebGLRenderer。维护者的讨论也把问题指向 per-object uniform/UBO 更新和大量 render item 带来的开销,而不是简单归因于浏览器没有开启 GPU。参考:The performance of the WebGPU Renderer is much slower than WebGL。
这件事对工程模型尤其重要,因为 BIM、机械装配和工厂模型天然包含大量构件。一个源文件可能有数万甚至更多对象,每个对象又有自己的材质、变换、可见性和业务身份。如果把源结构原样映射成“一个构件一个 Mesh、每帧逐个更新”,换 API 不能消除对象级管理成本。
先把性能问题拆成五层
1. 数据准备
RVT、NWD、STEP、JT、CATIA 等源工程格式不是浏览器运行时格式。它们包含 B-Rep、装配关系、实例、属性和单位。第一次解析时应生成面向查看的分块几何、材质资源、结构属性索引和稳定对象身份;后续打开复用缓存。
如果每次打开都在浏览器重新解析完整源文件,渲染 API 还没有开始工作,CPU 和内存就可能已经成为瓶颈。
2. 对象与批次
减少 Draw Call 很重要,但“把所有东西合成一个 Mesh”不是通用答案。合并粒度过大后,视锥裁剪、局部隐藏、构件选择、LOD 和按需释放都会失效。
更实用的组合是:
- 重复零件共享几何和材质;
- 可实例化的对象使用实例化;
- 静态小对象按材质和空间形成合理批次;
- 批次内部仍保留从 GPU 结果回到工程构件 ID 的映射。
3. 可见性与工作集
工程大模型不应该全量常驻 GPU。相机当前能看到什么、接下来可能看到什么、哪些构件正在被选择或审阅,决定本帧工作集。
分块、LOD、视锥裁剪、遮挡、请求优先级和资源回收需要形成同一套调度策略。否则只是更快地把无关数据上传到显存。
4. 每帧状态变化
大量对象的矩阵、材质参数或可见性每帧变化,会造成 buffer 更新、绑定和状态切换。WebGPU 能提供更底层的控制,但应用仍需把变化聚合为紧凑数据:
- 把可见性、选择、高亮等状态放进结构化 GPU buffer;
- 只更新真正变化的范围;
- 避免遍历整棵场景树同步状态;
- 将静态资源与动态状态分离。
5. 交互与对象身份
大模型查看器不能为了批处理丢掉构件身份。结构树、属性、拾取、隐藏、测量、剖切和业务台账必须指向同一对象。
因此优化目标不是“Mesh 数最少”,而是“在可控批次内保留稳定身份,并让 GPU 和业务层都能高效访问”。这也是普通 Three.js Demo 与工程模型产品之间最容易被低估的差异。
测试时不要只看平均 FPS
至少分别记录:
- 首次源格式转换时间;
- 缓存后的再次打开时间;
- 首屏可交互时间;
- 相机快速移动时的帧时间和资源请求;
- 结构树选择、隐藏、剖切时的长任务;
- CPU 内存、GPU 显存和资源回收后的稳定值。
平均 FPS 很高但第一次交互冻结两秒,或者浏览十分钟后显存持续上涨,都不能算稳定的大模型体验。
工程化路线
我们做 ModelView 时,把 WebGPU 放在完整链路的一层:源格式首次转换后生成可复用缓存,运行时再通过分块、LOD、按需加载和 GPU 调度控制工作集,同时保持结构树、属性、选择和协同状态的对象身份。
性能口径也必须把条件说完整:模型完成首次转换并生成缓存后,亿级三角面模型可以在 5 秒内再次打开并进入可交互视图,浏览和审阅过程保持满帧渲染。这里不是把首次 RVT、STEP 等源文件转换时间算成 5 秒。
说明一下,我是 ModelView 的开发者。Windows 标准版可以直接下载并用自己的真实模型测试;如果项目需要企业内网、在线平台、界面工具或业务接口定制,也可以在现有引擎上继续开发: