In patch 1.0.4 of Wisps: The Redeeming I managed to get up to 40% performance boost on some integrated chipsets and even more on gaming grade video cards, although this is less important because the game was running on more than 60 FPS (frames per second) on those already.
Unity 3D is a tool, and like any tool it is the right tool for the right job. It is very good for games that have pre-created levels in its editor, where you can bake lightmaps, batch static meshes and what not, but it falls short when it comes to optimizing games with dynamic content generation. Wisps: The Redeeming is such a game. Every labyrinth is randomly generated by an algorithm at a logic level. Then the terrain height map is created based on the logic level to create elevations and nice texturing, after which all trees are instantiated and carefully positioned, while still looking as random as possible through rotations and scaling.
Here are some areas where Unity worked against me and how I managed to get away (or not):
1.1. Unity terrain engine is great for games where you are actually close to the terrain. This is because only the terrain near the camera is detailed, the rest of the terrain mesh being “decimated” dynamically the further you get from the camera. In Wisps, because it is a top-down game, the camera was “close” to a large terrain area at the same time, all the time, so Unity would display a lot of triangles and fail to optimize terrain properly.
I managed to fix this by reducing the detail of the best terrain LOD to acceptable levels, decreasing the number of displayed triangles considerably.
1.2. At some point I tried to add foliage and it went terribly bad because of the same issue, a lot of foliage would be displayed at any given time, killing the performance. Also there was a problem with the orientation of the foliage, because grass usually is made of vertical polygons, which didn’t really work for my top-down view.
There was no viable solution to this so I just gave up on this one and painted some foliage in the terrain textures.
1.3. Unity terrain engine uses custom shaders I didn’t tapered with. Because of that it requires at least one light to light up the terrain, there are no ‘vertex color’ shaders available. You can bake lightmaps for it, but that is only at design time, and since my terrain was dynamically generated I couldn’t use lightmaps either.
Again, I had no idea how to fix this one, so I had to use one directional light here.
As a conclusion, I chose to use Unity terrain in favor of some custom grid I could make myself because of the texture blending provided, but in the end I think I would have been better with implementing my own terrain mesh, however this was obvious too late in the development process to do anything about it.
My game required a lot of lights, because the whole point was having a dark forest full of lit wisps. At first I had two or three lights in every tree and it looked fantastic, but the performance was terrible. It would have worked with Unity Pro because it features deferred rendering, but here I had to go with the forward rendering path.
I have solved this by using custom animated shaders for the trees. Even if it looks the trees are affected by some light, they are actually using only vertex colors as fake lighting and an alpha channel based animation for the glowing effect. The trees at the border of the labyrinth are not glowing, so they are using simple vertex color shaders. At first I had all the trees glowing, but the performance bonus of having the border ones use simple shaders was too big to be ignored. The only light that still affects the trees is the one generating lightning bolts in levels with rain storm effect, but that one is only used for a few milliseconds and then it is disabled for the rest of the time.
The glow effect on wisps themselves is also the result of using a custom surface shader.
I had also revised all lights in the game (this was pre-release) and made sure each lights affects only the layers it needs too, this was also a big performance boost. I also had to reduce the number of pixel lights available at any one time on the screen, so only a few are pixel lights, the rest being vertex ones.
3.1. Because of the same top-down view, a lot of trees were on screen at any given time, up to a few hundreds sometimes. That was generating a lot of draw calls and was quite a performance hit.
This is why we had imposed limits on the number of triangles a tree has (at most 30). However, I have quickly discovered that although in a level I am using the same material for all trees and only four or five different meshes, Unity does its thing and splits the triangles even more, so I had to tweak the mesh importing a bit to reduce that.
3.2. Because I used Unity Free and my scenes were dynamically generated, I could not use static batching, generating a lot of draw calls for the trees. There is a so called ‘dynamic batching’ feature, but it only kicks in for meshes more detailed than our trees, so reducing the number of triangles per tree also worked against us in terms of draw calls.
I couldn’t do anything to optimize this one.
4.1. When I created my weather effects, I wanted to have some snow and sand storms in there. For those I required only a few particles but very large to be spawned, and that led to a great deal of performance loss.
I have optimized this by choosing more smaller particles. It doesn’t look as good, but it does the job quite well.
4.2. The rain effect was a problem, because I wanted to heavily rain all over the level, unfortunately using a static particle system for that would kill the performance too much.
The solution I have found is to have a ‘raining’ particle system attached to the camera, that only spawns particles within the view frustum. It is not as realistic and beautiful as the other one, but it still does the job well.
Unity is using nVidia PhysX which has some advantages, the main one is being accelerated by nVidia cards.
5.1. I had experience with PhysX in the past (when it was Ageia) and I knew it wasn’t the most reliable physics simulator. In the beginning I had a lot of trouble preventing wisps from going through trees at high speeds.
I had to decrease the simulation step a lot, killing performance, and I also had to use a lot of bouncy materials on wisps in order to stop them from passing through walls. Another step I had to take is to drop standard Unity colliders for trees in favor of a custom, dynamically generated mesh that builds impenetrable walls through the middle of the tree lines. This was quite tricky to make in order to keep its polygons number at a minimum.
5.2. Unity has a ‘spring joint’, but it wasn’t working as I expected in my case (a wisp following another).
The solution was to implement my own attraction/deflection simulation for the following behavior.
5.3. Having PhysX accelerated by nVidia cards it is also a disadvantage on video cards manufactured by other vendors, like Intel chipsets, where all the computations have to be made on CPU, and it affects performance as well, giving uneven physics simulation in some cases (wisps speed up a bit then slow down a bit for now apparent reason).
That’s about it. In my next article I will describe how I have optimized the game even more and what features I had to cut to make it playable on Android devices, stay tuned.