Occlusion Culling in Unity 4.3: Best Practices
The following blog post was written by Jasin Bushnaief of Umbra Software to explain the updates to occlusion culling in Unity Pro 4.3.
This is the second post in a three-post series. In the previous post, I discussed how the new occlusion culling system works in Unity 4.3. I went over the basic usage and parameters that you need to know in order to get the best out of occlusion culling. In case you missed it, check it out here.
This post gives you a list of general recommendations and tips that should help you get the best results out of occlusion culling.
Good quality occlusion
It may seem obvious, but of course the first thing to make sure is that your scene actually contains meaningful occlusion. Moreover, the occlusion should preferably consist of good, large occluders if possible, as opposed to fine details that only accumulate as occluders when looking at from a certain angle. Umbra will generally not be able to perform occluder fusion, so even if your lush forest with lots of foliage will occlude something behind it, it will do so only once the individual occluders are “accumulated”. So in this sense, the trees and forests in general will be pretty bad occluders from Umbra’s point of view. On the other hand, a mountain is a good occluder and Umbra will certainly be able to capture it into the occlusion data as expected.
There are two main types of objects Umbra cares about: occluders and occludees. The former are just geometry and Umbra treats them basically as a single, solid model. The latter are the ones whose visibility Umbra actually tests using the occlusion data. Occluders consist of pretty much all geometry that have the “Occluder static” flag set, and unsurprisingly, occludees that have the “Occludee static” flag, respectively.
As a rule of thumb and by default, you can and should set most if not all your renderers as occludees in order for Umbra to cull them. Also by default, most of your static renderers can be occluders as well. Just make sure that if your renderer is non-opaque, it shouldn’t be an occluder either. (Unity will actually issue a warning if this is the case.) This naturally includes transparent objects and such.
But also, if your object contains very small holes (consider e.g. a cheese grater or a bushy plant) that you wish to see through, but reducing the value of smallest hole globally doesn’t make sense (see the previous post as to why), simply removing the occluder flag from the renderer is the correct thing to do.
Furthermore, because occluders are considered solid, correct culling can typically be guaranteed if the camera doesn’t intersect an occluder. This means that if e.g. the collision system cannot prevent the camera from flying inside an occluder, you should probably remove the occluder flag in order to get meaningful results.
Given the fact that Umbra does object-level occlusion culling, it doesn’t make a whole lot of sense to have objects of several kilometers in size. Such massive objects are very hard to cull, as some part of the object is almost always visible, especially combined with Umbra’s conservative culling. So, splitting up e.g. the terrain into multiple patches is typically a good idea, unless you want the entire terrain to always be visible.
In terms of occlusion culling, typically the best object subdivision is a natural one, meaning that naturally distinct objects should probably kept separate in culling as well. So chunking objects too aggressively typically doesn’t help. One should group only objects that are similar in terms of visibility. On the other hand, too fine-grained subdivision may introduce some unnecessary per-object overhead. In reality, this becomes a problem only once there are tens of thousands of occludees in the scene.
Maybe it should be emphasized that only the object subdivision of occludees matters. Occluders are considered to be a single big bowl of polygon soup anyway.
In the previous post, I briefly described how Umbra first voxelizes the occluding geometry, groups these voxels into cells and then connects the cells with portals. In the process, Umbra is always conservative, meaning that in various cases Umbra considers the occluders slightly smaller than what they are in reality, or conversely, the empty areas slightly larger.
This means that if there happens to be an unintentional hole in your occluding geometry, one which rather than getting patched up is retained by voxelization, there’s a good chance it’ll become somewhat magnified in the final output data. This may lead to surprising “leaks” in occlusion. The camera may be looking at a seemingly solid wall, but things behind the wall don’t get occluded because there’s some unnoticeably small crack somewhere.
So, while voxelization does patch a lot of unintentional cracks and gaps in the occluding geometry, it’s still highly important to try to model the geometry as water-tightly as possible. In the next post, I’ll describe the Visibility Lines visualization which may help you debug these kinds of issues.
Finding the right parameter values
Admittedly the hardest part of using Umbra is finding the right parameter values. The default values in Unity do a good job as a starting point, assuming that one Unity unit maps into one meter in your game, and the game’s scale is “human-like” (e.g. not modeled on a molecular level, nor is your typical object a planet or a gigantic killer-mech-robot).
A good rule of thumb is to start with relatively large values and work your way down. In case of smallest hole, for instance, the larger value you can use, the swifter is the bake process. Thus, you should tune it down only if/when you start experiencing culling artifacts, i.e. false negatives. Similarly, starting with a relatively large value for smallest occluder typically makes sense.
Then you can start adjusting it downward and see how Umbra culls better. Stop when culling starts taking up too much time and/or the occlusion data becomes too large.
As for backface threshold, start with 100. If your occlusion data is too large, or if you happen to get weird results when the camera is very, very close or possibly even intersects an occluder, try using 90 or even a smaller value. More on this in the next post.