performance: one single mesh with multiple materials or scene graph (multiple objects with single material) ?

Hi,
I am working on a 3d viewer for buildings. Performance is the main concern here, for this the building elements have been abstracted, i.e. no frivolous meshes like door handles and the materials are restricted to lambert with color and a transparent material for windows.
Right now the building is built as a single mesh with multiple materials, which is very responsive, but we are running into secondary complications that deal with selecting objects and visibility (hide/unhide, isolate) of subobjects like buildingparts.
We have come a long way to get it working based on a single mesh, but I have to question the design decision of single mesh.

My question really boils down to this: given a complex model with a lot of triangles, which is the most optimal organisation of the scene based on the first principles of webgl ? Threejs implements a scene graph with all the fixings involved (iteration, visibilities, boundingbox etc), is there a compelling reason to not use it from a performance perspective ? Is there a compromise situation imaginable ?

thanks,
S

Hello! How much draw calls in your scene now ? For each mesh three.js calculate frustum, matrix, uniforms. On my hardware 1000 draw calls take 9-10ms.

Drawcall is also per material even though you are drawing a single mesh. So you have a chance that you are drawing the same mesh multiple times of how many materials are attached to your mesh. (See this codepen for a demonstration. Draw call is printed in the console. https://codepen.io/avseoul/pen/dyZKXOe)

Also, opaque objects and transparent objects cannot be rendered at the same stage in general since they need to be sorted differently for blending, so there is another chance your batched mesh is broken and rendered in multiple times since you mentioned that the mesh has transparent materials as well.

I think rule of thumb here (since there might be some internal drawcall optimizations done by threejs that I’m not aware), you want to batch the meshes that are share the same material that are share the same uniforms. If you need some diversity for those batched mesh then you should be able to via some tricks:

  • Use geometry attribute, pass variables per vertex and look them up in shader via vertex id or something (Something similar that is done in this instancing example)
  • Use texture as a map to store variables per vertex and look them in shader via texture coordinates (Something similar in threejs gpgpu examples)
  • If you need different textures for different part, then use texture atlas
2 Likes

@sndr_b late to the discussion here. I’m trying to build similar applications in manufacture industry, with original glb model having no textures but high triangle count and scene graph objects (up to 50000), objects are diverse and instancing might not be an option.

Given the similar background, is it possible for your to kindly share which path did you take and the pros and cons after ?

I recommend using BatchedMesh + LODs created at runtime for more complex geometries, as in this example

3 Likes

@agargaro thank you for replying! I am not particular familiar with BatchedMesh and LOD, but after a bit of research, I’m wondering if my case would be suitable for such approach:

  1. my loaded models are large, 50 mb in glb and larger ones are expected, under such case I feel that introducing LOD might cause memory pressure or draw call issues?

  2. I’m planning to add features such as highlighting mesh on selection, or filtering and dynamic show/hide specific parts of the mesh based on userData’s content, BatchedMesh seems to merge all geometry into one single mesh and these features won’t be able to implement.

50,000 draw calls is too many, unless you’re OK with low framerates. To reduce the draw calls while preserving interactivity, filtering, highlighting, etc., you must use some form of batching. This does mean that you’ll need to implement these interactive features without the convenience of separate scene graph meshes and materials on each of the 50,000 objects, but that’s OK.

BatchedMesh is the next-most-convenient built-in option for three.js. For example you can show/hide objects dynamically, to ā€˜hide’ an object you could temporarily hide it from the batch and render it separately as a 2nd draw call, or use custom shaders to highlight it using the fragment shader.

That said, it’s also possible to implement this just within a normal mesh, it just requires more manual control, see When is it actually beneficial to use LOD in Three.js for performance? - #10 by donmccurdy.

1 Like

What is the the total vertex count you’ll need to visualize in the scene at one time? That’s usually a more helpful number than the file size on disk — in case your GLB contains more data than just geometry, or uses compression, it’s hard to guess vertex count based on file size.

If you implement LODs using BatchedMesh, as in the example @agargaro shared, then the LODs don’t necessarily require any extra draw calls, and memory cost shouldn’t be much, but – if the individual meshes are not very complex, then LODs might not help you much and I’d just focus on the draw calls first.

1 Like

Appreciate the insightful suggestions.

By using glTF report (thank you for the convenient tool), here is the currently known extreme rendering case I might encounter for one given glb model. Original glb has no textures, and materials are all simple MeshStandardMaterial and deduplicated, and total of 55700 meshes in total. Meshes are not super complex geometries, mostly cylinders with half-transparent material

Given the suggestion to reduce draw calls, I have tried to merge all meshses into one giant mesh using BufferGeometryUtils.mergeGeometries with geometry groups. And I have set all matrix update related properties to false as i’m rendering a static scene.

FPS definitely has a great improvement (~15fps on my 4060, still a bit laggy but … kinda acceptable). But now I am kind of stuck and try to work a way out to implement the show/hide, highlighting specific geometry groups features.

If i understand correctly, I have to come up with a system to dynamic manipulate the material at corresponding index of materials array (since manipulating shared materials affect other geometry groups) to perform show/hide or highlighting, and properly dispose it to avoid memory leak. And given my case, I suppose memory consumption is already quite intense and adding LOD might need careful considerations.

But if allocating extra materials, each materials will result in a new draw call, which is another consideration to take into account.

I’ m trying to build a viewer such that on selection, item name can be used to query detail info on the backend for display. I’m wondering if I am on the right path? As this approach allows large vertex rendering but gives up the flexibility of separate mesh and materials, I’m afraid there are features that will become impossible to implement if I take this path, or maintaining such material system will eventually bite myself and other approaches will be recommended.

Thanks again for your advice

One thing to note is that ā€œgeometry groupsā€ are also draw calls, so merging 50K meshes into 50K geometry groups does not reduce draw calls, only the CPU overhead of having those objects in the scene graph. A quick thing to test would simply be to merge the geometry – without groups – and check your framerate when rendering. You’ll need to do

Personally I would probably aim to have <100 meshes and materials, in total. This does mean that selection, visibility, and highlighting need to be implemented without separate meshes and materials for each object. For example, a lookup table tracking that geometry indices 1-100 correspond to object A, 100–200 are object B, etc. This is essentially what BatchedMesh does under the hood.

If you are OK with pre-processing the scene for streaming, 3D Tiles might be another option, GitHub - NASA-AMMOS/3DTilesRendererJS: Renderer for 3D Tiles in Javascript using three.js, essentially streaming chunks of the scene in and out of memory.

2 Likes