Three.js 中的如何一次性绘制八千多个网格对象,还能保持渲染性能

要一次性绘制这么多网格对象,还能保持 60 帧,关键要借助于 Three.js 中的 InstancedMesh(实例化网格)。

InstancedMesh

介绍

InstancedMesh 作为一种具有实例化渲染支持的特殊版本的 Mesh。

我们可以通过 InstancedMesh 来渲染大量具有相同几何体与材质、但具有不同世界变换(位置、缩放、旋转)的大量重复物体,比如,我们可以通过 InstancedMesh 来渲染一大片青青草地或者一棵棵相同的树木。

而却 InstancedMesh 将帮助我们减少 draw call 的数量,从而提升你应用程序的整体渲染性能,绘制了几万个模型,还能保持 60 帧。

InstancedMesh 是如何做到帮助我们减少 draw call 的数量

通过以下手段,InstancedMesh 做到将渲染过程中的大部分开销从 CPU 转移到 GPU 上,并将多个实例的渲染合并为一次绘制调用。

这样就大大减少了 CPU 与 GPU 之间的通信开销,提高了渲染性能,特别是在需要渲染大量重复对象的场景中。

1. 单次绘制多个实例

使用传统的渲染方式,每个对象都需要进行一次绘制调用。

而通过 InstancedMesh,您可以在单个绘制调用中渲染多个实例,因为它将所有实例的数据打包在一起,一次性传递给渲染器。无论渲染多少个实例,只需要一次绘制调用,从而显著减少了绘制调用的数量。

2. 实例数据的缓冲区

InstancedMesh 将每个实例的属性数据存储在单个缓冲区中,而不是为每个实例创建单独的几何体对象。这样一来,数据传输和处理的开销大大减少。

实例数据可以包括每个实例的位置、旋转、缩放、材质参数等。通过将这些数据存储在缓冲区中,渲染器可以直接从缓冲区中读取数据,而无需逐个访问每个实例。

3. 并行处理

现代的图形处理器(GPU)具有强大的并行处理能力。

使用 InstancedMesh 进行实例化渲染时,渲染器可以利用 GPU 的并行计算能力,同时处理多个实例。这样可以大大加快渲染的速度,提高整体性能。

开发应用

  1. 构建 Three.js 场景的世界要素(scene、camera、renderer)以下简略描述:
tsx 复制代码
const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(frustumSize, aspect, 1, 10000);

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('canvas.webgl')!,
  antialias: true,
});
  1. 使用 BufferGeometryLoader 加载器来加载 BufferGeometry 对象,实例化 THREE.InstancedMesh 网格对象:
tsx 复制代码
const loader = new THREE.BufferGeometryLoader();
loader.load('./models/json/suzanne_buffergeometry.json', (geometry) => {
  geometry.computeVertexNormals();
  geometry.scale(0.5, 0.5, 0.5);

  let material = new THREE.MeshNormalMaterial();
  let mesh = new THREE.InstancedMesh(geometry, material, count);
  mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
  scene.add(mesh);
});

这里解释一下instanceMatrix的使用:

tsx 复制代码
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);

通过将实例矩阵instanceMatrix的使用方式设置为 DynamicDrawUsage,您告诉 Three.js 渲染器,实例矩阵可能会经常改变,并且希望优化相关的内存和 GPU 资源管理。

这对于需要动态更新实例矩阵的情况非常有用,例如在每帧中更新实例位置或动画实例对象。

  1. 通过循环遍历 THREE.InstancedMesh 网格对象中实例的索引(20 _ 20 _ 20),设置它们的位置、旋转、缩放等属性:
tsx 复制代码
let dummyRef.current = new THREE.Object3D();
let instancedMeshRef.current = modelMesh; // THREE.InstancedMesh

let amount = 20;
let i = 0;
let offset = (amount - 1) / 2;

for (let x = 0; x < amount; x++) {
  for (let y = 0; y < amount; y++) {
    for (let z = 0; z < amount; z++) {
      dummyRef.current.position.set(offset - x, offset - y, offset - z);
      // 更新局部变换
      dummyRef.current.updateMatrix();
      instancedMeshRef.current.setMatrixAt(i++, dummyRef.current.matrix);
    }
  }
}

instancedMeshRef.current.instanceMatrix.needsUpdate = true;
instancedMeshRef.current.computeBoundingSphere();

其中,setMatrixAt( index : Integer, matrix : Matrix4 ) 方法是设置给定的本地变换矩阵到已定义的实例,并确保在更新所有矩阵后,将 instancedMeshRef.current.instanceMatrix.needsUpdate 设置为 true。

在 Three.js 中,mesh.computeBoundingSphere()是一个用于计算网格对象的边界球(Bounding Sphere)的方法。

请注意,mesh.computeBoundingSphere()方法是一个在运行时计算边界球的方法。如果您的网格对象的几何体发生了变化,您可能需要在适当的时机调用该方法来更新边界球。

边界球的计算对于许多应用非常有用,特别是在进行场景剔除(frustum culling)时。 场景剔除是一种优化技术,用于确定哪些对象在相机的视锥体内,并只渲染那些可见的对象。通过使用边界球,可以快速检查一个网格对象是否与相机的视锥体相交,从而进行剔除操作,避免对不可见的对象进行渲染,提高渲染性能。

相关资料

相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax