three.js小白的學習之路。
在上上一篇博客中,簡單驗證了一下three.js中的網格共享。寫的時候就有一些想法,如果說某個場景中有一萬棵樹,這些樹共享一個geometry和material,有沒有好的辦法將其進行一定程度上的渲染優化,以提高瀏覽器的幀率。
查閱了了一番資料,找到一個three.js中的類——InstanceMesh。
介紹
three.js的官網是這樣介紹的:InstancedMesh實例化網格是特殊版本的Mesh(具有mesh的相關屬性和方法),可以用來渲染大量的具有相同幾何體和材質、但具有不同世界變換的物體。InstancedMesh可以減少draw call的數量,從而提升程序整體的渲染性能。聽起來就和我們想要的效果很搭。
構造函數:
InstancedMesh(geometry: BufferGeometry, material: Material, count: Number)
其中geometry和material表示相同的幾何體和材質,count表示有多少共用這些幾何體和材質的mesh。
屬性:
.count
實例的數量,可以在運行時改變這個數值,范圍是[0, count],如果比count大,需要重新創建實例。
還有其他如boundingBox(外邊接矩形)、instancedColor(所有實例的顏色)、instanceMatrix(所有實例的局部變換)等。
方法:
.computeBoundingBox(): undefined
.computeBoundingSphere(): undefined
分別對應boundingBox和boundingSphere兩個屬性。
.dispose(): undefined
釋放實例的內部資源。
.setMatrixAt( index: Number, matrix: Matrix4): undefined
index表示是第幾個實例Mesh,范圍是[0, count);matrix表示一個給定的局部變化矩陣。
其他的方法還有setColorAt(設置已定義實例的顏色)、getColorAt、getMatrixAt等。
示例
假如說有這么一個山地場景,里面需要很多很多的樹(這里為了方便,就使用一個平面+五個立方體表示一棵樹)。每棵樹共用geometry和material,但是每個數的平移旋轉縮放均不相同。
首先先正常的將其加載到場景里面:
new GLTFLoader().load("/instanced.glb", (glb) => {const mesh = glb.scene;scene.add(mesh);
});
此時,場景的渲染幀率如圖所示:
幀率大概只有14幀左右。
接下來對場景中每棵樹,也就是立方體進行InstancedMesh實例化。
首先需要獲取到所有的立方體Mesh:
const instanceArr: Three.Mesh[] = [];
glb.scene.traverse((item) => {if (item.isMesh && item.name.startsWith(name)) {instanceArr.push(item);}});
接下來就是對這些Mesh進行實例化:
const count = instanceArr.length; // 相同模型的總個數// 創建InstancedMesh實例const instanceMesh = new Three.InstancedMesh(instanceArr[0].geometry,instanceArr[0].material,count);
通過數組的第一個元素,獲取到共享的geometry和material。
然后就是在獲取所有mesh的世界變換,并分別應用到每一個實例Mesh中:
const matrix = new Three.Matrix4();const pos = new Three.Vector3();const qua = new Three.Quaternion();const sca = new Three.Vector3();instanceArr.forEach((obj, i) => {obj.getWorldPosition(pos);obj.getWorldQuaternion(qua);obj.getWorldScale(sca);matrix.compose(pos, qua, sca); // 將此矩陣設置為由平移、旋轉和縮放組成的變換。instanceMesh.setMatrixAt(i, matrix); // 更新InstancedMesh中每個實例的變換矩陣});
最后將InstancedMesh加入到場景中,并且將原有的所有立方體mesh從場景中移除:
scene.add(instanceMesh);instanceArr.forEach((obj) => {obj.parent?.remove(obj); // 模型移除});instanceArr.splice(0, count); // 清除引用
看一下運行結果:
可以看到,幀率幾乎拉滿到60幀,提升效果顯著!
思考
做了一個簡單的實驗,就是將現在這個模型在一個性能較差(內存小,沒有獨顯等)電腦上加載,使用InstancedMesh前后的性能對比如圖所示:
使用前
使用后
可以看到,使用前后的幀率幾乎一樣,都在3-5幀。
這就拋出了一個問題,就是如果模型本身就已經超出或者將要超出瀏覽器的承載極限時,這個InstancedMesh方法幾乎是沒有效果的。從這個角度來說,這個優化或許有一些局限性,即適用于那些有大量的共享網格和材質,且瀏覽器幀率較低,但是瀏覽器依然可以正常運行的情況。
總結
InstancedMesh類可以很好地優化那些具有大量的共享材質和網格,但是有不同平移旋轉縮放的模型,就類似于blender中使用了Alt+D復制了很多份,然后每一個模型的位姿又有不同。