I have lost count of the number of times I have been asked 'why does my scene perform poorly'.
The root cause in 99% of cases for poor frame rates and performance is lack of knowledge, and the good news is that you can usually make significant improvements with some fairly simple tweaks!
There is no silver bullet as every game and every scene is different. You literally need to consider every scene as a unique entity to be optimized.
This tutorial provides some practical guidance on how to optimise your Unity environments to run well on your target device.
Performance in a nutshell!
The metric typically used to measure performance is frame rate. The frame rate you should aim for will depend on the type of game you are making.
What is a frame rate ?
- Your game engine renders the content of your camera's view port to the screen as a 'frame'. Frame rate is the number of times this is done per second (frames per second - fps)
- This will vary wildly based on where you look because the amount of content you are rendering is depend on what you are looking at!
- Frame rate has a significant impact on the perceived playability and responsiveness of your game.
What frame rates should you aim for ?
- Turn based games, little changes in point of view, 30 fps
- Real time games, lots of changes in point of view, 60 fps
- VR games, 72 fps to 90fps dependent on device
What causes poor frame rates ?
- Trying to do too much per frame
Bottlenecks - if either resource is bottle-necked, both will suffer, as will your frame-rate
- CPU bound (CPU can not keep up with what you are trying to compute)
- GPU bound (GPU can not keep up with what you are trying to render)
How do I check for bottlenecks ?
GPU is often limited by fill rate or memory bandwidth.
- Lower the display resolution and run the game. If a lower display resolution makes the game run faster, you may be limited by fill rate on the GPU
GPU has too many vertices to process.
- The number of vertices that is acceptable to ensure good performance depends on the GPU and the complexity of vertex shaders
- Aim for no more than 100,000 vertices on mobile.
- A PC manages well even with several million vertices, but it is still good practice to keep this number as low as possible.
CPU is often limited by the number of batches that need to be rendered.
- Check “batches” in the Rendering Statistics window. The more batches are being rendered, the higher the cost to the CPU.
- The CPU has too many vertices to process. This could be in skinned meshes, cloth simulation, particles, or other game objects and meshes. As above, it is generally good practice to keep this number as low as possible without compromising game quality.
- GPU is often limited by fill rate or memory bandwidth.
- If rendering is not a problem on the GPU or the CPU, there may be an issue elsewhere - for example, in your script or physics.
How do you fix performance issues ?
- Profile - run the profiler, first in editor, then on device to measure performance
- Analyse - review the output of the profiler, and determine where your bottlenecks are
- Change - change your assets, code or scene to reduce the impact of your bottlenecks
- Repeat - keep doing this until your performance is acceptable
NOTE: Performance optimization is a deep subject, and there is much more that can be done than can be written about in one article. If you would like a professional evaluation and help optimizing your project then please contact us for a quotation.
Some Thoughts On Performance Optimization:
Time is money and optimization is a game of frustratingly diminishing returns!
Solve the big problems first and then get on with making your game. If you don’t need to do it then don’t waste your time!
What platform and hardware will your target audience be running your game by the time your game will release?
Graphical power is advancing incredibly quickly. Know your target hardware and keep your target audience and future release date in mind as you optimise. Don't over optimise for today, if your hardware for tomorrow will handle it for you.
NOTE: The corollary to this is that most people do not have the luxury of running the latest hardware, so you will dramatically increase the addressable audience of your game if you target lower end hardware.
Be realistic and match what you are doing in your scene against the target hardware!
Regardless of how nice it might look on your powerful development desktop, putting a dense forest of high poly trees with cool volumetric lighting into a mobile or vr based game is never going to end well. Neither is rendering amazing water, or volumetric clouds and rich post fx. Do your research, there are many many guides out there on the internet.
Rule of thumb:
For mobile and VR: Use low poly assets, and less of them. Use minimal post fx.
For desktop and console: You can use higher poly assets, but still be careful.
For both: Pay close attention to the next three tips. Create a simple scene with the assets you want and see how far you can push it. Then you will have a good sense of what you can realistically do with your game.
When profiling, isolate in order to find the real issue!
Unity does the best it can to render your scene, and will make decisions for you based on what it understands about your scene. This will skew the results you see in the profiler and what you think you are seeing may not actually be the root cause of the issue. The more you have in your scene, the worse this will be and you may end up doing unnecessary optimisation. So in order to really understand where the bottle neck is, create a scene with that one thing, and then hypothesise and test, hypothesis and test.
Real world example: We had an issue where terrain render performance for our trees was much less than we expected. We assumed (incorrectly) that it was the poly count on our trees. To test we created a terrain and filled it with 1 type of tree. Render was abysmal. Then we swapped all those trees with cubes. Render was still abysmal. Root cause was that Unity terrain has an inherent limitation on the number of trees it can render. Simple Fix: Spawn less trees!
Level design dramatically impacts performance!
Embrace this! Be clever in the way you construct your scenes in order to minimise what is being rendered. For example, use clever placement of larger assets to draw player attention and to obscure large chunks of your scene (and thereby negate the need to render it), or use fog to hide things in the distance and render lower quality LOD’s or not at all. Good level design make a massive difference to performance and your audience will never know! Check out your favourite AAA game for some great examples of how you can do this.
Performance in editor is vastly different to a compiled build!
While you can profile in the editor to get a sense of where your issues are, running the editor is a huge overhead, and is vastly slower than runtime. The only true indicator of performance is to run a build with vsync disabled on your target hardware. Make sure you turn vsync back on before shipping your final game!
The approach I take is to experiment with one setting at at time and keep experimenting and profiling until I get the settings I am happy with.
General Tips and Tricks:
Here are some tips to explore. They vary in impact based on the unity version and render pipeline being used. Pick and choose as you want:
- Spawn less stuff
Simple eh! The more you put into your scene the more expensive it is to render. If you have performance issues, consider what you can cut from your scene. Use tricks like occlusion culling.
- Bigger usually isn't better
I hear this a lot. I need an infinite / massive world. Really? A massive world needs quality content and game play to be interesting, and creating content and experiences is hugely time consuming. If you are new to game development then focus your attention on smaller environments and filling them with great game play, your game will have a much higher chance of achieving success.
- Unity terrain is expensive
The higher the resolution, the more trees you render, the further the detail distance, the worse your performance will be. Make your terrains smaller. Put less in them. The number of trees in particular will tank your performance. We have done experiments in which tree rendering will tank even when rendering cubes. Spawn less trees. Use clever level design to occlude (hide) as much as you can.
- Stream large environments
The bigger your environment is, the more system resources it takes to load and render, and the slower your performance will be. If you really need large environments then stream them. Steaming is the process of loading a scene in from disk and then rendering it. Streaming systems will dynamically load and unload scenes only as they are needed, and make large environments possible. Gaia Pro has a simple but effective terrain streaming and culling solution built in. SECTR is a specialized stand alone streaming solution. Storm is a high end solution that streams and acceleration any scene up to planet earth sized environments.
- Use less textures on Unity terrain
Every multiple of 4 textures causes the unity terrain shader to render an additional pass (draw the terrain all over again). So even adding 1 texture over this boundary of 4 will cause another pass.
- Use less terrain grass / details on Unity terrain
Each additional grass texture consumes memory, rendering and culling overhead. This is also very expensive.
- Do not spawn too many trees on Unity terrains
It is tempting to spawn lush dense forests in Unity, but the terrain renderer will have problems with this. Instead consider making thin dense borders of high poly trees for great visuals, and then spawn interiors much lower poly tress that are much less densely packed. Every poly counts!
- Reduce the size on your texture and normal maps
Experiment with 256 and 512 sized textures. You are often better to optimise your textures with specialist 3rd party tools than to let Unity do it for you as they will most likely be cleaner and this can have a substantial visual impact.
- Smooth your terrain
Run a smooth or two on your terrain after you have finished stamping. Smoothing the terrain reduces the number of polys that need to be rendered. This comes at the cost of a reduction in the perceived detail which is especially noticeable on things like mountains. This additional detail catches the light and creates the classical mountain looks that we all love.
- Increase the Pixel error on your terrain
This will have the effect of reducing more distant terrain at a lower resolution (LOD), but will also increase visual artefacts as you move around. Experiment until you find a trade off you are happy with.
- Either remove grass completely, or use it sparingly
It is a performance hog. You can shrink the detail distance in your terrain settings, and also lessen the detail density. Make sure that you color your grass so that it blends with the color of the terrain – this way you lessen the visual impact of hitting the detail distance.
- Use occlusion culling
By default the camera will use frustrum culling to remove objects that are out of view on either side of the current camera view, however you can get another level of performance as well by also using occlusion culling. Occlusion culling will stop objects that are behind other objects from rendering and will result in more objects being culled than just frustrum culling. Rendering less objects results means less draw calls and higher performance. You can bake your occlusion culling via the occlusion culling window to generally get an immediate performance improvement. TIP: Be creative during level design and use of larger objects such as mountains, hills and walls in your scene. These will block your view of distant objects, and hence increase your framerate via culling.
- Reduce your shadow distance for each of the quality settings you use
If you can get away with it, use hard shadows. You will find these under Edit -> Project Settings -> Quality : Shadow Distance. Also explore the other shadow settings while you are there. Click on Game view and experiment to get the visual settings you want.
- Reduce your LOD bias for each quality setting you support
You will find these under Edit -> Project Settings -> Quality : Lod Bias. Click on Game view and experiment to get the visual settings you want.
- Use Post FX sparingly
Profile the FX to validate their impact on performance. In later versions of Unity you can see these at design time. Click on Game view and experiment to get the visual settings you want.
- Lose wind completely
Delete the Wind Zone object that Gaia creates. You will find it under Gaia Environment / Wind Zone in the scene hierarchy.
- Mark rocks and other assets that wont move as static
This allows unity to batch them, which in turn reduces draw calls and increases frame rate. Make sure when you spawn these assets that their scale is a whole number and this will allow Unity to choose the best case between dynamic and static batching.
- Instance as much as you can
Instancing is the process of sending the object to the GPU once, and then rendering it multiple times, as compared to sending it to the GPU for every single time it needs to be rendered. Check this article out on Instancing SpeedTree.
- Use baked lighting
If you do not need dynamic lighting that changes at times of day then bake your lighting instead. It looks and performs better.
- Use ambient lighting instead of a skybox
In your lighting tab, make your ambient light source a fixed color. This is less GPU intensive than using the skybox.
- Use an HDRI skybox instead of a procedural skybox
In your lighting tab, make your sky a non procedural Skybox instead of the default procedural Skybox. Its much cheaper often looks better.
- Lighting tip
Often in editor mode objects will look really dark and will only display correctly only after you have finished the lighting bake. Disable precomputed and baked lighting in your lighting tab and run a quick bake.
- Use Forward instead of Deferred
This is cheaper for environments that have a single light e.g. the sun. It will perform words however if there are multiple lights in the scene. You will find this setting under Player Settings. File -> Build Settings -> Player Settings. If you have multiple lights however, Deferred will be faster and better.
- Select the “Fastest” setting under Quality settings
Select Edit -> Project Settings -> Quality. Experiment.
- Delete unused Update methods in scripts
In your scripts, never leave the Update method if the script does nothing. This uses cpu even if its empty. If you must do something regularly, then consider using co-routines, or using a master manager component.
- Cache everything
Its much cheaper than finding things during Update.
- Never use methods like Find(), except perhaps in Start()
It's very expensive.
- Avoid using Vector3.Distance()
Use Vector3.sqrMagnitude instead.
- Avoid using Ray casting
If you must use, then use sparingly.
- When using colliders
In order of cheapest to most expensive, use Sphere, Capsule, Cube, Mesh, Convex Mesh. Mesh colliders are way more expensive, so estimate the collision volume with a sphere or capsule if you can.
- Use GPU Instancing
- Set per layer cull distances on your camera
Need Help ?
Performance optimization is a deep subject, and there is much more that can be done than can be written about in one article. If you would like a professional evaluation and help optimizing your project then please contact us for a quotation.