Disable unnecessary Player or Quality settings
In the Player Settings, disable Auto Graphics API and remove additional graphics APIs that you don’t plan on supporting for each of your targeted platforms. This can prevent the generation of excessive shader variants. Disable the Target Architectures setting for older CPUs if your application is not supporting them.
Learn more about the Graphics API.
Switch to IL2CPP
Switching the scripting backend from Mono to IL2CPP (Intermediate Language to C++) can provide overall better runtime performance. However, it can also increase build times. Some developers prefer to use Mono locally for faster iteration, then switch to IL2CPP for build machines and/or release candidates. Refer to the Optimizing IL2CPP build times documentation for more on reducing your build times.
Note: By using this option, Unity converts IL code from scripts and assemblies to C++ before creating a native binary file (.exe, .apk, .xap) for your target platform.
Choose the right frame rate
Mobile projects must balance frame rates against battery life and thermal throttling. Think of the frames per second (fps).
Instead of pushing the limits of your device at 60 fps, consider running at 30 fps as a compromise. Note that Unity already defaults to 30 fps for mobile.
You can also adjust the frame rate dynamically during runtime with Application.targetFrameRate. For instance, you can drop below 30 fps for slow or relatively static scenes (such as menus) and reserve higher fps settings for gameplay.
Mobile platforms won’t render half-frames. Even if you disable Vsync in the Editor (Project Settings > Quality), it’s still enabled at the hardware level. If the GPU cannot refresh fast enough, the current frame will be held, and as a result, reduce your fps.
More information is available in the documentation.
Reduce or disable accelerometer frequency
Unity polls your mobile’s accelerometer several times per second. Disable it if it’s not being used in your application, or reduce its frequency for better performance.
Learn more about the accelerometer.
Avoid large hierarchies
Split your hierarchies. If your GameObjects do not need to be nested in the Hierarchy, simplify the parenting.
Smaller hierarchies benefit from multithreading to refresh the Transforms in your scene. Complex hierarchies incur unnecessary Transform computations and cost to garbage collection (GC).
Compress your textures
The two examples above use the same model and texture – yet the settings on top consume more than five times the memory compared to those on the bottom, without much better visual quality.
Texture compression offers significant performance benefits when applied correctly, such as faster load times, a smaller memory footprint, and dramatically increased rendering performance. Compressed textures use only a fraction of the memory bandwidth needed for uncompressed 32-bit RGBA textures.
Refer to this Recommended list of texture compression formats for target platforms.
Leverage Texture Import Settings
Textures can potentially use excess resources, so optimizing your Import Settings is critical. In general, try to follow these guidelines:
- Lower the Max Size: Use the minimum settings that produce visually acceptable results. This is non-destructive and can quickly reduce your texture memory.
- Use powers of two (POT): Unity requires POT texture dimensions for texture compression formats.
- Toggle off the Read/Write Enabled option: When enabled, this option creates a copy in both CPU and GPU addressable memory, doubling the texture’s memory footprint. Disable it in most cases, and only enable it if you generate a texture at runtime that you need to overwrite. You can also enforce this option via Texture2D.Apply, passing in makeNoLongerReadable set to True.
- Disable unnecessary Mip Maps: Mip Maps are not needed for textures that remain at a consistent size onscreen, such as 2D Sprites and UI Graphics. However, leave Mip Maps enabled for 3D models that vary in distance from the Camera.
Learn more about Texture Import Settings.
Atlas your textures
Atlasing is the process of grouping together several smaller textures into a single larger texture. Texture Atlases reduce memory usage and require fewer draw calls, thereby decreasing the effort required by the GPU.
- For 2D projects: Use a Sprite Atlas (Asset > Create > 2D > Sprite Atlas) rather than rendering individual sprites or textures.
- For 3D projects: You can use your Digital Content Creation (DCC) package of choice. Several third-party tools like MA_TextureAtlasser or TexturePacker can also be used to build Texture Atlases.
Combine textures and remap UVs for any 3D geometry that doesn’t require high-resolution maps. A visual editor gives you the ability to set and prioritize the sizes and positions in the Texture Atlas or Sprite Sheet.
The Texture Packer consolidates the individual maps into one large texture. Unity can then issue a single draw call to access the packed textures with a smaller performance overhead.
Read more about Sprite Atlases here.
Check your polygon counts
High-resolution models require more memory usage and potentially more work for the GPU. As such, aim to keep the geometric complexity of GameObjects in your scenes to a minimum. Otherwise, Unity has to push significant vertex data to the graphics card.
Best practice is to cut down the models in your DCC software and delete unseen polygons from the Camera’s point of view. For example, if you never see the back of a cupboard resting against a wall, the model should not have any faces there.
Be aware that on modern GPUs, the bottleneck is usually related to polygon density as opposed to polygon count. Try to perform an art pass across all assets to reduce the polygon count of distant objects. Microtriangles can be a significant cause of poor GPU performance.
Depending on the target platform, think about adding details via high-resolution textures to compensate for low-poly geometry. Use textures and normal maps instead of increasing the density of the mesh. Reduce pixel complexity by baking as much detail into the textures as possible. For instance, you can capture the specular highlights in the texture itself to avoid computing the highlight in the fragment shader.
Be mindful and remember to profile regularly. After all, these techniques impact performance and might not be suitable for your target platform.
Manage Mesh Import Settings
Much like textures, meshes can consume excess memory if not imported carefully. Try these tips to minimize your meshes’ memory consumption:
- Mesh Compression: Mesh Compression can reduce disk space (though memory at runtime is unaffected). At the same time, mesh quantization can result in inaccuracy, so experiment with compression levels to see what works for your models.
- Disable Read/Write: Enabling this option duplicates the mesh in memory, which keeps one copy of the mesh in system memory and another in GPU memory. In most cases, you should disable it. In Unity 2019.2 and earlier, this option is checked by default.
- Disable rigs and blend shapes: If your mesh does not need skeletal or blend shape animation, disable these options.
- Disable normals and tangents: If you are absolutely certain that the mesh’s material will not need normals or tangents, uncheck these options for extra savings.
See more mesh optimizations
Additional mesh optimization options are available in the Player Settings:
- Vertex Compression sets the vertex compression of each channel. For example, you can enable compression for everything except positions and lightmap UVs. This can reduce runtime memory usage from your meshes.
- Note: The Mesh Compression setting in each mesh’s Import Settings overrides the Vertex Compression setting. In that event, the runtime copy of the mesh is uncompressed and might use more memory.
- Optimize Mesh Data removes any data from meshes that is not required by the material applied to them (such as tangents, normals, colors, and UVs).
Audit your assets
By automating the audit process, you can avoid accidentally changing asset settings. The AssetPostProcessor can help you standardize your Import Settings or analyze existing assets. It allows you to run scripts when importing assets, and essentially, prompts you to customize settings before and/or after importing models, textures, audio, and more.
Adjust the async texture buffer
Unity uses a ring buffer to push textures to the GPU. You can manually adjust this async texture buffer via QualitySettings.asyncUploadBufferSize.
If either the upload rate is too slow or the main thread stalls while loading several textures at once, adjust these texture buffers. You can usually set the value (in MB) to the size of the largest texture you need to load in the scene.
Keep in mind that changing the default values can lead to high memory pressure. Also, you cannot return ring buffer memory to the system after Unity allocates it. If GPU memory overloads, the GPU unloads the most recent and least-used texture, and forces the CPU to reupload it the next time it enters the Camera frustum.
Stream Mip Maps and textures
The Mip Map Streaming system gives you control over which Mip Map levels should load into memory. Enable it by going to Unity’s Quality settings (Edit > Project Settings > Quality) and check the Texture Streaming option. You can enable Streaming Mip Maps in the Texture Import Settings under Advanced.
This system reduces the total amount of memory needed for textures because it only loads the Mip Maps necessary for rendering the current Camera position. Otherwise, Unity loads all of the textures by default.
Texture Streaming trades a small amount of CPU resources to save a potentially large amount of GPU memory. It also automatically reduces Mip Map levels to stay within the user-defined Memory Budget.
You can use the Mip Map Streaming API for further control.
The Addressable Asset System simplifies the management of assets that make up your game. Any asset, including scenes, Prefabs, text assets, and so on, can be marked as Addressable and given a unique name. You can then call this alias from anywhere.
Adding this extra level of abstraction between the game and its assets can streamline certain tasks, such as creating a separate downloadable content pack. Addressables make referencing those asset packs easier as well, whether they’re local or remote.
Install the Addressables package from the Package Manager. Each asset or Prefab in the project has the ability to become “Addressable” as a result.
Checking the option under an asset’s name in the Inspector assigns it a default, unique address. Once marked, the corresponding assets appear in the Window > Asset Management > Addressables > Groups window.
Whether the asset is hosted elsewhere or stored locally, the system will locate it using the Addressable Name string. An Addressable Prefab does not load into memory until it’s needed, and then automatically unloads its associated assets when no longer in use. This blog post on Saving memory with Addressables demonstrates how you can organize your Addressable Groups in order to be more efficient with memory.
See Addressables: Introduction to concepts for a quick overview of how the Addressable Asset System can work in your project.