glTF in Unity optimization - 2. Avoid Tangents and Normals Calculation
This is part 2 of a mini-series.
Instant side quest
Originally I wanted to investigate the benefit of parallel jobs in this post. I started by creating test cases and profiling the status quo. That's where I figured that there's higher potential in other optimizations.
Analysis
I created two test scenes
- The "HighRes" scene with one 4 million triangle mesh
- The "Sixfold" scene with six meshes with ~252k triangles each (1.5 million triangles total)
This is the HighRes scene in Unity's profiler. More specifically, the first frame:
Most time is spent/lost allocating Unity's data structures (a hint to switch to the new Mesh API, maybe). Let's look at the second part in the following frame:
Here the Unity Mesh is created and it takes a devastating 1.279 seconds !!! Most of that time (1.06 seconds) is spent calculating tangents. Overall this scene loads in ~1.6 seconds.
For the Sixfold scene the result is similar, except that the six Mesh creations are spread amongst six frames, so at least the frame stall is not as bad.
Optimize
So tangent recalculation is expensive. The best and first way to make code faster is to not execute it, so why do we need tangents in first place? Some materials/shaders rely on correct them. I know they are needed for consistent normal mapping, but I'm not sure if for anything else (maybe anisotropic shading?). For now I assume we only need them for normal mapped materials.
Calculating normals is less expensive than tangents, but while we're at it, let's think about them as well. They are necessary for all shaded materials, but not for unlit ones.
So in glTFast 0.10.2 I changed the importer to only calculate normals/tangents, if the material requires them. Let's have a look at the final frame when loading the previous scene again:
Boom, down a full second. The whole scene now loads in ~700 ms. It's even faster when using unlit materials:
Loads consistently in under 400 ms. Nice achievement for a couple of lines of code.
This was released in glTFast 0.10.2.
Next up
What about all the cases where having normal/tangents is absolutely necessary? The Unity mesh API does not provide us with a threaded version of RecalculateNormals or RecalculateTangents, so the only way around this would be to make one's own C# job that does the normal/tangent calculations on a thread.
I definitely plan to do this, but only after glTFast switched to the new Mesh API (based on NativeArrays) and other improvements, so stay tuned!
Follow me on twitter or subscribe the feed to not miss updates on this topic.
If you liked this read, feel free to
Next: 3 Parallel Jobs