GPU Occlusion Culling in Unity 6 doesn't work for Entities

I have just upgraded from Unity 2022.3 to Unity 6 Preview to take advantage of the new GPU Occlusion Culling, but in my initial tests it doesn’t seem to do any culling for entities, just game objects.

I assumed that the GPU Resident Drawer using BatchRendererGroup would mean that both Game Objects and Entities would be using occlusion culling.

Is GPU Occlusion Culling only for game objects? Are there plans to add it to entities in a future version of Entities Graphics? Burst Occlusion Culling was deprecated so I think there aren’t any options for occlusion culling right now.

It is GameObjects only.

If you aren’t using LODs, use LODs. I’ve yet to encounter an ECS project where well-designed LODs were insufficient.

Personally, I think GPU occlusion culling is not particularly effective given Unity’s rendering architecture around shaders, meshes, and materials. You still have to conservatively send all those draw commands to the GPU. For an engine that relied upon unified UBER shaders and bindless textures and multi-draw commands, GPU occlusion culling makes a lot more sense. But in Unity’s demo where they first showed off their GPU occlusion culling, they made a rock cover nearly half the screen and the framerate barely increased.

CPU occlusion culling would make a lot more sense for ECS, but making that performant enough on the CPU is a huge challenge. It is something I would be very interested in experimenting with if I had a scene that needed it.

3 Likes

Thanks @DreamingImLatios!

I am already using LODs, but it’s still painful a ton of stuff getting rendered in the Frame Debugger that is completely occluded behind a hill in a huge island. I was hoping GPU Occlusion Culling would help with that.

I think the GPU Occlusion culling at least is culling the terrain tiles that are behind the hill (which are game objects), but it doesn’t really save that much.

It might be worth turning on depth prepass if you haven’t already. I’d be very curious to see a profiler cpature or even a test scene if you’d be willing to share.

My suspicion is that your LODs aren’t low-enough poly. But I won’t rule out that you have a real use case for occlusion culling. I have a highly customized version of Entities Graphics that makes it easy for me to experiment with different culling algorithms, and I’m always open to trying to make it faster for other people’s scenes.

I have depthNormals pre-pass enabled and seems to be writing the right data to the texture:

But then I reach the draw opaques pass and it’s drawing everything behind the hill anyway:

It looks like the DrawDepthNormals pre-pass is writing to:
_CameraDepthTexture_2560x1440_D32_SFloat_S8_UInt_Tex2D

In between the prepass and the opaques I have these passes:

Screenshot 2024-08-28 170053

Copy Depth seems to be copying the depth texture to: DBufferDepth_2560x1440_D32_SFloat_S8_UInt_Tex2D

The DBuffer pass doesn’t seem to be doing much (probably trying to draw the DBuffer technique decals to the depth buffer?), doesn’t have any inputs and it has both textures as output and they don’t seem to change.
_DBufferTexture0_2560x1440_R8G8B8A8_SRGB_Tex2D
DBufferDepth_2560x1440_D32_SFloat_S8_UInt_Tex2D

Then finally ClearDepthStencilView pass has this texture as output (also 100% black)
_CameraDepthAttachment_2560x1440_D32_SFloat_S8_UInt_Tex2D

Once in the DrawOpaque batches, in the input textures I see they have:
_DBufferTexture0_2560x1440_R8G8B8A8_SRGB_Tex2D
Which doesn’t have anything inside (100% black).

Sounds like somehow the opaques are not getting the right depth texture, but I am not sure why. Looks like the one with float types is the one with the actual depth texture but the opaques are using the R8G8B8A8 texture instead?
_DBufferTexture0_2560x1440_R8G8B8A8_SRGB_Tex2D

Do you know why this could be happening?

Oh my god, I just run through all my URP settings and found out Depth Priming Mode was disabled, so it wasn’t using the depth prepass to discard fragments. Now it doesn’t seem to be drawing all those fragments in the background anymore!

The number of triangles appears to still be the same (still needs to check each triangle against the depth buffer) but I guess it should be a little faster than actually drawing all those pixels multiple times.

I’d imagine it would be a lot faster. But it is hard to say given you haven’t provided any performance numbers. The next thing is going to be making sure your triangle count is reasonable. LODs help with that. So would occlusion culling, but the issue with occlusion culling for that purpose is that it is very easy for there to be spots where there isn’t much occlusion and your polygon count blows up anyways. You could specifically design your level to prevent that, but at that point, you would then have the information to disable entities in ECS depending on player location.

Yes, I have LODs, impostors and/or distance culling on everything. I still have to add some simpler LODs for some stuff, but it’s getting there. My game is an RTS with huge procedural islands and you can move the camera freely, so I definitely need to optimize everything to just cubes when very far away if I want to keep most stuff making sense in the distance.

I will probably implement some LOD Crossfade for ECS so I can swap those LODs much closer to the camera without being too noticeable when they are swapped.

Also thinking about adding simple shadow proxy entities to those LODs to make the shadow pass faster. I am using pretty long shadow distances so it’s drawing a ton of stuff there and takes the same amount of time as the DepthNormals pass or the DrawOpaques pass.

I have a working version of LOD Crossfade in my framework if you’d like to try it. Though it sounds like you are using your own custom renderer?

1 Like

I am using regular Entities Graphics stuff, just did some optimizations on the LODs to bake them into fewer entities and switch the mesh and materials.

I tried Latios a few months ago and was having serious trouble with the bootstrapping stuff, I may give it a try again. Is there a way to just use the LOD Crossfade or it depends on all the other stuff like the custom transforms and all that?

The framework has a Unity Transforms mode (add the LATIOS_TRANSFORMS_UNITY scripting define symbol). Unless you use or have a package that uses Unity.Deformations, all the basics will just work (assuming you add the correct bootstrap) and the really advanced stuff will have feature equivalents.

Unfrotunately, there’s no way to isolate the LOD Crossfade. I had to rewrite the culling pipeline to make actually performant skinned mesh renderers, and it is in that rewritten pipeline where I was able to insert LOD Crossfade.

If you need help with setting up the bootstrap, feel free to PM me or jump into the framework’s Discord, and I can guide you through it.

1 Like