Ideas for rendering and optimizing complex scenes

Optimizing the geometry is great, but if the scene is CPU bound, it probably won’t help. The amount of work on JavaScript needs to be reduced (numbers of nodes in the scene, what features are being used, etc.). It’s hard to tell from the CPU profile what is happening since it’s so far zoomed out. Can you attach a saved profile or send a more zoomed in picture?

Yes, I used a cascaded shadow map. If you limit the cascading casters. My idea is to determine the center position of the mesh to determine whether or not to add that object to the cascade of casters. However, the mesh with a large number of object faces in the scene has a large world scope, so these meshes need to be segmented.



A calculation usually done at each frame time

Thanks for the reminder, I’m using KHR_draco_mesh_compression at the moment. I read the documentation and found that both extensions do the same thing.

thanks

You spend a lot of time in the shadow map, so reducing the number of meshes rendered in this texture may help. Also, you could try to reduce the number of cascades to 2.

It seems you also spend quite some time in a layer, glow or highlight (it’s the _mainTexture.customRenderFunction in the snapshot). Same thing as the shadow map, can you limit the number of meshes you add to the layer?

Thanks for reminding me. I will optimize the shadow caster count and glow layer item count. Thanks for reminding me. I will optimize the shadow caster count and glow layer item count. I will be adding more mesh to the scene later, any suggestions for a workflow to handle such large scenes?

Now I’ve removed the effect layers and cascaded shadows inside the scene and only kept imageprocess in drp.

But drawcalls still have more than twice as many as mesh.



current calculation usually done at each frame time


I removed the highlight layer and drawcalls is 655. Even if the highlight layer only add an mesh, It will also add the same number of drawcalls? Because I find that adding one or more mesh to the highlight layer increases the number of drawcalls by the same amount. my scene had very few meshes using the highlight layer, but it produced a surprising number of drawcalls.

add many mesh to the highlight layer drawcalls increase 656
add one mesh to the highlight layer drawcalls increase 656 too

Here are some more ideas from chatGPT to optimize your code. The input was your first text on top.

It seems like you’ve put a lot of thought into optimizing your Babylon.js scene. Here are some suggestions that might help you improve performance:

  1. Culling and LOD (Level of Detail):
  • Implement frustum culling to ensure that only objects within the camera’s view are being rendered. This can significantly reduce the number of unnecessary draw calls.
  • Utilize LOD for objects with varying levels of detail based on their distance from the camera. Babylon.js provides LOD support that can be used to swap out high-detail models for simplified versions as objects move away from the camera.
  1. Scene Segmentation:
  • Your idea of segmenting the scene into regions and dynamically showing/hiding objects based on the camera position is a good one. You can manually manage object visibility based on camera proximity or use spatial data structures like octrees to efficiently organize and query the scene.
  1. Offloading with Web Workers:
  • Continue exploring the use of Web Workers to offload rendering tasks. While there might be a delay in passing interactive events, you can experiment with different strategies to minimize this impact. Consider using shared buffers for communication between the main thread and Web Workers.
  1. Progressive Loading and Unloading:
  • If progressive loading is causing issues with thin instances, you might need to implement a custom solution for unloading or use alternative techniques like object pooling. Consider managing the loading and unloading of assets dynamically based on the camera’s view.
  1. 3D Tiles and Scene Simplification:
  • Investigate whether converting large world-range static objects into 3D tiles can benefit your project. This can help in efficiently loading and rendering only the necessary portions of the scene. Tools like loaders.gl can be used in conjunction with Babylon.js for this purpose.
  1. GPU Instancing:
  • Check if GPU instancing can be applied to further optimize rendering performance, especially for repeated geometry. Babylon.js supports GPU instancing, which allows multiple instances of the same object to be rendered in a single draw call.
  1. Optimize Shader Code:
  • Review your shader code for potential optimizations. Simplify complex shaders, remove unnecessary calculations, and ensure that you’re leveraging Babylon.js features like PBR (Physically Based Rendering) efficiently.
  1. Profiling and Monitoring:
  • Use Babylon.js’ built-in tools for profiling and monitoring performance. Identify bottlenecks, such as high CPU usage or GPU load, and prioritize optimizations accordingly.

Remember to test and iterate on these suggestions to find the combination that works best for your specific use case. Additionally, keep an eye on updates to Babylon.js, as new features and optimizations are regularly introduced to the framework.

thank you. i will test these suggestions for feasibility and rendering efficiency

You still have an effect layer enabled (glow / highlight):

image

_mainTexture comes from EffectLayer.

Yes, I removed the effect layer. drawcalls are reduced by a lot. But at some point my scene needs a glowlayer and I need to manually control the mesh list in the glowlayer. But highlight doesn’t seem to be restricted to the mesh you added.

For the highlight layer, you should call addExcludedMesh for the meshes you don’t want to add to the layer. The output will be different than if you don’t call it, though:

Ground not added to the excluded list:

Ground added to the excluded list:

In the latter case, you will get one less draw call.

The highlight layer needs 3 (to blur the hl render target) + 2 (to merge the result with the main scene)=5 additional draw calls to operate, in addition to one draw call per mesh in the layer.

So, in a PG with a sphere+ground, you get:

  • Ground not excluded: (3 + 2) (hl pass) + 2 (main pass) + 2 (hl merge) = 9 draw calls
  • Ground excluded: (3 + 1) (hl pass) + 2 (main pass) + 2 (hl merge) = 8 draw calls

PG:

thanks, this discussion helped me a lot. Thanks again

Current optimization methods for my scene

  1. Use the hl.addExcludedMesh method to exclude the meshes of the scene, because the highlighting effect is only applied to a single mesh at certain times.
  2. Use gl.addExcludedMesh method to exclude most mesh in the scene, because the glow effect is applied to some mesh in the scene.
  3. Use the camera.isInFrustum method to determine whether the mesh is in view and dynamically add and remove shadow caster

I couldn’t exclude the mesh from the light and highlight layers because of occlusion.
I just managed the shadow caster list manually, and there are ~ 1500 fewer drawcalls than before.

Hi,

as I’ve been working on my ^^game^^ for almost 6 years now I think I have collected quiet the list of optimisations for myself - maybe some will help you! :slight_smile:

Optimisations on Engine:

  • antialias: false
  • adaptToDeviceRation: false
  • hardwareScalingLevel >= 2 and more (this is a personal preference, if you want your game / project crispy looking don’t do this :slight_smile: )
  • WebGPU but this is not a given, I realised :slight_smile:

Optimisations on Scene:

  • Create your scene with
useClonedMeshMap: true,
useGeometryUniqueIdsMap: true,
useMaterialMeshMap: true,
  • scene.freezeMaterials()
  • scene.blockMaterialDirtyMechanism = true
  • scene.skipFrustrumClipping = true
  • scene.skipPointerMovePicking = true
  • scene.autoClear = false

If you’re not using pointers to interact with meshes

  • scene.pointerMovePredicate = undefined
  • scene.pointerDownPredicate = undefined;
  • scene.pointerUpPredicate = undefined;
  • scene.constantlyUpdateMeshUnderPointer = false;

Optimisations on Meshes:
Instance and Merge your meshes! if you have many same meshes, instance them, if you have a static mesh that is made of multiple meshes, merge it!

  • mesh.isPickable = false by default, use true only for those you want to pick
  • mesh.checkCollisions = false by default, use true only for those you want to collide with
  • mesh.freezeWorldMatrix() unless you want to translate / rotate / scale the mesh
  • mesh.cullingStrategy = AbstractMesh.CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY
  • mesh = (mesh as Mesh).convertToUnIndexedMesh();

Optimisations on Materials:
I have one global material that I just clone and have a material store and always re-use materials.

  • material.freeze()
  • material.checkReadyOnlyOnce = true
  • material.disableLightning and use emissiveColor for a nice trick that should increase performance

I am sure I have more but this is the building blocks I now always think about :slight_smile: hope it helps anyone

Here’s 5000+ meshes dynamically rendered and animated with less than 150 draw calls:

Edit: I had a bug in the perf reader, there is actually about ~150 draw calls :slight_smile:

6 Likes

Also what I always do - frustrum culling but not based on camera view, rather distance from player, which works for my case, might not work for you:

scene.removeMesh(mesh) and then scene.addMesh(mesh)

1 Like