1、网格合并
简介
多数情况下使用组可以很容易地操纵和管理大量网格。但是当对象的数量非常多时,性能就会成为一个瓶颈。使用组,每个对象还是独立的,仍然需要对它们分别进行处理和渲染。通过
THREE.Geometry.merge() 函数,你可以将多个几何体合并起来创建一个联合体。
当我们使用普通组的情况,绘制20000个立方体,帧率在15帧左右,如果我们选择合并以后,再绘制两万,就会发现,我们可以轻松的渲染20000个立方体,而且没有性能的损失。合并的代码如下:
javascript
//合并模型,则使用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<gui.numberOfObjects; i++){
var cube = addCube();
cube.updateMatrix();
geometry.merge(cube.geometry, cube.matrix);
}
scene.add(new THREE.Mesh(geometry, cubeMaterial));
THREE.GeometryUtils.merge() 已经将此方法移动到了 THREE.Geometry 对象的上面了,我们使用 addCube 方法进行立方体的创建,为了确保能正确的定位和旋转合并的 THREE.Geometry 对象,我们不仅向 merge 函数提供 THREE.Geometry 对象,还提供该对象的变换矩阵。当我们将此矩阵添加到 merge 函数后,那么合并的方块将被正确定位。
和组的优缺点对比
缺点: 组能够对每个单独的个体进行操作,而合并网格后则失去对每个对象的单独控制。想要移
动、旋转或缩放某个方块是不可能的。
优点: 性能不会有损失。因为将所有的的网格合并成为了一个,性能将大大的增加。如果需要创建大型的、复杂的几何体。我们还可以从外部资源中创建、加载几何体。
2、尽量重用Material和Geometry
这里以Material和Geometry为例(使用比较频繁)
javascript
for (var i = 0; i < 100; i++) {
var material = new THREE.MeshBasicMaterial();
var geometry = new THREE.BoxGeometry(10, 10, 10);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
改为
javascript
var material = new THREE.MeshBasicMaterial();
var geometry = new THREE.BoxGeometry(10, 10, 10);
for (var i = 0; i < 100; i++) {
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
3、尽量使用clone方法
这个的原理其实跟2是一样的
4、重点优化requestAnimationFrame内的方法
我们知道几乎所有的资源都是花费在requestAnimationFrame内的方法,所以我们在需要的时候去执行,如果不需要则不执行。
5、删除模型时,将材质和几何体从内存中清除
使用 remove() 将模型从场景内删除掉,大家会发现内存基本上没有怎么降低。因为几何体和材质还保存在内存当中,我们需要手动调用 dispose() 方法将其从内存中删除。
javascript
1. item.geometry.dispose(); //删除几何体
2. item.material.dispose(); //删除材质
6、在循环渲染中避免使用更新
这里的更新指的是当前的几何体、材质、纹理等发生了修改,需要 Three.js 重新更新显存的数据,具体包括:
几何体:
javascript
geometry.verticesNeedUpdate = true; //顶点发生了修改
geometry.elementsNeedUpdate = true; //面发生了修改
geometry.morphTargetsNeedUpdate = true; //变形目标发生了修改
geometry.uvsNeedUpdate = true; //uv映射发生了修改
geometry.normalsNeedUpdate = true; //法向发生了修改
geometry.colorsNeedUpdate = true; //顶点颜色发生的修改
材质
javascript
material.needsUpdate = true
纹理
javascript
texture.needsUpdate = true;
如果它们发生更新,则将其设置为true,Three.js会通过判断,将数据重新传输到显存当中,并将配置项重新修改为false。这是一个很耗运行效率的过程,所以我们尽量只在需要的时候修改,不要放到render()方法当中循环设置。
只在需要的时候渲染
如果在没有操作的时候,让循环一直渲染属于浪费资源,接下来我来带给大家一个只在需要时渲染的方法。
首先在循环渲染中加入一个判断,如果判断值为true时,才可以循环渲染:
javascript
var renderEnabled;
function animate() {
if (renderEnabled) {
renderer.render(scene, camera);
}
requestAnimationFrame(animate);
}
animate();
然后设置一个延迟器函数,每次调用后,可以将 renderEnabled 设置为 true ,并延迟三秒将其设
置为 false ,这个延迟时间大家可以根据需求来修改:
javascript
//调用一次可以渲染三秒
let timeOut = null;
function timeRender() {
//设置为可渲染状态
renderEnabled = true;
//清除上次的延迟器
if (timeOut) {
clearTimeout(timeOut);
}
timeOut = setTimeout(function () {
renderEnabled = false;
}, 3000);
}
接下来,我们在需要的时候调用这个 timeRender() 方法即可,比如在相机控制器更新后的回调
中:
javascript
controls.addEventListener('change', function(){
timeRender();
});
如果相机位置发生变化,就会触发回调,开启循环渲染,更新页面显示。
如果我们添加了一个模型到场景中,直接调用一下重新渲染即可:
javascript
scene.add(mesh);
timeRender();
javascript
最后,一个重点问题,就是材质的纹理由于是异步的,我们需要在图片添加完成后,触发回调。好在
Three.js已经考虑到了这一点,Three.js的静态对象THREE.DefaultLoadingManager的onLoad回调会在
每一个纹理图片加载完成后触发回调,依靠它,我们可以在Three.js的每一个内容发生变更后触发重新
渲染,并且在闲置状态会停止渲染。
javascript
//每次材质和纹理更新,触发重新渲染
THREE.DefaultLoadingManager.onLoad = function () {
timeRender();
};