[Advice] Unity Optimization

9 posts

Flag Post

Optimization is one of those things that I’ve learned little bits and pieces of over time as it’s been needed. As there isn’t a good list of the different kinds of topics that help to make a Unity game run well on mobile devices I thought I’d start a thread with various factors. These will not be in-depth instructions on how to utilize each technique, but rather an overview of what the technique is, what it’s called, etc.

Feel free to add to this list.

  • Draw Counts
  • This is the primary cause of lag in a unity game. There are a number of contributors (below) but this number (which can be found by looking at the game view and clicking the “stats” button in the upper right) is the easiest go-to number to look at to see if a change positively or negatively impacted performance.
  • Shaders
  • Complex shaders are slow. That color doodad that lets you tweak how a texture looks requires an extra multiplication step during rendering. Additional textures require a second lookup and second multiplication. Bumpmaps take even more (texture point lookup, lighting inputs, multiplication…). While these won’t inherently cause more draw calls, they are slower than simpler shaders. If you can get away with it, use the shaders under Mobile. If you absolutely need X feature, use the default shaders. Avoid reflections!
  • Lighting
  • Bake your lightmaps! For the love of god, bake your lightmaps. You do not need every single light to be real-time rendered with high quality shadows. Every light you have causes an additional draw pass. A draw pass is essentially “take your draw counts, add that many again.” A scene with 50 draw counts, just for meshes, with 5 real-time lights will require 1600 draw counts! (This is approximate, if a point light doesn’t overlap with a given mesh, then that mesh is not included in the increased count, but even a couple of small point lights can double your draw counts).
  • Vertex Lighting. If you can get away with it, vertex lighting requires fewer lighting calculations and will result in lower draw counts. At the per-shader level, the savings will be small, if you can get away with it, set the camera to use Vertex Lighting instead of forward or deferred. You’ll lose some detail, like bump mapping, but it will cut your draw counts by 60% easily.
  • For dynamic objects, you will want to set up light probes so that as objects move through the scene they can approximate their lighting more accurately from the baked lightmaps.
  • Textures
  • Use a texture atlas / sprite sheet. If you have two shaders that are identical except for the texture used, find a way to combine those textures and use a single shader. Every mesh with a unique shader causes an additional draw count.
  • MaterialPropertyBlock. This is a better way to make run-time alterations to a shader than editing the renderer’s material. A MaterialPropertyBlock doesn’t create a new material, rather it alters the parameters just before the material goes to the renderer. Thus it can be used with Combine Children or other batching (although there are some limitations).
  • Meshes
  • Combine your meshes whenever possible. Unity can render one 100,000 vertex mesh faster than it can render four 25,000 vertex meshes, even if they have the same texture. The best way to do this is to set the mesh as static (upper right corner of the Inspector panel). There’s a dropdown that will let you specify what kind of static it is, including occlusion, navigation, and lightmapping.
  • The Combine Children utility will also work, but static batching is pretty fast already and does basically the same thing (static objects can’t be combined with CombineChildren either).
  • Occlusion
  • While Unity will automatically cull objects outside the view fructum, it will still render everything in front of it, even if its completely hidden by another object.
  • Not-rendering things that can’t be seen is good. Make all of your small meshes as “occludees” and your large ones as “occulders” (mid-size ones should be both, if they occlude a large portion of things). Don’t forget to set up occlusion volumes for places the camera can go. This is similar to Valve and the Source Engine’s Visleaf system, although done by hand.
  • Animators
  • Remove any unused animator components from your imported mesh objects. They prevent the object from being statically batched as they are rendered as if they were a dynamic moving object, even if they’re not doing anything.
  • Other
  • If you can get away with not-drawing something at certain points of the game—such as a GUI indicator that can’t readily be acted upon for a length of time—turn it off. For instance, I had a bunch of meter-bars in a Tower Defense game that pointed out certain bonuses when a tower was built there. As the player was less likely to be building towers when a wave was in progress (and with the increased draw counts that enemies caused) it was a big performance increase to turn those bars off and save on their draw counts.
  • For mobile games the ideal draw count is as close to 0 as you can manage. I’m currently getting away with ~150 for a project on an iPad 2, but under 300 should “work” although “under 30” should be your target. At 800 the lag is noticeable. At 8000 its downright unacceptable (this project started at 8800!). For desktop applications you get get away with more, my machine was capable of handling the 8800 draw counts and still maintaining 30 FPS (at 150 it gets 200-250 FPS).
 
Flag Post

GameObject.Instantiate(), GameObject.Destroy(), and GameObject.Find() are all REALLY slow. Only use them in constructors or methods that are not executed every frame, or if possible avoid them altogether.

Unity uses inefficient serialization when creating gameobjects. Keep your gameobject count as low as possible.

Avoid using “new” in Update or onGUI. If it is an object you use every frame it is faster to create one object and alter it than it is to create a completely new object. This excludes small structs such as Vector2/3 and Quaternions.

 
Flag Post

GameObject.Destroy() isn’t that intensive. It’s the garbage collection that comes along a few frames later that will om nom CPU cycles. In the first TD I did in Unity I batched the creep destruction by turning off their scripts and throwing them under the map until the end of the level, then calling Destroy() on all of them. The lag spike for the one frame was largely unnoticed.

I don’t recall what I did with bullets.

 
Flag Post

as for Instantiate, do this in the start of the game for the objects needed and use a pool manager.
There may still be a few places where you need to Instantiate objects, but if common objects (bullets, effects etc.) come from pool manager, it save a lot of time.

As for Occlusion I’m not sure if I read it wrong, I missed something, to Occlusion work rather well in Unity Pro. And if I remember correct its not even possible in the free version.

 
Flag Post
Originally posted by Zerakil:

As for Occlusion I’m not sure if I read it wrong, I missed something, to Occlusion work rather well in Unity Pro. And if I remember correct its not even possible in the free version.

It is. Sorry, I’ve got access to Pro so I’ve got Fancy Features.1 Occlusion is really only about a 30% gain, whereas static batching will get you closer to 90% (if both are set up properly and you follow the “as few shaders” rule).

1 Like intersection shaders using the depth buffer (that’s actually a custom shader that is both an intersection shader and a rim shader, on Free only the rim works). I may actually buy Unity 5 for personal use, depending on a couple of factors.

 
Flag Post

Thanks for the info, I know static batching is best practice, but must admit that I have not had any real need for optimization when it comes to Occlusion, But guess it get more importan if working on large 3d levels for FPS or true 3d Games.

Now I’m a bit sad that this post have not had more attention, as its contain very valid information.
I know its a long-shot, but darn it would be nice if it was made a sticky post :P

 
Flag Post
Originally posted by Zerakil:

Thanks for the info, I know static batching is best practice, but must admit that I have not had any real need for optimization when it comes to Occlusion, But guess it get more importan if working on large 3d levels for FPS or true 3d Games.

Basically anything with a ton of small and complex models will benefit.

 
Flag Post

I know it may be a little abrupt, but I do want to join the crew of Unity devs. However I’m not the best at programming at all and only know few code- guess I’ll have to find out.
P.S: Do you automatically get the ‘developer’ sign when uploading a game or do you need permission

 
Flag Post
Originally posted by ChazmicSupanova:

I know it may be a little abrupt, but I do want to join the crew of Unity devs. However I’m not the best at programming at all and only know few code- guess I’ll have to find out.
P.S: Do you automatically get the ‘developer’ sign when uploading a game or do you need permission

You get it when you publish it. And then it will also be public for others to see/play.