In short, I would like to get tips and advice about optimizing models with a big number of instanced objects in them.
Read on for the long version, any thoughts are appreciated!
After having created some lowpoly assets, I indeed sometimes try to use them as particles in blender and display them in ways I like, as you can see on the two models at the end of this post (watch out, they are quite slow to load!).
Scenes I try to create
Here are roughly the steps I follow (no need to read if you are not familiar with blender):
- Create a particle system (a plane for the penguins, a sheep skull for the vegetables/fruits)
- Set the particle parameters (number, rotation, lifespan, physics…)
- Use objects from a “Dupli Group” for the rendered objects (under the “render” panel in the particle system settings)
- If the particle system is dynamic, let the particle system live its life and decide on a frame at which it looks fine to me
- Convert the particles to objects at one specific frame with a method inspired from the one explained on this post which allows me to create linked duplicate objects instead of simply duplicated objects (meaning that the geometry is shared between similar objects)
I then end up with a really light .blend file, containing many instances of a few blender objects.
In the case of the model with the penguins for instance, I end up with a .blend file having ~7500 instanced objects, sharing “only” 5 different geometries. In this case, the models share a unique material as they don’t use textures but vertex colors.
This kind of scene is fully optimized for blender (and offline rendering in general), and although the total number of triangles in the scene is 11 millions, the whole scene is fully described with 5*1500 triangles (5 models, 1500 triangles each), and the final .blend file weights only 3MB.
Hurra! Nice optimized renders in blender!
Now comes the Sketchfab export.
Uploading is really quick as the file is very light (3MB), but the processing step is slow (tens of minutes to a few hours), and although most of the times the processing succeeds after quite a long waiting time, I often witnessed failures during this phase (but a few tries always made it working).
And as you can see if you try to view the penguins model, the “time to first view” is really long, and the performance is far from ideal…
On my Windows laptop (i7 7820hq, Quadro M1200, 16GB RAM ) and under Google Chrome, the total memory used by this scene is ~6GB, and the GPU is used to its maximum capacity ( sorry for the poor benchmark for the time being ). So the graphics seem to be the bottleneck here, but the RAM consumption is not really pleasing to see either…
The questions / requests / calls for help
How can I optimize such a scene, containing thousands of instanced objects, to display on Sketchfab while still keeping a low file size for export?
Some of my thoughts on this matter are in the next paragraph.
Optionnaly, is there a “debug” mode somehow available for the viewer in order to better understand the bottlenecks, number of WebGL Draw calls, objects memory usage… Something similar to the screenshot below, from blend4web.
What I’ve understood, what I want to try
The most obvious solution to improve the performance is to merge every objects together, and therefore upload a file containing only one object. In .fbx or .blend formats, the penguin scene ends up weighting ~300MB. But that’s 100x the minimal file size…
Lowering the polygon number of each model might also help the viewer performance. 11M triangles is quite a lot for the total scene, and every penguin could still retain its shape if it was remeshed to 150 triangles instead of 1500 for instance.
But if we exclude the optimizations made by Sketchfab servers’ converting pipeline, the number of WebGL draw calls, instanced objects and materials should stay the same, and I guess that it is the number of calls that we want to reduce, more than the size of the data processed.
Also, that would feel like cheating, so let’s consider that I want to display 7500 penguins made of 1500 triangles each, no matter what.
| optimizer - materials : 3.00ms
| optimizer - nodes : 5042.00ms
| Geometry count : before 7557, after 541
| Material count : before 2, after 2
optimizer : 5053.00ms
Download model_file_wireframe.bin.gz (Async) : 17794.00ms viewer-771ed3ff4bbc9ee05670.js:12:361004
optimizer - wireframe : 5972.00ms
Lots of pre-processing is done, and I guess that OSG.js spends this time merging objects (7557 objects become 541), probably as a best attempt to reduce the number of draw calls?
But what is this “model_file_wireframe.bin.gz”, which takes 18s (on a good internet connection) to load? And is there anyway to reduce the usage of this kind of files?
Following this kind of optimizations, and if people are curious/interested and that I no one can really help me, I’ll try to do some “benchmarks” in the coming weeks, to try and decide for the best strategy in such cases:
- should I upload a heavy file with a single model?
- should I keep the file size low, but loose on performance by instancing every object?
- should I try to reduce the number of instanced objects by creating groups of 10 penguins for instance, and instancing those groups instead? (this is probably the best type of compromise…)
- what is the best compromise I can do between file size and performance?
Sorry if this post is a bit messy (and very long), as my ideas are not 100% clear on the subject and I don’t know the inner working of OSG.js and its optimization methods.
Any advice or tips are of course very welcome, and if a developer knowing the mechanics of OSG.js and the viewers’ WebGL strategy happens to read this post, I’d love for some explanation on those subjects, or maybe pointers to external resources!
Thanks for reading!