1. 程式人生 > >Three.js 效能優化

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();
};