Hi @daniel-holz!
I am building a pretty massive RTS game with 400km2 islands, with hundreds of thousands of rigid bodies on them. I am using ECS/DOTS and I am working on optimizing the physics cost for all this by subdividing the island into grid cells and disabling/enabling rigid bodies in those cells when nothing is moving in the cell (plus a margin around them).
I am using a shared component to divide the elements into the right cells (which puts them in the separate chunks that are cheap to modify using queries), and I add/remove the PhysicsWorldIndex component to enable/disable them.
Doing that I can reduce the “CreateRigidBodiesJob” cost by a lot (one of the most expensive jobs in the largest islands), as it won’t create rigid bodies for elements that don’t have a PhysicsWorldIndex.
But then I found out that many other physics systems are still querying ALL entities that have a PhysicsCollider (no matter if they have a PhysicsWorldIndex or not).
Despite my system disabling about 95% of the bodies (removing the PhysicsWorldIndex) due to lack of movement, the broadphase and narrowphase are still very slow because of this.
Is there a way to prevent all those colliders that don’t have a PhysicsWorldIndex from being included in the broadphase, narrowphase, jacobians and solving systems and jobs?
Is there a better way to implement completely disabling those rigid bodies for ALL calculations? Can it be added if not?
Unity Physics for ECS lacking body sleeping capabilities is really bad for large scale games like mine where there are a ton of physic objects but only a few really move at any given time.
Hi @alemnunez !
This is a very interesting topic, and a nice approach!
Thanks for bringing this up.
Systems querying entities without PhysicsWorldIndex:
That doesn’t sound right to me. All physics systems should only query entities with a PhysicsWorldIndex component. I’ll have a look at that.
Sleeping:
I totally hear you. Until we address this feature, I think your approach of temporarily turning bodies off when they fall below a certain motion threshold is the best for the moment. This, paired with the new incremental broadphase feature should help scale things up.
Naturally it can be a bit tricky to wake these static rigid bodies up again though at the right moment without resorting to complex callback logic based on changes to collisions on these bodies (newly added contact, or removed contact). So, I suggest for simplicity to use an “active region” approach which simply keeps rigid bodies awake within certain regions of interest. I suppose that is more or less what you are doing here with your cells.
I just had a look here, and the systems you mention only operate on the results of the BuildPhysicsWorld system, namely the PhysicsWorld.
Here is an example for the BroadphaseSystem, which doesn’t run its own queries over entities but simply calls this function on the “input” which contains the PhysicsWorld created by the BuildPhysicsWorld:
The other systems you mentioned operate identically:
- The BuildPhysicsWorld system produces the PhysicsWorld which is handed to the BroadphaseSystem for identifying overlapping pairs of rigid bodies at the AABB level.
- This list is handed to the NarrowphaseSystem which performs intersection tests on the colliders in these pairs in parallel. This yields a contact list.
- This contact list is handed to the CreateJacobiansSystem which assembles the data (Jacobians) necessary for producing the contact forces for these contacts. It also adds Jacobians for joints.
- The final list of Jacobians is handed to the SolveAndIntegrateSystem which produces the contact and joint (i.e., constraint) forces, added to the dynamic rigid bodies, and moves the rigid bodies forward in time accordingly during the last phase, the integration phase.
So, the time spent you are observing must be due to something else I suppose, not based on queries of rigid body entities without PhysicsWorldIndex.
It would be interesting to look into what these systems are doing in your case in more detail to understand why you are not getting the performance improvements that you should.
Can you get the PhysicsWorld after the BuildPhysicsWorld is done from the PhysicsWorldSingleton component and check if the number of dynamic bodies in the physics world is what you would expect in your sleeping implementation?
What I am looking at is the systems window in the editor:
You can see the BuildPhysicsWorld is using 15997 entities (which is correct after disabling a ton of rigid bodies by removing the PhysicsWorldIndex).
But all the other systems say they are querying 153702 entities. And it’s because they have queries on just PhysicsCollider alone (see query #6) here in the broadphase system:
Maybe it’s not a problem and they are not being used in those systems in any expensive way, but I wonder why it’s querying all colliders like that without the PhysicsWorldIndex.
About the activation regions, that’s what I am doing, whenever a moving rigid body gets near a deactivated cell +20m, I enable the objects inside that cell.
1 Like
Bonus question while you are here!
I am using procedurally generated meshes for the terrains and unfortunately that forces me to use mesh colliders which make the narrowphase pretty expensive.
I currently have mesh colliders for each area (a farm, a city block) and each road or intersection so they aren’t too large, but it’s probably still checking every single triangle in the mesh in the narrow phase.
I can subdivide the terrain into small colliders however I want during generation, but that increases the number of RigidBodies, making “CreateRigidBodiesJob” more expensive and also making broadphase worse.
So I am wondering how the engine handles compound colliders in the narrow phase. Does it use the bounds of each sub-collider first to skip entire colliders/meshes without checking individual triangles?
(if that’s the case, I could subdivide the colliders into smaller meshes, but compounding them to get the best of both worlds)
MeshColliders use their own BVH internally (a middle phase) to find the overlapping candidate triangles before performing any actual intersections with the triangles.
It’s obviously still more costly than using primitives or convex meshes, but unless your meshes have very large triangle counts, the performance should be decent.
You can surely break them up into pieces to reduce the workload and put them into compounds, which themselves also use a BVH (middle phase), but that might give you no significant gain.
Reducing the triangle count generally helps.
Or using the TerrainCollider for the ground could also help (which implemens a height field) and only adding concave MeshColliders for buildings and the like.
Finally, using enclosing convex meshes for certain colliders that are rather convex rather than MeshColliders could also help.
1 Like
Interesting. I’ll check where these could come from. I am not seeing them in code…
What about the jobs that these systems spawn? I would expect a significantly reduced amount of time spent by the BVH building jobs when you reduce the number of entities by 95%:
- BildFirstNLevelsJob
- BuildBranchesJob
- FinalizeTreeJob
If you do get a considerable gain here, then your approach works.
1 Like
I am using meshes with vertex painting and irregular shapes for the terrain (with some vertical triangles for curbs, and will probably support some terrain deformation like craters and trenches, too), so I don’t think TerrainColliders are a good solution for my case.
But given that compound colliders have their own BVH, I will try subdividing some of the larger colliders (or more triangle dense) into compound colliders with multiple sub-colliders with axis oriented bounding boxes.
I think that should work way faster when I have 100 pieces of debris sitting on a large farm collider that has 1000 triangles, so instead of checking 100 x 1000 triangles, it can quickly check which of the 10 sub-parts of that huge area the debris is sitting on and just do 100*10 + 100*100 = 100*110 checks or something like that, which should be ~9 times cheaper if all the checks had similar costs (even if it’s 3 times cheaper it’s a big win).
1 Like
Yes, totally. If you know the shapes of objects (in your case, arrangements in the UV plane) then this user-guided approach would allow creating case-optimized BVH middle phase layers that can speed up the intersection calculations in the narrow phase a lot.
Exploiting known arrangements like this is likely to create BVHs with a layout that is better than the layout of the automatically created ones in the MeshCollider. This can also lead to time savings during runtime MeshCollider creation.
1 Like
Did you have a look at whether you do see a gain in these jobs?
Note that these would only be run for static rigid bodies if they are changed and if you don’t use the new incremental broadphase feature.
The NarrowphaseSystem and the rest of the pipeline should also just perform any work for dynamic entities. So unless you have a large number of those, the resultant jobs shouldn’t eat up much time.
I’ll see why that PhysicsCollider query is even there, leading to that very large misleading entity count for these systems.
It might be because these systems depend on the PhysicsCollider data and we add an explicit dependency on that data in these systems to make sure the job ordering is done in a way that respects this.