Romanian IT companies, a smart choice for software outsourcingAugust 23, 2017
This tutorial presents DrawCall reduction, a basic optimization concept in Unity 3D: A DrawCall is a command that tells the GPU to render a set of vertices as triangles with a certain state (shader, blend state, etc.). The underlying assumption in the entire article is that you have some basic notions of Unity, such as: the definition of a GameObject, how to import it and add it to a scene. We will be using a rather large scene: a city with lots of low-poly objects (roads, vehicles or, buildings).
The Performance Profiler.
DrawCalls need to be measured somehow, and for that we will be using the Performance Profiler, which can be found in the top-menu (Window→ Profiler or Ctrl+7).
The Profiler windows has a few profiler-tabs loaded by default, such as: CPU Usage, Rendering, Memory, Audio, Video, Physics, Physics (2D), Network Messages, Network Operations, UI, UI Details and Global Illumination. We will be focusing on the Rendering tab, which is used to evaluate all DrawCalls. Our main objective is to reduce the DrawCalls and Batched DrawCalls (Dynamic and Static).
The 2nd half of the Profiler screen shows actual data for the graph drawn. Our current scene has every possible optimization turned off, so you can see the difference step by step, while performance increases
This is our scene … 3854 draw calls for a few objects barely more than a cube… !? Well, it kind of makes sence the way this project is currently setup. There are 3852 renderers (roads, buildings, props etc), each of them with one Unity-Standard material with an Albedo map.
The scene is simple: there are 3852 renderers (roads, buildings, props, etc.), each of them with one Unity-Standard material containing an Albedo map. 3854 DrawCalls (or a few objects that are slightly more complex than a cube is way too much for an acceptable framerate.
The following steps help you to start reducing the number of DrawCalls
- Enable Static Batching (Edit→ Project Settings→ Player→Other settings). Static Batching bakes your models into one huge mesh, where applicable. All the 100 models get rendered in a single Draw Call! Keep in mind that only static objects are affected by the batching process. The current scene doesn’t have any GameObjects marked as static. Select and mark as static all the objects that cannot/should not move under any circumstance. We have marked all the scene as static since we won’t be moving anything (yet). The change in the number of DrawCalls is quite noticeable: from 3854 down to 1005.
- Dynamic batching (Edit→ Project Settings→ Player→Other settings). This feature batches non-static objects that meet the less than 900 vertex attributes criterion. For instance: if your shader uses Vertex Position, Normal and single UV, only 300 verts will be batched. On the other hand, if your shader uses Vertex Position, Normal, UV0, UV1 and Tangent, only 180 verts will be batched.
- Render Path. Choosing the correct render path for your scene can make a big difference in the number of DrawCalls. By default, a new Unity project has the default Render Path set to Forward.
- In Forward rendering, the highest Intensity LightSource that affects an object is set to per-pixel lit mode, followed by up to 4 lights that are calculated per vertex. All the rest are computed as Spherical Harmonics ( SH).
- In Deferred rendering, there is no limit on the number of lights that can affect a GameObject. All lights are evaluated per pixel and all lights can have cookies and shadows. The lighting is proportional to the number of pixels the light shines on, regardless of the scene complexity.
- Well, what does all of this mean? Big scenes with lots of light sources tend to behave better in Deferred lighting.
- When using Deferred lighting, mobile devices have limited support, the orthographic camera support becomes unavailable, received shadows cannot be turned off and no more than 4 culling masks can be used (If more than 4 culling masks are used, graphical artefacts may occur).
In our “default” scene, we have switched the rendering path do Deferred since it is geometrically “complex”. The result is a drop from 1005 down to 170 DrawCalls!
Forward rendering tends to work faster and better for small dynamic objects when using Spherical Harmonics (SH). Keep in mind that your LightSource needs to have the RenderMode set to Not Important. We should mention that SH lighting is not local. Point or spot lights tend to look wrong close to some surfaces, SH lighting has very low frequency, which means you can’t have sharp lighting transitions and the transitions only affect the diffuse lighting (too low for specular highlights)
(Note: shadows aren’t visible in the play window since shadows have a min/max rendering distance. Since our camera is quite far away from the center of the scene, the shadow drawing distance is only visible in the editor windows, closer to an object). This can be changed from Edit→ Project Settings→Quality→Shadow Distance; for this scene, it’s been changed from 150 to 1500. This did increase the DrawCalls back to 718, but we’re a long way from finishing the optimizations).
- Baking lightmaps. This is the process of generating a specific map to your scene, which has all the shadows/light data included. Only static objects can be lightmapped, so make sure you’ve marked your static objects accordingly. If done properly, this will drastically decrease the number of DrawCalls, since objects already have their “shadows baked” over the textures and no longer need calculations for them.
- The light needs a few settings; the main directional light must have the Mode set to Baked.
- Make sure you have marked your static objects as static (floors, walls buildings, etc.).
- Open the Lighting panel (Window→ Lighting→ Settings). Here you will find a panel, with quite a lot of options for environment lighting, reflections, realtime lighting etc. For our demo scene, we have chosen the following settings:
- Realtime Global Illumination
- Baked Global Illumination (Substractive
- Indirect Resolution 1 (texels per unit)
- Lightmap Padding 2 (texels)
- Lightmap Size 1024
- Compressed Lightmaps
- Ambient Occlusion
- Max distance 1
- Indirect Contribution 1
- Direct Contribution 0
- Final Gather
- Directional Mode: Non-Directional
- Indirect Intensity 1
- Albedo Boost 2
- These settings should be enough for a basic lightbake. Click the Generate Lighting button and wait… Depending on your scene complexity and number of lights, this may take a while. These settings for our scene had a significant impact on our scene. From 718 DrawCalls, the scene went down to 427 draw calls. At this point you can actually disable the directional light source and notice no difference in the gameplay window. For this scene, the bake lasted nearly 4 hours with 543.7k verts, so be advised to either have patience or reduce your quality settings. The light baker (called Beast) did quite a lot of work. Textures with shadow positions have been mapped to all the static objects. As a small reference, our scene generated 82 textures (1024×1024 BC6H).with these settings.
- Occlusion culling. This is the feature that disables the rendering of objects that are obscured (occluded) by other objects. The best example to understand the feature would be a box behind a wall. Since you cannot see it, there is no reason for it to be rendered. The Occlusion needs to be baked in Unity. To start using Occlusion Culling, we will:
- Turn on Occlusion Culling for the camera (in the camera’s Inspector panel)
- Open the Occlusion Culling panel (Window-> Occlusion Culling). From the scene, select all the static renderers (the same ones that have been included in the lightbake). The default settings are usually enough for a proper bake.
There may be some scenes with very small or very big objects that require different settings. The 2nd panel , (“Bake”), has 3 parameters: Smallest Occluder, Smallest Hole and Backface Threshold. Adjust the Smallest Occluder and Smallest Hole until you have bake visualizations that include the objects The dimension of the Baked Occlusion Cells must be as close as the encapsulated dimension of the object. Setting up and baking proper Occlusion Culling data can reduce the amount of draw calls several times. The following image shows the visibility lanes from the camera (green), the meshes included in the visible bake probes and the camera volume (yellow). As you can tell, the invisible part of the city, which is bigger than the visible one, isn’t even loaded anymore.
- The following animation shows a down-street view with DrawCalls ranging from ~160 to ~260. Why? Only the visible geometry is rendered.
This has been the Introduction to Unity3D optimizations. Applying a few basic concepts was enough to reduce the total number of draw calls from 3854 to 260.
What does this actually mean? With a constant 200 DrawCall rate, you can use the scene on a ~4 year old Android with a rather smooth framerate for start. More in-depth optimization tutorials will arrive soon. Check our blog!.
If you want to learn more about our company visit our website or send us your inquiry here.
This tutorial on Unity 3D optimization explains how you can reduce he total number of draw calls in a large scene from 3854 to 260.