I have had trouble figuring out whether precomputed visibility works on a per instance, per primitive component or per actor basis, as well as what the exact conditions necessary for any instance/primitive comp/actor to function as an occluder or occludable in precomputed visibility.
As such I have done a test, with a map containing a StaticMeshActor floor with its mobility set to static, a StaticMeshActor wall higher than the PlayAreaHeight also set to static, and several StaticMeshActor cubes hidden behind the wall, set to static as well.
They all have BasicShapeMaterial as their material, and there is both a LightmassImportanceVolume and PrecomputedVisibilityVolume covering the entire map. Static Lighting is allowed, and r.AllowPrecomputedVisibility is set to 1. I have baked lighting and precomputed visibility.
And yet when placing the camera behind the wall, well inside a precomputed visibility cell, no primitive is statically occluded. In fact, disabling occlusion queries shows the cubes behind the wall as unoccluded (provided they are not frustrum culled).
I’ve looked at the doc and everywhere else online and have no idea what I’m doing wrong, if anything. I know precomputed visibility used to be broken for a few UE5 versions but that was supposed to be fixed. Am I missing something or is precomputed visibility broken again?
Precomputed Visibility in Unreal Engine is evaluated per primitive component, not per actor or per instance. Actors are simply containers, what actually gets marked as visible or hidden are individual UPrimitiveComponents. Instanced Static Meshes are a special case, all instances share a single visibility state.
A common misunderstanding is that Precomputed Visibility performs static occlusion, which it does not. Instead, Precomputed Visibility is essentially a precomputed data set that divides the world into discrete regions (cells) and records which primitives may be visible from each region before the game runs. It is not camera-based. Each cell stores a list of potentially visible primitives.
Occlusion culling is what actually uses this data. Rather than performing expensive occlusion tests on every primitive, the system first uses Precomputed Visibility to narrow down the set of candidates. Occlusion culling then operates only on the primitives associated with the relevant cells, significantly reducing the workload.
as the cells shown in the image, occlusion culling first filters the relevant cells, retrieves the list of potentially visible primitives, and then performs occlusion tests only on those primitives.
Hope this helps clarify the issue. Let me know if you have any further questions.
Thank you, while there was confusion over whether cells stored a list of actor or a list of primitive, I did understand that each cell stored a list of which actor/primitive might be visible while the camera overlaps it.
Hi,
I’m testing precomputed visibility on UE5.6 and I noticed from source code that r.VisualizeOccludedPrimitives (GVisualizeOccludedPrimitives) will render a green box if occluded by occlusion queries but it renders a dark red box if occluded by static occlusion.
I’ve fixed it adding the lines between comments in FEmbreeAggregateMesh::IntersectLightRay function (Embree.cpp file) inside the #if USE_EMBREE_MAJOR_VERSION >= 3 section just before its #else.
This makes everything works, static culled meshes amount is listed in stat initviews tab and culled boxes are dark red and not green with r.VisualizeOccludedPrimitives on.
UnrealLightmass.exe must be recompiled and precomputed visibility rebuilt.
#if USE_EMBREE_MAJOR_VERSION >= 3
if (EmbreeRayHit.ray.tfar >= 0.0f && EmbreeRayHit.hit.geomID != -1 && EmbreeRayHit.hit.primID != -1)
{
[...]
}
// Fix PrecomputedVisibility. In Embree.cpp rtcOccluded1 set tfar to -inf if found a hit, must set bIntersects true to make PrecomputedVisibility works
else if (!bFindClosestIntersection && EmbreeRayHit.ray.tfar < 0.f)
{
ClosestIntersection = FLightRayIntersection::None();
ClosestIntersection.bIntersects = true;
} // --------------------------------------------------------------------------------------------------------------------------------------------------------
else
{
EmbreeContext.TransmissionAcc.Resolve(ClosestIntersection.Transmission);
}
#else
Unfortunately, none of these responses answer the original question that OP posed, which is that the Statically occluded primitives part of the stat initviews does not appear.
In fact, there is even more evidence of this when r.AllowOcclusionQueries is set to 0, and r.VisualizeOccludedPrimitives 1 … because none of the occluded primitive debug boxes are being drawn in that case:
With r.AllowOcclusionQueries=0, you can see that statically occluded primitives are still being occluded correct (indicated by the dark red debug boxes on the right). With occlusion queries enabled, you can see that they both work in tandem