跳到主要内容
3D Playground
GPU 与 WebGPU 计算第 8 / 11 节

绘制调用与实例化

进阶WebGL

绘制调用的代价

每次绘制调用(draw call),CPU 都要做一堆准备:切换着色器、绑定缓冲、上传这个物体的矩阵 uniform、做驱动校验,然后才把命令交给 GPU。几千个小物体时,瓶颈往往不在 GPU 画三角形,而在 CPU 发命令。状态栏里的 draw calls 来自 renderer.info.render.calls。

三种策略

  • separate:N 个 Mesh 共享同一几何体,但每个都是一个场景对象,就是 N 次绘制调用。最灵活,最慢。
  • instanced:一个 InstancedMesh,一次绘制调用。每个实例的变换矩阵和颜色放在 GPU 缓冲里(instanceMatrix、instanceColor),顶点着色器按实例编号去取。
  • merged:把所有副本预先变换好,合并成一个大 BufferGeometry,也只有一次绘制调用;但各个小物体再也不能单独运动了。

实例化的写法

js
const inst = new THREE.InstancedMesh(geo, mat, count);
inst.setMatrixAt(i, m.compose(pos, quat, scale));
inst.instanceMatrix.needsUpdate = true;

每帧更新所有矩阵,然后一次上传整个缓冲,代替 N 次 uniform 更新。用 setUsage(THREE.DynamicDrawUsage) 告诉驱动这个缓冲会频繁改写。

在着色器层面,实例化就是 gl.drawArraysInstanced(WebGPU 里是 draw(vertexCount, instanceCount)),顶点着色器多了一个内置的实例编号。

怎么选

  1. 很多相同几何体、各自运动:实例化。
  2. 静态、不再移动的场景(建筑、地形装饰):合并,连每帧更新矩阵的 CPU 循环都省了。
  3. 少量需要独立材质、独立交互的物体:普通网格就好。

看数字

把 count 调到 30000,在 separate 模式下观察 CPU update + render() 的毫秒数和帧率,再切到 instanced 和 merged。三角形数量完全一样,差别几乎全来自 CPU 一侧。

注意 instanced 模式下 CPU 时间主要花在 JS 循环里重算 3 万个矩阵;如果把运动挪到顶点着色器(或计算着色器)里,CPU 就几乎无事可做了。

核心 API

THREE.InstancedMeshinstancedMesh.setMatrixAtinstancedMesh.setColorAtinstanceMatrix.needsUpdateTHREE.DynamicDrawUsagemergeGeometriesrenderer.info.rendergl.drawArraysInstanced

动手试试

  1. 1把 count 拉到 30000,依次切换三种模式,记下每种的 CPU 毫秒数和 draw calls。
  2. 2关掉 animate objects,再看 instanced 模式的 CPU 时间降了多少。
  3. 3把 IcosahedronGeometry 的细分从 0 改成 3,三角形变多后,哪种模式最先吃力?
运行中