How to Occlusion Culling properly

You don’t need to set the position of the occluders as you make them children of the same parent than the regular meshes: they will automatically inherit the same parent transformation.

However, because they now inherit the parent transformation and we compute their bounding boxes in world coordinates, we must counter this transformation when computing the min/max vectors because the parent transformation will be applied by the system before display. We do it by multiplying by the inverse transformation (see lines 98/99):

But if I don’t set the position of the occluders, they somehow look like this:


The position of the bounding box is way off to the actual mesh it is referring to. I don’t know if this is of relevance, but does it affect given that I’m using version 4.2?

I’m not sure if If I can replicate it in the playground as I try to make it exactly as you suggested.

You should try with 5.0, as there has been some changes in the way the gltf/glb files are loaded.

1 Like

I will need to confirm, but if in any case I can’t is there another way to potentially work around this?

Upon checking we are actually using “@babylonjs/core” and “@babylonjs/loaders” version 4.2.1.
I’ll try with “babylonjs” latest module and hopefully see some new results.

Edit: I tried with the latest babylonjs but it still does not work. I still have the issue of it. I’m not really sure why the position of the bounding box is not translated properly it would seem. It still uses the mesh it self as the copy even though we have update the bounding box :frowning:

I’m also checking around the documentation and there seems to be frustum culling but would it be a different thing entirely or the same?

Hello @VtuberFan just checking in, do you have any more questions? :smile:

@carolhmj I would say yes, but I would probably put it in a different topic/thread all together (Since it is not related with occlusion culling).

@Evgeni_Popov thanks again for the help!

2 Likes

@Evgeni_Popov It appears that alpha tested meshes do not occlude other meshes with respect to occlusion queries. I was able to “break” the occlusion PG by forcing the plane to be alpha tested (see https://playground.babylonjs.com/#QDAZ80#295). However, if I set the rendering group of the sphere to 1 (comment in line 25) and do scene.setRenderingAutoClearDepthStencil(1, false, false, false) as you suggest, then the plane occludes the sphere again. My question would be is this a reliable solution for testing occlusion by alpha-tested or alpha-blended meshes that actually have transparency, as opposed to just having the properties set. Also (potentially unrelated so I can ask in a new forum topic if needed) are there performance implications to having meshes designated as tested or blended that does not actually have transparency (i.e. the texture uses an image with no alpha or alpha 1 everywhere)?

The problem is that alpha tested and blended (transparent) meshes are displayed after the opaque meshes by the RenderingGroup class for proper rendering. So, if your occluder is alpha tested/blended it will be rendered after the meshes that you are testing for occlusion if those meshes are opaque, hence why it is failing.

You can make it work in your PG if you also flag the sphere as alpha tested, as it will move the mesh into the alpha tested queue alongside with the wall. But that is more of a hack than anything else…

Setting a lower renderingGroupId for the occluders than for the occludees is probably the best option.

Flagging a mesh (material) as alpha tested has a performance hit (because of a discard instruction in the shader that prevents some optimizations at the GPU level) and flagging it as alpha blended has also a performance hit because of the blending the GPU must apply. Depending on the GPU those hits can be small or big, so you should definitely don’t use alpha tested/blended if your material does not need it/them.

2 Likes

Thanks for the fantastic explanation.

@Evgeni_Popov The scene.setRenderingAutoClearDepthStencil(1, false, false, false) doesn’t appear to work with alpha blended meshes in that the occluded mesh always shows through the occluder when the occluder is alpha-blended and the occluded mesh has a greater renderingGroupId (https://playground.babylonjs.com/#8MTV22#29). How can I get this to work with alpha-blended occluders?

Blended meshes are supposed to be at least partially transparent (else blending would not be required). And if they are transparent, they can’t occlude what there is behind.

More technically, alpha blended meshes don’t write to the depth buffer, that’s why occlusion queries don’t work. You can force those meshes to write into the depth buffer by setting material.forceDepthWrite=true and the occlusion queries will work. But your alpha blended meshes won’t probably behave as expected as they now write to the depth buffer (so they won’t blend with what is behind them).

Thanks. So if you have a mesh with an image texture with transparency, you cannot occlude another mesh with the opaque portions of the mesh from the point of view of the occlusion query and further alpha blended meshes need to be in the same rendering group as any meshes they occlude so that the blending works properly? So in the case of our mesh with an image, we are better off using alpha test and using FXAA post processing to prevent aliasing of the opaque edges?

That works in that the blended mesh in rendering group 0 occludes the mesh with rendering group 1. However, as I think you expected, the entire mesh occludes the mesh in render group 1, even the non-opaque areas of the mesh as shown in the image
image
Is there a way to have just the non-opaque areas write to the depth buffer, maybe using a custom shader? Or some way to have the depth and stencil buffers cleared if alpha != 1?

I have a hard time understanding what an occlusion query should return when the occluder is (semi)-transparent? Even if the occluded mesh is entirely behind this semi-transparent, what do you expect the result to be?

To me, the result should be “not occluded” because the mesh is (partially) visible behind the occluder. So, when using a transparent occluder, the occlusion query result should always be “false” (meaning, the mesh is visible behind the occluder). That’s why I don’t really understand what you want to achieve by using transparent occluders?

[EDIT] Note that an occlusion query is a yes/no answer for the whole mesh, you can’t query on a per-pixel basis.

An explanation of the use case is probably warranted. The disc has a texture that displays video sent from a server. I want to cull out video frames when no part of the disc is visible. The content that could occlude the disc is typically static (animated images or 3D models would be the exception). In the case above, it would suffice to cull the video if the alpha-tested version of the material would fully occlude the disc, but I don’t see a way to say treat this mesh as blended for the purposes of rendering but tested for the purposes of occlusion queries. Given the difficulties and that occlusion queries don’t appear to work in Safari, though, I wonder if it would be better for a mostly static case to bake a depth map (using alpha-tested meshes only) and cull out any video feeds when the disc is fully within a region of the map with a depth value that is equal to or less then the disc z value. Thoughts as to that approach?

In any case I have blended meshes in the scene and having the disc on a different rendering group causes it to show through the blended meshes which basically means I can’t cull any video if the disc goes behind a blended mesh, even if the opaque portion of the mesh fully occludes the disc.

That’s not possible, occlusion queries can’t test some parts of a mesh that would be opaque and some others that would be transparent.

I don’t remember if I already said it, but occlusion queries work by rasterizing the bounding box of the (potentially) occluded mesh, and if all the pixels of the bounding box are occluded (meaning, their depth is greater than the depth in the depth buffer - these checks explain why the occluders must be rendered before the occludees: so that the depth buffer contains the data from the occluders) then the query concludes to an occlusion.

So, in your case, you would need that your occluder generates data in the deph buffer for opaque pixels and does not update the depth buffer for transparent pixels, but those pixels should still be displayed on screen…

You can do it with a custom material (or a material plugin) by writing 1 (bigger Z value, so farthest one) to the depth buffer if the alpha component is below some threshold. By writing 1 you basically do the same thing than using the discard instruction (which is what the alpha test is doing) but the fragment shader still executes and a pixel is rendered on screen.

Example:

However, you still have a problem that you can’t really solve, which is that your alpha blended mesh (occluder) is rendered before the occluded mesh (for the occlusion query to work), but for alpha blending to work the transparent meshes must be displayed AFTER the opaque meshes… You can see this in the PG, the cloud is not alpha blended with the disc when the disc is partially visible through it.

I don’t see a way to solve that problem, as the two constraints are opposite…

1 Like

That should work, but the performance may be subpar because you will need to retrieve the depth buffer on the CPU side and this is a slow operation.

I see the problem you are talking about, but the larger problem is that the occluder appears over all meshes, even ones in the same rendering group. I had an idea to create a depth mask mesh that writes to depth but not color using the same texture but alpha tested (https://playground.babylonjs.com/#8MTV22#35). This actually does the best I think I can get, which is that meshes in the same rendering group are blended correctly, but meshes in a later rendering group are fully occluded by the area with alpha above the cutoff. The only problem with this approach is the disc renders on top of the areas with alpha below the cutoff. Is there any way to fix that, or is this the best I can do?

Using the approach above, if I put the occluder in group 2 so it renders after the disc, but I put the depth mask in group 0 so the disc renders after it, would that work to occlude the disc using the area above the alpha cutoff, but to still shown the disc behind the areas below the cutoff? I tried it (https://playground.babylonjs.com/#8MTV22#38), but the disc still renders on top of the plane, even though the plane is in a lower rendering group than the disc. Any ideas what I’m missing? if this could work, can I use custom render order instead of rendering groups? Putting all the scene meshes in group 2 would be a pain.