Draw call batching in Unity
This week, I want to share with you my journey into optimization in Unity 3d with Draw call batching.
I’m currently working in a project where I’m instantiating at runtime, several thousand meshes in the same scene. It quickly led to an awful performance and got me researching about optimization tips.
The 3d artist (@atomonun21) was already aware of this possibility and employed some techniques to minimize the performance impact:
Even so, the performance was still subpar.
So, we checked the draw call count and it was indeed above the roof. A “draw call” is a request issued by the engine to the graphics API to draw an object on the screen (hence the name). This is a quite “heavy” procedure so we should always try to keep the draw call number at a minimum. But, with more GameObjects, more draw calls.
DRAW CALL BATCHING
Luckily, Unity uses a technique called draw call batching, to reduce the total number of draw calls. To put it simply, instead of having a draw call per GameObject, it groups, or batches multiple GameObjects in the same draw call.
However, to take advantage of batching we need to follow some rules (to be honest a whole lot of rules). If we “break” those rules, we won’t be able to batch the GameObjects together.
For example, one simple rule to remember is:
“Only GameObjects sharing the same Material can be batched together.”
So you should always try to share Materials among GameObjects. You can have a closer look at what’s being batched or not, using the Frame Debugger window.
In this case, since both cubes are sharing the same material (and a lot more rules that we didn’t cover), they are being dynamically batched into the same call.
In this second example, we cannot batch both cubes together because they have different materials.
Now, please pay close attention to the following (trust me, I’ve learned it the hard way):
“If you need to access shared Material properties from the scripts, then it is important to note that modifying Renderer.material creates a copy of the Material. Instead, use Renderer.sharedMaterial to keep Materials shared.”
In this project, most, if not all objects were static so I had to dig deeper into this type of batching.
Basically, we can batch together, GameObjects that share the same material and are marked as static in the Editor.
It sounded really easy to use this in the project since most objects were static. However, I’m instantiating these object at runtime. So I had no way of marking them as static in the Editor.
At first I tried to mark the prefab (the one I was instantiating at runtime) as static, assuming it would be equal to have all the objects marked as static already in the Editor. BIG MISTAKE!
Then I found out about the following command: StaticBatchingUtility.Combine. It basically allows you to combine meshes generated at runtime preparing them for static batching, something like marking the meshes as “static” at runtime. After instantiating all objects, simply call the Combine method with a reference to the objects parent:
- 100 meshes instantiated from a prefab: 6 Draw Calls / 100 Batched Draw Calls (IMAGE4);
If you cannot use Static batching, another option I found out while researching this topic was GPU Instancing:
“Use GPU Instancing to draw (or render) multiple copies of the same Mesh at once, using a small number of draw calls. “
“GPU Instancing only renders identical Meshes with each draw call, but each instance can have different parameters (for example, color or scale) to add variation and reduce the appearance of repetition.”
Let’s suppose, for example, that the rocks in the last example were not static. Since they are copies of the same Mesh, we could reduce the number of draw calls by enabling the GPU Instancing option in the Material (IMAGE5).
- 100 dynamic meshes instantiated from a prefab: 2 Draw Calls / 100 Batched Draw Calls;
These were just a couple of examples about my experience with Draw Call batching. There is still a lot more to cover on this topic. To read more about batching and all it’s rules please read the following post.
Happy Coding 🙂