Let’s talk about optimization, this time let’s go over draw calls!
What is a Draw Call?
Whenever we need to an object to appear on the screen, we need to make a call to the GPU to draw out this object to the screen. This call to the GPU is what the Draw Call is. As we can imagine, having hundreds of objects in a scene would also require hundreds of draw calls as well. Too many draw calls and we’ll run into performance issues.
Keep in mind, it isn’t as simple as one game object = one draw call. A draw call will be made for every material on an object. That means if we have an object with 4 different materials to help give it more depth, then it will have 4 different draw calls when rendering it to the screen.
One thing to point out is that Unity has named it’s draw calls differently and instead uses the term Batching. This is because Unity tries to optimize performance by grouping draw calls together so they can be drawn together instead of individually.
Now that we know some basic terminology, let’s discuss how to optimize draw calls.
What's a Good Metric for Drawcalls?
If we’re going to optimize something, it always helps to have a target. Again, we look towards Meta’s metrics as they have the highest market saturation with the Meta/Oculus Quest 2. According to Meta, there is actually a range of draw calls that we should aim for based upon what is currently happening in our scenes.
According to their website, found here, we should aim for about 80-600 draw calls depending on if our scene is performing busy or light simulations. So what does is mean to have busy or light simulations? Well luckily, Meta answers that for us too.
So there we have it! Multiplayer games with lots of actions fall under the busy simulation category, single-player fps games under medium and finally escape room and puzzle games are light simulations.
With that in mind, most developers should aim for between 200-600 draw calls unless they are creating a very busy multiplayer experience.
Visulization with Frame Debugger
it always helps to be able to see the problem and luckily, with the frame debugger, we can do that!
- Go to Window -> Analyisis -> Frame Debugger
So now that we can visualize the problem, let’s start looking at ways to reduce draw calls.
Shadow Ban
Depending on how much we have going on in a scene, we may want to consider just eliminating shadows. If we look at our frame debugger, we can see that shadows actually account for a decent amount of draw calls. Depending on our application, we might be able to get away with turning off shadows without ruining too much of the user experience.
- Go to Edit -> Project Settings -> Quality
- Locate Shadows and select Disable Shadows from the drop menu
With that small change, we can see that we went from 57 frames down to 20. That is a difference of almost 65% less frames! Although it isn’t ideal to lose shadows in our game, if they aren’t too important to the experience, it can be a massive improvement.
We can also simulate shadows by baking light ahead of time, but I’ll save that for when we go over light optimization in future videos.
Let’s move on to static batching!
Static Batching
One of the first things we can implement is a optimization method called Static Batching. Like the name implies, this method focuses on batching objects in the seem that are not intended to move. The Unity game engine can group these stationary objects together and render them by making it into a single mesh. Since they’re all grouped up into a single mesh, it will then only need a single draw call in order to produce it.
We’ll start by making sure static batching is turned on.
- Go to Edit -> Project Settings -> Player
- Other Settings
- Static Batching = True
- Other Settings
- Select an object in your scene that will be stationary and that you wish to mark as static
- In the top right of the Inspector
- Static = True
- In the top right of the Inspector
So what about batching objects that are moving?
Dynamic Batching
Dynamic batching will allow us to batch draw calls together if they meet a certain criteria. Those criteria are as follows.
- 300 vertices for the object being rendered.
- Have the same material as other objects.
- Have the exact same scale as other objects.
- Go to Edit -> Project Settings -> Player
- Other Settings
- Dynamic Batching = True
- Other Settings
GPU Instancing
GPU Instancing will allow us to perform draw calls on objects that are in motion, but our objects that are moving will need to have a few things in common before they can be considered for GPU Instancing.
- Objects must have the same Mesh
- Objects must have the same Material
- Select an object that has multiple appearances in the scene (Same mesh used multiple times)
- Make sure all of these objects also share the same mesh
- Expand the mesh that is being shared by all objects
- GPU Instancing = True
Now if we start up the scene, we can see that our batch calls have been reduced!
The way GPU instancing works is by gathering up a list of vertices and instance transforms. With all the positions, rotations and scales of all the copies of the same object, it then sends it in a single draw call to all be drawn.
Conclusion
So there we are! A few new tools in the toolbelt to help us reduce our draw calls and make our games even more efficient than before.
My shrimp brain is tired! I’ll see you in the next one!