Are you planning to optimize your game made with Unity and get the best performance? In this Complete Unity Optimization Guide, I will show you how to increase performance in Unity correctly, so your game will run fast & stable.
Here I will consider optimization tools provided by Unity Game Engine and also by Indie Developers (Publishers) in Unity Asset Store — Top Unity Assets (Tools) for Unity Optimization.
I will also deep into details of common performance issues in video games and find solutions for them: optimization of code and assets, tips & tricks.
- 1. Main Optimization Cycle in Unity
- 2. Types of Performance Issues
- 3. Profiling
- 4. Benchmarking
- 5. Unity Optimization: Tips & Tricks
- 6. Conclusion
Main Optimization Cycle in Unity
Unity Optimization refers to the process of making your game run better. Usually the main reason for optimization is to make the gameplay smoother, or to make the game more available to a wider audience so the game runs better on lower end devices.
Next will be presented common optimization cycle, which you need to go sequentially every time when noticeably low FPS suddenly occurs in your game after next stage of development:
- If your game works without lags and freezes on target platforms, then the game does not need to be optimized. Do more important things like new levels in current game.
Optimization for the sake of optimization is a waste of time.
- If you have lags and freezes then the starting point is Profiling (firstly with Unity Profiler) to detect what kind of performance issues Unity Project has.
- After the optimization tasks have been determined and before the process of optimization is started, it is advisable to take some time and consider which area of optimization is the priority. This is done by comparing the approximated time each specific implementing of optimization would take and a performance gain in result.
Resulting ordered list of optimization tasks will work as a guideline for the entire process of optimization starting with the most effective action. This also helps to gain the most performance with the least efforts. Follow this list until the game on target platforms has smooth gameplay and stable frame rate.
- If the game is executing excellent without visible problems and the list of optimization tasks is not empty then forget further optimization process.
- If the game still has problems then project should be profiled and the data stored. Next stored data can be compared with a previous data allowing for closer inspection of the state of the whole optimization process.
Repeated profiling allows you to understand what happened after the completion of a single optimization task, whether this procedure brought a performance improvement or not.
Types of Performance Issues
You can Optimize Unity Game in different ways depending on problem nature. Performance problems can be divided into several types depending on their nature.
Spike is a sudden drop in the frame rate of a game. This is noticed when a game suddenly stops and doesn’t move for a noticeable time. This can break the immersion of the player or cause him to make a mistake he wouldn’t have otherwise made. Spikes can be seen as a high points on Profiler Graph.
Spikes are mainly caused by complex calculations or difficult operations performed during a single frame. Annoying Spikes are a problem in high-intensity games which need a stable frame rate and high control over the game to feel good, such as driving or shooting games (FPS).
Spikes of Garbage Collector
Garbage collection spikes are frame rate drops specifically caused by the garbage collection system of Unity. These can be huge frame rate spikes and are easily noticed while playing the game. The spikes happen when the memory garbage limits met and the collection runs, cleaning up the unnecessary objects from the memory. Their frequency is mandated by how much garbage the game generates each frame. The frequency can be slowed down by generating less garbage during runtime.
The only way of preventing these spikes completely is to generate no garbage during runtime. This is a huge undertaking and must be considered from the very beginning of a project.
Every-frame costs are the calculations and operations that are run every single frame. These can be, for example, physics calculations, running AI behavior or handling animations of characters.
Every-frame costs slow down the general frame rate of the game. They are the little things that slow the game down and make it feel less fluid. If a game just generally runs poorly, this is the area that needs work.
Loading time refers to how long the game takes to load. This includes the first load when the game is opened, and loading that happens during runtime, for example between scenes.
While not usually a major issue, having extremely long loading times or having loading screens appear far too often can negatively affect the user experience.
To reduce the length of loading screens, consider splitting up the work done during them. This can mean preloading assets beforehand to reduce the number of objects that need to be loaded during loading screen, or reducing the complexity of loaded scenes.
In an open world game, where many objects need to be loaded during runtime, a method of recycling or streaming assets can be implemented. In “Inside” Game a small part of every frame is dedicated to loading and unloading assets. This allows the whole four-hour experience to be played through with just a single initial loading screen.
RAM (Random Access Memory) is the memory on which a game is loaded while running. RAM stores everything needed by a game during runtime.
VRAM (Video Random Access Memory) is on a graphics card and is intended for graphical effects. VRAM is the storage used to store textures and models drawn by the graphics card.
RAM is what your CPU uses, while VRAM is what your GPU uses. Either or both might become a bottleneck on a project running with unoptimized or simply with far too many assets. When there is not enough memory, stuttering may occur.
To reduce the amount of memory needed by your project, consider next:
- Reduce the complexity of objects and the resolution of textures;
- Make less unique assets in the project;
- Use Object Pools.
To know what is the issue with the performance of a project, profiling is needed. Profiling assists in understanding the unique issues found in a project, and should always be the starting point for optimizing a project.
Process of Profiling means reading the performance data of the project and finding out where the performance issues are located. From this data bottlenecks can be found.
Unity provides some different tools for profiling:
- Profile Analyzer (package),
- Frame Debugger,
- Memory Profiler (package),
- Physics Debugger,
- UIElements Debugger.
This chapter goes through each these tools, analyses them and gives information on how to use them.
Connection to Profiler
Unity Profiler and Frame Debugger can both be used to profile a project currently running on the same editor. However, the editor affects the performance of the project and the profiling information can be inaccurate. For this reason, if accurate profiling data is needed, a separate build should be created for profiling purposes.
Making a build for profiling
To make a build which can be profiled, turn on options “Development build” and “Autoconnect Profiler” in Build Setting before making a build. This allows Unity Editor to find the build and for the Profiler to connect to it.
Profiling External Devices
To get accurate profiling data, the build should be profiled on end user device. When you optimize Unity Game for Mobiles then Profiling on a powerful PC gives little insight on how a user will experience the game.
The easiest way to connect the Profiler to an external device is to have the two devices share the same local network. If there is a device running a development build of the project, it should be selected in the “Editor” dropdown menu of Unity Profiler, Frame Debugger or Console.
Once the correct device has been selected, the Profiler should automatically start profiling it. Profiling on an external device still affects the performance, but the effect is less than when profiling on the same device.
Profiling of Mobile Devices
Modern mobile devices are powerful but often lack an active cooling system. Because of this, the heat of the device affects its performance highly.
On mobile devices, the performance of a game can be directly compared to the heat level of the device. When the device is just turned on and the application is launched, the performance is at its best. Once the device has warmed up the performance starts to fall as the device tries to cool itself. And again, once the device has cooled down enough, performance is improved.
Mobile devices can be profiled through USB cable.
To effectively profile a mobile device, it should be warmed up first. After the build has run for 10 minutes, and the device has warmed up properly, the profiling information becomes more accurate and more applicable to an actual user experience.
The Unity Profiler is the main tool for profiling Unity Projects. It provides much information on the different performance areas of a project:
- CPU & GPU Usage;
- Rendering Info;
- Memory usage;
- Audio & Video;
- Physics (2D & 3D);
- Network Messages & Operations;
- Global Illumination.
This tool is the beginning of every optimization process on Unity. From information provided by it, bottlenecks are found and from these bottlenecks the first optimization tasks are created. Learning to use it helps to understand the work of Unity Engine and Current Project.
Using the Profiler
Profiler Window can be opened through the “Window > Analysis” tab of Unity Editor, or by pressing the shortcut CTRL+7 on Windows or CMD+7 on Mac OS X.
Once Unity Profiler has been connected to a running build and the record button has been pressed, profiling information will begin to be collected and displayed in the window. To stop or pause Unity Profiler simply toggle the Record button.
On the left side of the window the different profiling categories can be seen, and more can be added from the “Add Profiler” dropdown menu at the top.
When profiling, information on each active category appears on the right side of the window. Each slice of the information presents a single frame. On the CPU usage category, execution time the frame is presented by the height of the slice. Frames can be switched by pressing the arrow keys on the window, or by clicking a slice with the mouse.
Information on the selected category and target frame appears at the bottom of the screen.
Next to the function name different values are shown. The values provide information on the performance of the function in the selected frame. These values are the percentage of the total CPU time spend on this function, the number of times the function is called in the chosen frame, how much garbage the function generated and the time it took to finish the function in milliseconds (total time including inner functions (Time ms) and without them (Self ms)).
If these functions themselves call other functions, a small arrow is shown next to the name of the function. Clicking this arrow opens a function hierarchy that shows how the full time of the function is split among the functions called by it.
The height of the lines shown on the upper half of the window tells how long that frame took to finish. If these lines have noticeable spikes in them, there has been a frame rate drop. Hunting down what function caused the spike and optimizing it is a way of making the frame rate and gameplay experience smoother.
Hierarchy Raw Mode
Hierarchy Raw Mode will separate global Unity function calls into individual lines. This will tend to make Profiled Data more difficult to read, but may helpful if we are trying to:
- understand how Unity Engine works,
- to count how many times a particular global method has been invoked,
- to determine if one of these calls is costing more CPU/Memory than expected.
You can also use Timeline Mode of Profiled Data. It’s just another way to show the data. You have in quick glance the whole situation of a frame in order of occurrence for all threads. So events showed at the left are the first that happened and the right are the last.
Timeline Mode can be useful since it show more details than the normal version, and if you will click on some block then Timeline will tell you what Game Object connected to this block (e.g., for Skinned Mesh Renderer or Animator).
It is useful to discover some bottlenecks, maybe you can notice the rendering is taking very little but it’s waiting a lot for CPU to send commands. You have the same data on Hierarchy Mode of Profiler but for some cases it’s easier to spot certain issues as you can graphically compare two items. Hierarchy Mode of Profiler is better when you are focusing on specific parts, the Timeline Mode it’s better for a general picture.
Unity Profile Analyzer
It complements the Unity Profiler’s single-frame analysis by adding the ability to analyze multiple frames at once. This is useful when it’s important to have a wider view of your performance, such as:
- upgrading Unity versions,
- testing optimization benefits,
- tracking performance as part of your development cycle.
It analyzes CPU frame and marker data that is pulled from the active set of frames currently loaded in the Unity Profiler or loaded from a previously saved Profile Analyzer session. The analyzed CPU frame and marker data gets summarized and graphed using histograms, and box and whisker plots, which compliment ordered list of activity for each marker, including:
- instance count,
- which frame the marker first appeared in.
Getting Started with Profile Analyzer
Use Unity 2018.3 or higher:
- Open your project in Unity and go to “Window > Package Manager”.
- If you don’t see “Profile Analyzer” in package list then open the “Advanced” drop-down and make sure “Show Preview Packages” is set.
- Select the “Profile Analyzer” & click “Install”.
- Open “Window > Analysis > Profile Analyzer”.
Unity Frame Debugger
Draw Call — single task performed on the GPU used to draw the screen. A single frame consists of multiple Draw Calls. Modern 3D games with complex graphical assets and effects may need thousands of Draw Calls to render the screen each frame.
Reducing the number of Draw Calls is a simple method to reduce the workload of the GPU.
To get information on what the GPU spends its Draw Calls on, Frame Debugger can be used. By using the tool, more insight on the rendering process can be got. It also is used to find the areas where the most Draw Calls are spent, and where optimization is required.
Frame Debugger Window can be opened through the “Window > Analysis” tab of Unity Editor.
Frame Debugger is connected the same way as Unity Profiler and can easily be used either inside Unity Editor, debugging “Game” Window, or on a separate device.
Debugging a build on the same device as the editor can cause some issues like window freeze.
Once Frame Debugger has been activated, it freezes the profiled build and collects data on the frozen frame. Once the data has been collected, information on every Draw Call used to render that frame appears on the left side of the window. Draw Calls are organized into hierarchies such as drawing and image effects. Next to the hierarchy name is shown the amount of Draw Calls it took to finish rendering that area.
By switching between Draw Calls on Frame Debugger Window, changes can be seen on the frozen frame of profiled build (or in the “Game” View in Unity Editor if you are profiling without build) as the debugger shows what was drawn by each call.
By clicking a single Draw Call, more information on that specific call appears on the right side of the window. Switching between Draw Calls and watching the frozen screen change can give enough of an idea on where optimization is required.
If the most of Draw Calls are spent drawing a single object or a character in the scene, work on that object might be required.
Unity Memory Profiler v2
Use the Memory Profiler (package) to identify potential areas in your Unity project (and the Unity Editor itself) where you can reduce memory usage. For example, use the Memory Profiler to capture snapshot (represents the whole memory usage of the project), inspect, and compare 2 memory snapshots.
The Memory Profiler is unified solution allowing you to profile both small projects on mobile devices and big AAA projects on high-end machines.
The Memory Profiler is a window in Unity Editor with an overview of native and managed memory allocations and can help you detect memory leaks and fragmentation with some different tool:
- Tree Map;
- Memory Map (Sequential Visual Representation of Memory Blocks);
- Tables (Lists of Objects with Filtering);
- Overview or Connectome (Visual Graph of Connected Objects).
Getting Started with Memory Profiler
Use Unity 2018.3 or higher:
- Open your project in Unity and go to “Window > Package Manager”.
- If you don’t see “Memory Profile” in package list then open the “Advanced” drop-down and make sure “Show Preview Packages” is set.
- Select the “Memory Profiler” & click “Install”.
- Open “Window > Analysis > Memory Profiler”.
To use it, a build of the profiled project is needed because separate build has more accurate data in comparison with running project in Unity Editor. Profiler must be connected to the build when the build is running and Memory Profiler is open in Unity Editor.
Once the window has been opened, the “Capture Player” button can be pressed. Wait for Problem Moment of Game and Capture Second Snapshot.
Once the snapshots have been captured, they show up on the Memory Profiler and the profiled build is no longer needed. Next you can investigate each snapshot with different tools and compare them with “Diff” button.
Tree Map — Visual Representation of Memory like Programs for Space Analyzing of Hard Disk.
Different colored squares indicate memory taken by different object types, for example textures or meshes. By clicking a square, it is divided further into smaller squares representing individual objects. More information on these individual objects can be seen at the bottom of window by clicking them, and even a reference on what is using the object can be seen.
Just by looking at the memory usage snapshot split up by object type tells which objects use the most memory. Tracking down the individual big spenders and working on those can quickly bring down the memory used by the project.
Unity Physics Debugger
Physics Debugger — tool for quickly inspecting the Collider geometry in your Scene, and profile common physics-based scenarios. It provides a visualization of which Game Objects should and should not collide with each other. This is particularly useful when there are many Colliders in your Scene, or if the Render and Collision Meshes are out of sync.
You can use Physics Debug to profile and troubleshoot physics activity in your game.
You can customize which types of Colliders or Rigidbody components you can see in the visualizer, to help you find the source of activity.
Show active Rigidbody components only
To see only the Rigidbody components that are active and therefore using CPU/GPU resources, tick “Hide Static Colliders” and “Hide Sleeping Bodies”.
Show non-convex Mesh Colliders only
Non-convex (triangle-based) Mesh Colliders tend to generate the most contacts (comparing to colliders of simple forms) when their attached Rigidbody components are very near a collision with another Rigidbody or Collider.
To visualize only the non-convex Mesh Colliders, set the window to Show Selected Items mode, click the “Select None” button, then tick the “Show Mesh Colliders (concave)” checkbox.
Unity UIElements Debugger
UIElements Debugger — tool for debugging of Unity Editor Extensions that was written with UIElements, a new retained-mode GUI system for extending the Editor in Unity 2019 and higher. It is useful for Asset Store Publishers who develop Editor Tools like “Bolt“.
When you are working with UIElements, UIElements Debugger helps you visualize the hierarchy tree, properties and USS (Unity Style Sheets) files applied to each element.
Benchmarking refers to collecting data from a project to measure its performance. This is different from profiling in that the benchmarking data should be as close to the performance of the final product as possible. Because of this the data should not be collected by using the Unity Profiling tools, since just running the tool has an impact on the performance.
Benchmarks should be taken on the same device as the final product is meant to run on. A single device can be good enough, but for getting many data, multiple devices of different performance capabilities should be benchmarked on. This way gives more information of the performance on low- mid- and high-tier devices.
When benchmarking the frame rate of the project, a separate script can be used to track the frame rate instead of using the profiling tool.
To keep the frame rate data accurate between benchmarks, the gameplay should be reproduced as accurately as possible. The best way to handle this is to have a script that plays the game the same way every time. When the gameplay is identical between benchmarks, they can be compared to each other and Unity Optimization Process can easily be monitored.
You can create a script having a single “Hub” Scene from which the other scenes were launched. The script would start the project on the lowest quality setting in the “Hub” Scene. From this scene it would open and play through the gameplay scenes, always returning to “Hub” Scene after a scene was completed. After finishing all the gameplay scenes, the quality level would be increased and playing through the scenes started again.
The automatic system would also collect performance information while running. This information was then stored on text files created by the script. These files were saved on the hard drive of the device and organized by scene and quality level. You can run script on two or more devices over the night and fresh data could be gathered on the next morning.
Unity Assets for Benchmarking
Advanced FPS Counter — Simple and flexible in-game frames per second counter with advanced features, made to let you monitor stats of your game right on the target device.
Show Advanced Stats with 3 tools:
- FPS Counter;
- Memory Counter;
- Device Information Counter.
Also, take a look on These Awesome Tools:
Unity Optimization: Tips & Tricks
Every Unity Project is unique and has its own unique issues. However, there is a many of well-known performance related problems associated with Unity Engine itself. This chapter goes through several of the known performance issues in Unity and provides information on avoiding them.
The article is updating regularly. Please, share your experiences in the comments to extend this manual.
Code & Scripting
Yes, C# Events are not as convenient to use as Unity Events because Unity Events can be shown in Unity Editor at least, but Unity Events have bad performance in most cases. You can read research about comparing C# & Unity Events. Moreover, assigning methods to events with Unity Editor is a bad practice in terms of code because debugging a project by another person in this case becomes hard.
Don’t combine 2 strings in Update () Function because every time it creates garbage by memory allocation.
Example with UI:
scoreText.text = "Score: " + currentScore.ToString();
Instead of it you need to create 2 separate text objects (TextMesh Pro, of course):
- For updating the text with current score.
- For caching “Score: ” text.
Unity UI (UGUI)
The most common optimization issue with new Unity projects is the UI system.
The basic element of the Unity UI is a canvas. These canvases then house all other elements of the UI, be it text or images. The issue with these canvases is that when a single UI element on a canvas changes, the whole canvas is marked dirty and needs to be redrawn.
Often with developers who haven’t had much experience with Unity, all the UI in the game is set up under a single canvas. This can mean that a complex menu system, even while hidden, needs to be redrawn every frame because it shares the canvas with a timer, for example.
Multiple canvases is solution to reduce CPU Usage.
Optimize Unity UI with common method by splitting UI elements to separate canvases by update frequency. Elements that are updated regularly, like Timers or Mana Orbs, under a single canvas and static elements under another one. This fixes the issue of having to update every single UI element in every frame.
UI is a bottleneck especially on mobile devices. Transparent graphics are not handled well by mobile graphics cards, and all the UI elements in Unity are considered transparent. For this reason, splitting elements between multiple canvases can have a huge effect on the performance of the project.
Unity Text vs TextMesh Pro
Unity Text Component (UI) & Unity 3D Text (Text Mesh component) have bad performance.
Optimize Text with TextMesh Pro instead of default Unity Texts.
Getting Started with TextMesh Pro
Use Unity 2018.3 or higher:
- Open your project in Unity and go to “Window > Package Manager”.
- Select the “TextMesh Pro” & click “Install”.
- Right Click on “Hierarchy View”:
- “3D Object > TextMeshPro — Text” for texts in 3D space.
- “UI > TextMeshPro — Text” for HUD Texts with Canvases.
- Making the most of TextMesh Pro;
- New TextMesh PRO for Legacy Projects that use previous versions of Text Mesh Pro.
Static Text vs. Texture
Your asset can have a texture that must contain static text (e.g., Road Signs). Yes, you can use TextMesh Pro to create a text over the texture, but this can be a bad idea, since UI elements that move and resize in 3D space as the player moves cause performance load. In this case signs can be replaced with transparent textures so Texture of Road Sign must contain image & text now.
Memory Management is done automatically in Unity. When deallocating the memory, Unity Engine often causes a huge frame rate drops. These drops are especially noticeable in fast reaction games, where stable frame rate is important.
Many Unity Functions generate a garbage, making it almost impossible to fix the garbage collection spikes, without changing the source code of the engine which is available for purchasing exclusively to Pro and Enterprise customers of Unity Engine.
Following are listed several methods of possible fixing of garbage collection issues.
Garbage Collector (GC)
All memory deallocation in Unity is done by Garbage Collector. When enough memory has been allocated, the garbage collector automatically clears unused objects from the memory for single frame. This is referred to as running the garbage collector, and always causes a frame rate drop.
Unity 2019.1 with .NET 4x Equivalent introduces to Incremental Garbage Collection which splits GC operation over number of frames. So just turn it on in “Player Settings > Other > Use Incremental GC”.
There are multiple ways to reduce the performance impact and the frequency of running the garbage collector. The simplest is to generate less garbage during runtime. When profiling the CPU, the amount of garbage generated by each function can be seen. Then you can change these functions to generate less garbage.
Manual Garbage Collection
One way to solve the issue is to manually run the garbage collection when the gameplay is less active, and the frame rate drop won’t affect the user experience. For example, during loading screens or during more peaceful (passive) periods of gameplay.
This still caused a noticeable frame rate drop, but at a time when it did not negatively affect the gameplay.
I don’t recommend to use Manual Garbage Collection because is a last measure — in 95% of cases you will success without it. First of all, you should deal with all the bottlenecks of your project.
Caching is a technique of reusing object. Caching refers to storing something that is often needed as a variable instead of calculating or fetching it whenever it is needed. For example, this can mean storing the camera as a variable in a script instead of calling “Camera.Main” whenever the camera is needed.
Unity APIs such as “Camera.Main” or “gameObject.transform” causes a little overhead. Camera.Main is a shortcut for finding a camera object from the scene with the tag Main. This value is never stored so every time the function is called, a search operation is performed. All this overhead can be fixed by storing a reference to the object as a variable after the first call of the function.
For example, no need of creating the same local variable every frame in Update () Function:
string s = "Text";
text.text = s;
Instead of it you can assign the variable when declare a variable as a field of class, and initialize it here or in Awake () or Start () functions if it makes sense in your case.
Object Pooling is also Caching, but in this case we are dealing with several objects of the same type (e.g. with the same script components) or same prefabs. Read more about Object Pool.
You can reduce the resolution by adjusting “Max Size”. “Max Size” setting is non-destructive meaning it does not change the original texture size — it only adjusts the imported size. E.g, set max size to 1024 instead of 2048 press apply to save the changes.
Normal Maps can usually be set to a lower resolution than Albedo or Color textures as the lower resolution is less noticeable in case of Normal Maps.
Common pitfall of texture memory usage is using “Read/Write Enabled” option on a texture that does not need it. If the option is enabled on a texture, it will stay in CPU memory even after being sent to the GPU (which you can’t access directly without a performance penalty). This can double texture memory in most cases.
The option is needed to enable access to the Texture data from script functions (such as Texture2D.SetPixels, Texture2D.GetPixels and other Texture2D functions). Note that a copy of the Texture data is made, doubling the amount of memory required for Texture Assets, so only use this property if necessary. This is only valid for uncompressed and DXT compressed Textures; other types of compressed textures cannot be read from. This property is disabled by default.
Audio Clip Settings
PCM — the most memory intensive format. PCM compression is a lossless format useful for short sounds as the CPU processing required to decode PCM is very low. For long clips you can use lossy Vorbis compression format. It will help us save the space in memory and on disk.
By reducing the quality setting with PCM we can trade audio quality for even more memory savings.
Unity pre-allocates a minimum amount of memory on startup. This is called the heap. When the heap is filled, new memory must be allocated and this can be performance intensive.
One way to avoid the issue is to manually allocate a large amount of memory on startup and then empty that memory. This amount should be close to the measured maximum amount of memory needed by the program. This forces Unity to expand the heap on startup and removes the performance issues caused by expanding the heap during runtime.
Reducing Draw Calls
The simplest way to optimize Game Objects is to remove objects drawn. This can be done by removing not critical (redundant) objects from the scene.
Static Game Objects and Batching
Game objects can be marked as static in the editor. This mark tells the engine that the marked object will never move. This allows the engine to use a method called batching to render it.
Batching happens when the engine draws multiple objects on a single Draw Call.
Work Conditions for Static Batching:
- the objects must be marked as static & share the same material;
- turn on “Static Batching” option in Player Settings.
By using the Frame Debugger that was considered in the first part of this article, you can see when batching has been used to reduce the number of Draw Calls. The debugger will also tell you why a Draw Call was not rendered in the same batch as the previous one.
Frustum Culling removes objects outside the camera view from the rendering process, saving Draw Calls without affecting the user experience. In Unity, this is an automated process and is always on, requiring no setup from the developer.
Occlusion Culling works alongside Frustum Culling as an additional method of reducing Draw Calls. While Frustum Culling hides objects outside the view, occlusion culling aims to minimize draw calls in view. This is achieved by removing objects hidden behind other objects from the rendering process.
Unlike frustum culling, an automated process, the occlusion culling must be set up and baked beforehand in Unity Editor. Unity has provided an object called “Occlusion Culling Area”, which can be created from the “Occlusion Culling” window. The window can be found under the “Window > Rendering” menu item. These objects need to be placed in the world before the data can be baked. This can be done manually or through scripts.
Game Objects with the tags “Occluder Static” and “Occludee Static” are included in the Occlusion Culling process. These are on by default in Static Game Objects. When an object marked with “Occluder” tag hides an object marked with “Occludee” tag behind it from camera view, the “Occludee” object is removed from the rendering process.
The occlusion areas are to be set up such that the camera will never wander outside them. If the camera go outside the occlusion culling areas, no occlusion culling will be applied, causing errors such as invisible objects and holes in the terrain.
Once the occlusion areas have been set up, occlusion culling can be baked. This is done from “Occlusion” Window. Once completed, the effects of occlusion culling can be viewed by using the “Visualize” tab on the scene window. This allows for debugging by moving the camera and observing the changes in editor.
If pop-in of objects and other errors occur, there are few ways to solve the issue:
- The values provided in the occlusion window can be tweaked, affecting the precision of the culling, the size of the baked file and the time it takes to finish the baking process.
- Other way of adjusting the culling precision is to change the size and frequency of occlusion areas. Multiple small areas can be more precise compared to a single larger area, but can increase the baking time and the size of the baked occlusion data file.
- If there are still objects that are not correctly culled, they can be taken out of the occlusion process entirely by removing the Occludee Static tag from the game object.
LOD levels on 3D objects
Optimize Mesh Data with LODs (level of detail — system used on 3D objects to reduce their impact on the performance). It functions by replacing the high-quality 3D object with a similar object of lower quality when they get further away from the camera. Often the 3D object is replaced further with a 2D render of it and even removed completely.
LOD levels can be set up by adding the LOD component to Game Object. From the component the amount of levels and the level changing distance can be modified. The way Unity determines when the LOD level should be changed, is how much of the object is on the screen at a given time. This can cause weird behavior in objects partially hidden or underground, for example rocks. This method of reducing Draw Calls causes more work on the artists end since the lower quality objects need to be made by someone.
- Use the lowest LOD level of the terrain, losing some of the fidelity but removing the need for runtime calculations.
- Replace most of the trees in scenes with 2D images, reducing the workload of the foliage system without much affecting the user experience.
Just having many objects, especially terrains, in a single scene caused the performance of scene go down drastically.
You can use several ways to stream scenes:
- The initial scene was split into multiple smaller scenes, and they were loaded and unloaded when necessary (when player enters in portal, e.g.).
- Streaming the terrains inside the scene. Instead of having over 30 terrains always active in the scene, only 9 terrains around the player character would be active at once. For the rest of the terrains a boundary was set up that would trigger when the player would move inside it. When a boundary was triggered, system moves the already existing terrains in front of the player and replaces the data of the moved terrain by a new one. So no spawning was necessary, and instead the same 9 terrains were being recycled through the whole scene.
Convex Mesh Collider for Complex Objects
Unity allows you to use only convex mesh colliders because they have less performance load instead of non convex ones. In some cases, it is not what you need because the collider covers more space, than the object itself in some points. Use the following techniques to achieve accuracy of collider space :
- Create many simple forms of colliders and place them on Game Object manually.
- To quickly & automatically get a convex mesh collider for complex objects, use Technie Collider Creator (Asset Store).
Asset Hunter PRO — solution for cleaning your project from garbage and missing elements.
Consoles: Xbox & PlayStation
Optimize Unity Game with the thought in your head that each project is unique and the performance issues found in them are unique as well. Learning the usage of the tools provided helps immensely in finding and understanding these issues.
Unity Optimization Process should be started in a project lifetime as soon as there is something to optimize. This should be done before there are too much to change if big changes are needed. However, over-optimization can also be an issue.
Remember to not spend time optimizing what doesn’t provide much performance gain.
If performance issue can’t be measured, it’s not an issue.
When starting a project, take some time to consider some baseline practices for the assets created for the project:
- Do you really need those 2K textures for your mobile game?
- Does it really need these complicated calculations done every frame?
Changes to an already existing asset or code can be tedious and take a long time to do. Doing them before the project has grown too large to handle can save a lot of time.