You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

THREE.js大量重复简单网格致帧率骤降,求最优解决方案

解决Three.js大量圆柱网格导致的帧率问题:最佳实践与最优方案

嘿,这个问题我太熟了——15000个独立网格确实会把GPU拖垮,毕竟每个网格都会触发一次独立的绘制调用,而绘制调用数是Three.js性能瓶颈的核心因素之一。你已经做了复用几何体和材质的基础优化,但这还不够,咱们来拆解最优方案和行业最佳实践:

一、最优方案:二选一,看场景需求

1. 静态场景首选:合并几何体(BufferGeometryUtils.mergeGeometries)

如果这些圆柱是完全静态的(不需要单独移动、旋转或修改),合并所有几何体成一个大的BufferGeometry是性能最优的选择——这样整个场景里只有1个绘制调用,GPU压力会骤降。

修改你的代码思路:

  • 先创建一个空数组,存储每个圆柱应用了位置变换后的几何体
  • 循环中不再创建Mesh,而是克隆基础几何体,应用位置矩阵后加入数组
  • 最后用BufferGeometryUtils.mergeGeometries合并所有几何体,创建单个Mesh

示例代码:

import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; // 注意导入工具类

var base_material = new THREE.MeshPhongMaterial( { color: 0x666666, side: THREE.FrontSide });
var cylinderGeometry = new THREE.CylinderGeometry( 1, 1, 1, 4 );
var floor_geometry = new THREE.PlaneGeometry( 68, 10000, 10 );
var floor = new THREE.Mesh( floor_geometry, base_material );
floor.receiveShadow = true;
scene.add( floor );
floor.position.set(0,-15,-530);
floor.rotation.x = -Math.PI / 2;

// 收集所有圆柱的几何体
const cylinderGeometries = [];
for ( var i = 0; i < 15; i++ ) {
    const posX = -28.4 + i*4.1;
    for ( var n = 0; n < 1000; n++ ) {
        const posZ = 0 - n*6;
        // 克隆基础几何体并应用位置变换
        const clonedGeo = cylinderGeometry.clone();
        clonedGeo.translate(posX, -14.7, posZ); // 直接把位置应用到几何体上
        cylinderGeometries.push(clonedGeo);
    }
}

// 合并成单个几何体
const mergedGeometry = BufferGeometryUtils.mergeGeometries(cylinderGeometries);
const mergedCylinders = new THREE.Mesh(mergedGeometry, base_material);
// mergedCylinders.castShadow = true; // 如果需要阴影,只需要给这一个Mesh开即可
scene.add(mergedCylinders);

2. 动态场景首选:InstancedMesh

如果以后需要单独控制某些圆柱的位置、旋转、缩放,甚至修改材质属性,InstancedMesh是更好的选择。它只需要1个绘制调用,但能通过矩阵数组控制每个实例的状态,兼顾性能和灵活性。

示例代码:

var base_material = new THREE.MeshPhongMaterial( { color: 0x666666, side: THREE.FrontSide });
var cylinderGeometry = new THREE.CylinderGeometry( 1, 1, 1, 4 );
var floor_geometry = new THREE.PlaneGeometry( 68, 10000, 10 );
var floor = new THREE.Mesh( floor_geometry, base_material );
floor.receiveShadow = true;
scene.add( floor );
floor.position.set(0,-15,-530);
floor.rotation.x = -Math.PI / 2;

// 创建InstancedMesh,总实例数是15*1000=15000
const instanceCount = 15 * 1000;
const instancedMesh = new THREE.InstancedMesh(cylinderGeometry, base_material, instanceCount);
scene.add(instancedMesh);

// 遍历设置每个实例的矩阵
let instanceIndex = 0;
const matrix = new THREE.Matrix4();
for ( var i = 0; i < 15; i++ ) {
    const posX = -28.4 + i*4.1;
    for ( var n = 0; n < 1000; n++ ) {
        const posZ = 0 - n*6;
        matrix.makeTranslation(posX, -14.7, posZ);
        instancedMesh.setMatrixAt(instanceIndex, matrix);
        instanceIndex++;
    }
}
instancedMesh.instanceMatrix.needsUpdate = true; // 必须更新矩阵数组

// 如果需要单独修改某个实例,随时调用setMatrixAt并标记needsUpdate即可

二、为什么你的原代码帧率低?

你已经复用了几何体和材质,这是很好的基础,但每个Mesh都是独立的渲染对象——即使几何体和材质复用,GPU还是要为每个Mesh执行一次绘制调用。15000次绘制调用会让GPU的CPU端瓶颈爆炸,这就是帧率暴跌的核心原因。

三、额外性能优化建议

  • 阴影优化:如果需要开启阴影,静态场景可以烘焙阴影到纹理;动态场景用InstancedMesh的话,开启castShadow只需要一次阴影计算,比15000个Mesh开阴影开销小得多。同时可以降低阴影贴图的分辨率(light.shadow.mapSize)。
  • 视锥体剔除:Three.js默认会剔除相机视野外的对象,但对于超大场景,你可以考虑用THREE.Octree或BVH加速结构来优化剔除,但对于这个场景,合并或InstancedMesh已经足够。
  • LOD(细节层次):如果相机离得远时不需要显示圆柱的细节,可以用LOD对象,远距时切换成更低面数的几何体。

总结最佳实践

  1. 优先减少绘制调用:用InstancedMesh或合并几何体是解决大量重复对象性能问题的核心方案。
  2. 静态选合并,动态选InstancedMesh:根据场景是否需要动态控制实例来选择。
  3. 基础优化不能少:复用几何体、材质、纹理是性能优化的基础,但不足以解决大量对象的问题。
  4. 注意渲染附加开销:阴影、抗锯齿等功能会放大性能问题,需要针对性优化。

内容的提问来源于stack exchange,提问作者Toki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 14:37:41