Terrain culling and colliders is hosed

This is so bad there aren’t really words for it. The terrain tree culling is causing huge stutters because of how it’s enabling/disabling the colliders based on what’s being rendered as opposed to what’s in range. A lot of people have run into this and I finally got around to fixing it because it was the single biggest performance killer in my game.

I placed all my trees without colliders, then used a 2d spatial hash to load/unload colliders only on trees within a certain range as I move around,and now my performance doesn’t suck.

It took me about 2 hours to fix this on my end. The issue has been open for months, I think there is a ticket on it but couldn’t find it. This is something that has caused issues for several games I’ve seen, and it’s such a simple thing to fix. Come on guys.

If it helps anyone else, here is the code for the 2d spatial grid I used. Note the coordOffset is used for grids that go into negative territory, as the hashing algo doesn’t support that. Most folks can just ignore that.

5 Likes

FYI I’ll post the specific code that handles the actual loading/unloading of tree colliders at runtime once I get it in better shape.

I started out enabling/disabling colliders on trees as they came into and went out of range, but I’m changing that approach to using a pool of colliders with rigidbodies set to kinematic that are always on, and just moving them around, which is hugely less expensive then enable/disable/create/destroy.

So it’s still really rough because it’s part of a real game that is a WIP, but here is the code that will dynamically place colliders on trees or other objects as a player moves around the world. There are a couple of game specific items in this code I didn’t put any time into making it completely drop in, but it should only take some minimal adjustments to remove my game specific logic or tweak it to meet your needs.

The start method has some game specific stuff like the player object.

Terrains are assumed to be in a gameobject container called Terrains. Other objects are assumed to be under a gameobject called TerrainObjects, with one child for each terrain that is a container for the actual objects. You can easily modify the above to suit your own game. My maps are minimum 64 terrains each with a lot of detail, so I have to break it all out like this just to manage it.

The core logic is I create a pool of colliders with kinametic rigidbodies for the trees. For other objects I just enable/disable their mesh collider.

I place limits on how many colliders get added/remove at any one time to avoid spikes. You might want to adjust the numbers to suit your game.

3 Likes

Why is everyone not saying how awesome this is.

this is awesome. There I said it.

So the issue is only really noticed when you are moving the camera around, right? And only if you actually have a somewhat far billboard start distance.

Do you think I am probably fine if my billboard start distance is something like 50? It’s not going to be enabling/disabling hundreds of colliders at a time as you sweep the camera around, right?

"So the issue is only really noticed when you are moving the camera around, right, “And only if you actually have a somewhat far billboard start distance.” Possibly.

You should probably just with/wtihout test and see if this is a bottleneck in your final implementation, if so, this is a solution. if not, happy days.

“It’s not going to be enabling/disabling hundreds of colliders at a time as you sweep the camera around, right?” No, that is what the current system does. This is the opposite.

I think you misunderstood what I was asking to a degree.

With the default setup, using a 50meter billboard start, there aren’t really that mean trees with full mesh around you, even if the tree view distance is 1000meters, because most a billboards. Therefore, stock terrain scripting, only has the colliders on for those handful of trees within 50meters. If I rotate the camera around, the trees within 50m in my view will become full mesh with colliders enabled.

Using @snacktime 's script, it will turn on colliders within a certain range just the same. The only difference would be that I could set the colliders to only come on within 25meters instead of 50meters for example, right?

I guess what I am trying to say, is that at the end of the day, this isn’t as big of an issue if your tree billboard start is relatively close at 50m, because you already don’t have that many full LOD trees with colliders in range, regardless of tree view distance, right?

Assuming the setting for the amount of mesh trees around does it’s job.
(pretty certain that didn’t work last time I checked, but UT have a dedicated “Terrain person” now so…)

And also assuming that the billboards don’t have colliders… However, if the current system already ./had/ a working way to disable colliders based on distance (your assertion) - rather than “render visibility” - there would be no need for this script…

I am going to do some tests today around this area. I will see if I can dredge up some “control” metrics. Which this discussion is sorely lacking.

Yeah, I know the max mesh trees setting is broken, but I’m saying IF we assume there’s only colliders enabled on full mesh trees AND billboard start is 50m, then there’s on avg lets say 10 trees that are full mesh with colliders enabled. So as you move around, only 10 colliders are ever enabled. Colliders are currently enabled on render visibility, but is it MESH visibility or MESH+BILLBOARD visibility?

If you had billboard start at 200meters for example, that’s A LOT more mesh trees, so I can see where the colliders would be causing more of an issue.

If you can figure out somethings, like are colliders enabled even if the tree is FULL billboard, that would be awesome.

Yeah - that is more or less what I want to be able to assert.

You are asking exactly the right questions. I will go off and try to discover some solid answers

1st test: Completely unrelated to the original usage.

Can it turn particle trees “solid”

2061166--134267--upload_2015-4-9_14-58-28.png

Dynamic “collision” trunks

Yes. Great.

Actually implementing the code was fairly trivial, it is very self explanatory.
I did end up ripping out the namespaces and changing a few hard-coded “Parent GameObject” names. nothing major.

For simplicities sakes I ripped the whole of
https://gist.githubusercontent.com/gamemachine/102be3ace8a308a3bd5f/raw/6d224f6f2e083fd40e64a06ab19ebaf3f451edf8/spatial_grid
and dumped it in
https://gist.githubusercontent.com/gamemachine/d222e3e1ff3a8c30d297/raw/2edbb605d8ec6075004e2919b83909b007643417/grid_tracking
and saved that as GridTracking.cs then attached that to my procedural particle tree script

then reworked the terrain tree position extractor to simply be:

 int treecount = 0;
 ParticleSystem.Particle p;

 for (int i = 0; i < PlaceParticles.ParticleList.Length; i++)
 {
 p = PlaceParticles.ParticleList[i];
 Vector3 tpos = p.position;

 GridValue gridValue = new GridValue();
 gridValue.position = tpos;
 gridValue.id = "tree_" + treecount;
 gridValue.entityType = GridValue.EntityType.Tree;
 grid.Set(gridValue);
 treecount++;

 }

as you can see it easily walked over a particle system positions list, just as it would have done a terrain tree list, a list of rocks, anything really (which is why I said this was so awesome, not the terrain thing)

I crank it up to 2000 particles to test, seems okay

As someone who cares little for the built-in terrain system, I see this as a nice way to spacially track - well - everything.

I will pull up a test terrain and chuck a few trees at it though, just so we have some test numbers :wink:

the RemoveOld() method needs a garbage Tidy-up at some point.
Initially just reworking the foreach and possibly a refactor of one of the collection types to give us free enumeration.

I am looking at the 5000 hard limits right now since we can have /many/ terrain trees.
If I get something I’ll share.

I am about to make a mistake here and not direct this at anyone in particular but:
Feel free to jump in and help with the Terrain Tree testing.

EDIT: Actually @snacktime can just confirm the points mbowen89 raised. without testing

EDIT: Ran 7000, it was starting to get a little lumpy.

here was the modified code I was using as one big lump

3000 seemed a decent mid ground.
and if you are not too fussy with the light direction those shadows are tamable


cheap trees and a confirmation that the system works nicely for desktop.
Might need a tiny bit more tidying up to be perfect for all mobile.

Happy to see this is getting some use.

Ya it could use some performance optimizations. I didn’t know in advance where the optimizations would be needed and just wanted to get a working version out.

So I’m kind of hesitant to put this next one out there. It has a very limited use case but if you actually need it, I’m not aware of anything else out there that currently does a better job. That is the java version that was designed for tracking objects in range on the server side and is highly optimized for space when it comes to bandwidth. It’s fully concurrent. Uses protocol buffers for bit packing. Converts floats to integers before sending over the wire, using a set scale so you just multiply/divide to convert. Sends delta’s instead of the full position when possible. Uses id substitution so you can effectively use string id’s but they only go over the wire when the object first comes into range, and a short integer id is used all other times. Smart enough to resend full data when objects go in/out of range so you can sync coordinates and id’s.

This is the core code. If someone actually has a need for it I can provide the C# class I use on the client,and the protocol buffer message definitions as well. I’ve been using this for over a year so it’s fairly stable, just a bit more complex to get setup and running.

https://github.com/gamemachine/gamemachine/blob/master/server/java/server/src/main/java/io/gamemachine/core/Grid.java

1 Like

like, heroic. puts up hand for c# version

Great script… I get some errors of course about GameManager and PlaceParticles not existing, but also a couple of errors to do with navmeshobstacle:

Assets/scripts/GridTracking.cs(413,29): error CS1061: Type UnityEngine.NavMeshObstacle' does not contain a definition for center’ and no extension method center' of type UnityEngine.NavMeshObstacle’ could be found (are you missing a using directive or an assembly reference?)

Right, just ditch that line then.

you don’t mention your version. but.

in the 5.0 release notes

Improved accuracy and raised limits:

  • NavMeshObstacle supports two basic shapes - cylinder and box for both carving and avoidance.

which I guess is the “center”, without checking

Either way: It’s an optional, additional, navMeshObstacle that can be generated at the tree capsule point. If you don’t need it, bin it :slight_smile: or rework it to use your navMeshObstacle geometries.

and PlaceParticles.cs Place Unity particles individually. · GitHub for place particles (pretty sure I used that one or very similar)

The particles will do that “rotation like a billbard” thing…
I would probably recommend using this in conjunction with TurboForest for best results.

ah ok. I’m using unity 4.6 on my project still, have not fully moved over to 5 yet.

twobob, I completely lost track of this thread.

What exactly did you mean by this? What I was saying was true or false?

When I look at the physics in the profiler, if you start game with Tree Colliders enabled, there are 528 static collliders. When I start game with colliders disabled, those all go away.

If I look around, up at sky, whatever, that 528 number doesn’t change. If I set tree distance to 0 and billboard at 0 so no trees, the colliders are still at 528.

Seems like Unity is keeping 500+ colliders alive all the time?

Not sure how internally it’s calculating which ones though.

I confirmed that the script had a hard limit of about 5000 trees for performance reasons.

Yes the colliders are always enabled (I believe) by the unity terrain engine, that was the point of this script (IIRC)
if you need a more hardcore implementation then https://github.com/gamemachine/game…r/src/main/java/io/gamemachine/core/Grid.java is the place to start converting the more rugged system.

Sorry for asking, is this related to PrepareSceneCullingParameters log in profiler window? Im using speedtrees and from my tests, i have 8 ms delay just because of culling. I have 24 map sectors… Occlusion culling holes are as as big as possible. I still dont get it. Why trees dont get disabled on nested terrain part that gets occluded? Am i actually culling trees one by one? And what actually means dynamic occluded? Should i disable that? I have trees made into prefabs because for layering which allows camera to go through trees. Could also that have some kind of effect on my framerate? Im thinking about moving to different game engine, because the performance is so horrible. Almost seems like unity is not designed for smaller aa games.