大家好,本文使用three.js實現了渲染大場景,在移動端也有較好的性能,並給出了代碼,分析了關鍵點,感謝大家~
關鍵詞:three.js、Instanced Draw、大場景、LOD、Frustum Cull、優化、Web3D、WebGL、開源
代碼:Github
我正在承接Web3D數字孿生項目,具體介紹可看承接各種Web3D業務
加QQ羣交流:106047770
需求描述
數字孿生項目通常需要渲染超大場景,比如智慧城市就需要渲染一片城市區域,甚至整個城市
渲染大場景所需要的技術點包括:
- Instanced Draw
一個Draw Call批量渲染物體,物體可以有不同的Transform、Color - Frustum Cull
剔除視椎體外的物體 - Occlusion Cull
剔除被遮擋的物體(WebGL1不支持) - LOD
根據物體到相機的距離,顯示不同Level的物體。越遠的越粗糙,越近的越細緻 - GPU Driven Pipeline
把前幾個優化都放到GPU中來做,並且物體可以有更多差異(需要WebGPU) - Space Partition
使用Octree、BVH、BSP等加速結構來劃分場景,在Cull、碰撞檢測、Ray Picking等操作時查詢加速結構而不是遍歷所有物體,從而提高性能 - Multi Thread Render
開一個渲染線程來渲染 - AssetBundle、Stream Load
劃分爲多個場景包,動態、流式加載
本文使用Instanced Draw+Frustum Cull+LOD來渲染大場景,最終實現效果演示:
場景總三角面數是千萬級,總物體數量是1萬(PC端)/5000(移動端),動態物體數量是800(PC端)/400(移動端)
其中,樹使用了Instanced Draw+LOD,白色立方體使用了Instanced Draw
可以把相機拉進、拉遠,可看到不同Level的樹
移動相機,可看到紅框內的Triangles在變化(大概在幾十萬到幾百萬),這是Frustum Cull後的三角面數
在5年前的中配手機上,FPS可達15左右
下面開始實現各個關鍵點,給出實現的思路:
Instanced Draw
比如要將克隆的1000個Mesh改爲Instanced的,則保留它們作爲source,並創建一個InstancedMesh,count設爲1000,寫入1000個Mesh的世界矩陣;然後隱藏source,顯示InstancedMesh
之所以保留source,是因爲可以用它們來做碰撞檢測、Ray Picking等單個Mesh的操作
值得注意的是物體可能是多材質的Object3D(如樹),所以要將其中的每個Mesh拆分到一組Instanced Draw中。舉例來說,如果樹有個3個材質(即3個子Mesh),則需要創建3個InstancedMesh,然後將所有樹的第一個材質的Mesh對應到第一個InstancedMesh中,其餘的以此類推
Frustum Cull
如上圖所示,實現Instanced Draw+Frustum Cull的原理是將要剔除的物體移到InstancedMesh的instanceMatrix的最後,並將count減1
值得注意的是要將three.js默認的對單個Object3D的frustum cull關閉(即將source的frustumCulled設爲false)
另外,我們直接遍歷所有的待剔除物體來進行Fustum Cull檢測,沒有使用Octree等加速結構
相關的討論請參考:Linear search vs Octree (Frustum cull)
LOD
如上圖所示,假設車有3個Level的LOD層級,我們希望離相機越遠,顯示越高的Level(Geometry、Material越簡單)
我們需要創建3個InstanceMesh,分別對應不同的Level,如下圖所示:
參考資料
InstancedMesh2 - Easy handling and frustum culling
What is a more straightforward way to do instance culling?
Linear search vs Octree (Frustum cull)
LOD with Instancing and Multi-Material Meshes
Three.js InstancedMesh performance optimizations - DevLog 10