Three.js 性能优化

尽量共用几何体和材质

如果你需要创建三百个简单的相同颜色的立方体模型:

for (let i = 0; i < 300; i++) {
	let geometry = new THREE.BoxGeometry(10, 10, 10);
    let material = new THREE.MeshLambertMaterial({color: 0x00ffff});
    let mesh = new THREE.Mesh(geometry, material);
    //随机位置
    mesh.position.set(THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200));
    group.add(mesh);
}

我们尽量共用相同的几何体和材质:

let geometry = new THREE.BoxGeometry(10, 10, 10);
let material = new THREE.MeshLambertMaterial({color: 0x00ffff});
for (let i = 0; i < 300; i++) {
    let mesh = new THREE.Mesh(geometry, material);
    //随机位置
    mesh.position.set(THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200));
    group.add(mesh);
}

删除模型时,将材质和几何体从内存中清除

使用remove()将模型从场景内删除掉,大家会发现内存基本上没有怎么降低。因为几何体和材质还保存在内存当中,我们需要手动调用dispose()方法将其从内存中删除。

下面为删除整个场景组的案例代码:

//删除group
function deleteGroup(name) {
    let group = scene.getObjectByName(name);
    if (!group) return;
    //删除掉所有的模型组内的mesh
    group.traverse(function (item) {
        if (item instanceof THREE.Mesh) {
            item.geometry.dispose(); //删除几何体
            item.material.dispose(); //删除材质
        }
    });

    scene.remove(group);
}

使用merge方法合并不需要单独操作的模型
这个方法新版本整合在了几何体上面,主要应用场景为大量几何体相同材质的模型。我们可以通过将多个几何体拼接成一个单个整体的几何体来节约性能,缺点就是将缺少对单个模型的控制。
请点击查看案例:点击这里
如果在不选中combined的时候,选择redraw20000个模型的话,一般只有十几帧的帧率。但是如果选中combined,会发现渲染的帧率能够达到满帧(60帧),性能巨大提升。

merge使用方法:
 

//合并模型,则使用merge方法合并
var geometry = new THREE.Geometry();
//merge方法将两个几何体对象或者Object3D里面的几何体对象合并,(使用对象的变换)将几何体的顶点,面,UV分别合并.
//THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead. 如果新版本用老版本的会报这个错
for(var i=0; i<20000; i++){
    var cube = addCube(); //创建了一个随机位置的几何体模型
    cube.updateMatrix(); //手动更新模型的矩阵
    geometry.merge(cube.geometry, cube.matrix); //将几何体合并
}

scene.add(new THREE.Mesh(geometry, cubeMaterial));

在循环渲染中避免使用更新

这里的更新指的是当前的几何体、材质、纹理等发生了修改,需要Three.js重新更新显存的数据,具体包括:

  • 几何体:
geometry.verticesNeedUpdate = true; //顶点发生了修改
geometry.elementsNeedUpdate = true; //面发生了修改
geometry.morphTargetsNeedUpdate = true; //变形目标发生了修改
geometry.uvsNeedUpdate = true; //uv映射发生了修改
geometry.normalsNeedUpdate = true; //法向发生了修改
geometry.colorsNeedUpdate = true; //顶点颜色发生的修改
  • 材质
material.needsUpdate = true
  • 纹理
texture.needsUpdate = true;

如果它们发生更新,则将其设置为true,Three.js会通过判断,将数据重新传输到显存当中,并将配置项重新修改为false。这是一个很耗运行效率的过程,所以我们尽量只在需要的时候修改,不要放到render()方法当中循环设置。

只在需要的时候渲染
如果在没有操作的时候,让循环一直渲染属于浪费资源,接下来我来带给大家一个只在需要时渲染的方法。

首先在循环渲染中加入一个判断,如果判断值为true时,才可以循环渲染:

var renderEnabled;
function animate() {

    if (renderEnabled) {
        renderer.render(scene, camera);
    }

    requestAnimationFrame(animate);
}

animate();
  • 然后设置一个延迟器函数,每次调用后,可以将renderEnabled设置为true,并延迟三秒将其设置为false,这个延迟时间大家可以根据需求来修改:
//调用一次可以渲染三秒
let timeOut = null;
function timeRender() {
	//设置为可渲染状态
    renderEnabled = true;
    //清除上次的延迟器
    if (timeOut) {
        clearTimeout(timeOut);
    }

    timeOut = setTimeout(function () {
        renderEnabled = false;
    }, 3000);
}
  • 接下来,我们在需要的时候调用这个timeRender()方法即可,比如在相机控制器更新后的回调中:
controls.addEventListener('change', function(){
    timeRender();
});

如果相机位置发生变化,就会触发回调,开启循环渲染,更新页面显示。

  • 如果我们添加了一个模型到场景中,直接调用一下重新渲染即可:
scene.add(mesh);
timeRender();

最后,一个重点问题,就是材质的纹理由于是异步的,我们需要在图片添加完成后,触发回调。好在Three.js已经考虑到了这一点,Three.js的静态对象THREE.DefaultLoadingManager的onLoad回调会在每一个纹理图片加载完成后触发回调,依靠它,我们可以在Three.js的每一个内容发生变更后触发重新渲染,并且在闲置状态会停止渲染。
 

//每次材质和纹理更新,触发重新渲染
THREE.DefaultLoadingManager.onLoad = function () {
	timeRender();
};

猜你喜欢

转载自blog.csdn.net/weixin_41111068/article/details/83068373