Home / Article / Draw call batching in Unity: Scene Optimization

Draw call batching in Unity: Scene Optimization

/
/
/
2543 Views

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:

  • Keeping the number of materials per mesh at a minimum (check here);

  • Using LODs to reduce the number of triangles being rendered at a given moment;  

Even so, the performance was still subpar.

DRAW CALLS

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.

You can check the number of draw calls in the Rendering Profiler (IMAGE1).

Check this explanation for why draw calls were removed from the Rendering Statistics Window.

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.

Draw call batching in Unity

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.

Draw call batching in Unity

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.

STATIC BATCHING

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.

Draw call batching in Unity

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!

  • 100 meshes marked as static in the Editor: 2 Draw Calls / 100 Batched Draw Calls (IMAGE2);
  • 100 meshes instantiated from a prefab marked as static:101 Draw Calls / 0 Batched Draw Calls (IMAGE3);
Note: In both tests I used the exact same mesh and single material.

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);

GPU INSTANCING

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;

Draw call batching in Unity

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 🙂

 

  • Facebook
  • Twitter
  • Google+
  • Linkedin
  • Pinterest
  • Reddit

1 Comments

  1. Haha! This is so funny! I have gone over pretty much the same exact steps you’ve gone through in your article: the shared materials, the same references to external sources in your article, even marking the prefabs static, haha. It was amusing to see how we both went through the same.

    I enjoyed reading your article. It’s very well written and you meticulously link references and attach relevant pictures, which is great. Congrats.

    Now the difference in our journey, is I’m currently stuck with static batching utility. I’m using the profiler to check if it works but I see no static batching in there. I have yet to try the frame debugger, which I was already informed about 😉 I’m simply adding game objects as I instantiate them to a list, the array of which I pass onto the second overload of utility along with an empty I set on the scene to act as a parent. But I do this after each instantiation, expecting it will be added to the batch, which as I have recently read is not the case. It will create a new batch. That wouldn’t be a problem just for testing purposes. But yeah, as I said I got no static batching in the profiler. So I’m wondering the following and i hope you can help with:
    1. Are objects which were previously included in a batch by the utility, no longer able to be included in a new batch?
    2. ‎Do the game objects passed to the utility need to be actual children of the parent passed in too? I.e. There’s a need to parent them to that said parent after instantiation?

    Regarding the GPU instancing, I had before read quick over it and quickly disregarded it as “this is not the batching I’m after”. But in your article it looks like it’s as useful as static batching without its caveats at the expense of a marginally worse optimisation. If its as simple as checkmarking that option in material, I’ll try it for sure. I’ll read the doc you referenced anyways.

    Now a few problems I encountered in your article I should point out:
    1. Image 4 links to image 3
    2. ‎There’s no GameObject variable named “gameObject” in your code, which could have helped me elucidate my second question.
    3. ‎The last video for GPU instancing says 2 draw calls, not 6?

    Anyways it was a pleasure to read such a well written article with so many references and pictures. Congrats again.

Talk to me :)
%d bloggers like this: