Combining Meshes vs. Occlusion Culling vs. ... Static Batching?

Does anyone have a clear explanation of how each of these work, or don’t work, in tandem? It seems that combining meshes OR using occlusion culling would be mutually exclusive. Is that the case? And I’ve seen conflicting points of view on whether or not static batching also renders occlusion culling pointless.

Thoughts? Feelings? :slight_smile:

Occlusion culling is simply hiding meshes that should not be seen, so the gpu doesn’t waste time on those.
Static batching isn’t quite the same as merging meshes. Merging meshes is simply that: you have 1 mesh as a result.

Where static batching differs is that while it is still 1 mesh, Unity will keep a list of verts/tris which comprise of each “mesh” that was statically batched, thus it can still cull parts of it. This is a lazy man’s solution, and will always be slower than manually managing your mesh chunks, however it is good if you’re not that experienced or don’t have those time resources to devote to manually creating merged geo that’s optimal.

This however takes up more ram, and unity will still test culling against each statically batched mesh, which can consume cpu depending on whatever you’re statically batching. Some things are a terrible choice for it, while other things are good. The docs hint at which.

As for occlusion culling / static batching advice you’ve read, I’m sorry that makes no sense at all, whatsoever, they’re entirely different things.

2 Likes

Hmm, okay. I’d found something that was quoting this:

And basically refuting it, saying that static batching also has a negative effect on culling. Of course, I can’t find the thing I was reading now, so who knows, I may have simply imagined the whole thing during one of my drunken stupors.

Another question…

Consider something like this scene below.

Each of the pillars is an individual static object. Static batching should be reducing all of the pillars to one “draw call” (or whatever it’s called now), though with some overhead, right? Additionally, pillars are also being occluded when they’re either out of the frustum, or being blocked by other occluders.

Would there be ANY reason to consider combining all of the pillars into one, level-wide mesh, thereby increasing geometry significantly, but reducing “everything else”?

If your meshes that share materials are going to be visible together, most of the time, you should combine them all in a single mesh.

I don’t think combining all the columns in a single level is a good idea though. They should be in a bunch of separate chunks so you can take advantage of frustum/occlusion culling and/or manual mesh management.

I think it’s a balancing act. You shouldn’t over rely on static batching, but you shouldn’t merge everything together either.

Tricky. I’m not too sure what the best approach for me is going to be. I’ll have to give it some thought.

The performance in my game is complete crap at the moment, but I might be inventing things that I don’t need to be looking at. I’m hoping that when the fix comes for the “Unaccounted” issue ("Unaccounted" in Profiler - Unity Engine - Unity Discussions) that I might be able to see what the REAL problem is.

Combining meshes might be something that I don’t even need to look at for this particular project, but I’ve just somehow convinced myself that it’s the solution to all my problems.

But you would not be adding lots of small things to static batching as per the docs so this should not become an issue. If you are, you need to merge them into chunks first before static batching. Static batching is for unique meshes which share same material, not lots of little repetitive ones (which is best served merging into a chunk).

Static batching is basically a situational tool. If you’re doing a large dungeon romp like that you’re much better off just merging every cell and hiding it/showing it with a custom culling scheme, it will be so much faster just traversing an array until a wall is hit, and only showing them I think. You might not even need to be merging a single thing if you do your own 2D array based culling… with a bucket or list of objects for each cell since I hardly think that’ll go above 500 draw calls from what I’ve seen.

3 Likes

Would doing it myself that way be any better than just using Unity’s occlusion culling though?

It’ll most likely be faster manually. Depends if that’s actually a bottleneck though. If your game is dungeon master style, then you have absolute vis control.

But it depends on what your slowest thing is.

It’s kind of weird. In this run I’ve combined the pillars, floor tiles, roof tiles, and walls each into their own single mesh. So the majority of the level is in 4 meshes. The tris/verts has increased significantly as expected, but the batches and setpass calls have not gone down at all, though I expected them too? In fact, the batches has increased from around 80 previously when all the meshes were separate.

Am I expecting the wrong result?

I’m running into a similar problem (see thread: High numbers of draw calls - is Unity the right tool? - Unity Engine - Unity Discussions).
I tried a combination of mesh merging and thinking about optimal OC. In the end I still end up with way too many draw calls and simplifying my meshes seems to be the only way out. Ie, merging, reducing vertex and poly count and merging more textures.

I want to get more info about the way Unity does frustum culling on
Static Batched object.
If you have some ref let me know please.

They check the visibility of each ‘sub’ mesh via their usual pipeline for culling. Each part of a static batched mesh pays the same cpu culling cost as any other mesh that would have been separate.

The only differences are:

  • It’s more memory. Probably not as much as HLOD but static batching should be replaced by a HLOD scheme in 2019 when the competition has all this automated…
  • It’s same culling cost but not same cpu cost for draw calls

So basically you’re saving on draw call cost only, and really I don’t see the worth of static batching for real world games. Because the optimal use of static batching is to use big infrequent meshes, something even regular culling doesn’t choke on because there isn’t enough meshes that can typically fit this criteria.

Basically for perf:

  • drawmeshinstancedindirect - use this with compute culling for grass, debris, rocks, repetitive things
  • use portal culling for room/area contents / classic camera culling (frustum) for local stuff that can occlude easily
  • use static batching for the large big things that are generally different meshes but same shader, and aren’t numerous

So as you can see it’s best to solve the problems with different approaches within the same program.

It’s a bit embarrassing Unity doesn’t have HLOD/Remesh on import and functionality like this built in but probably they are going to do that in DOTS.

But if you want a TLDR: Use a SRP with SRP batcher, don’t use static batching and solve the rendering with modern features like instancing and even better: drawmesh instanced indirect.

4 Likes

Yeah, these days even mobiles don’t have that much overhead from draw calls so the cost of batching in less than ideal situations is not really worth it.

It’s why I have been saying that the reason to use the SRPs is the SRP batcher, since it’s a more modern and efficient way of batching.

I nominate pinning your post somewhere, there’s solid info there.