When occlusion culling is enabled, the engine culls shapes according to the following -
If the shape was visible in the previous frame - or its (invisible) bounding box was “visible”, then it is rendered as usual in the current frame.
If the shape was not visible in the previous frame - then only its invisible bounding box is “rendered”. This acts as a placeholder and does not produce anything in the colour buffer - but serves to allow the occlusion culling to work.
I would find 2 additional features added to this extremely useful - if they are possible!
Firstly, I would like to be able to define an empty (null) shape - which contains no allocated resources to store graphics. But I want to be able to define a bounding box for this null shape which serves the same purpose as in the ordinary occlusion query. I think currently this bounding box is determined automatically by the engine for a given shape. But if a method could be added allowing to override this bounding box with a user defined one then this would serve the purpose.
Secondly, I would like to be able to read whether or not only the bounding box of a shape was “visible” - irrespective of whether any graphics in the shape were visible in the last frame.
The purpose of these features would be to facilitate dynamic loading of shapes based on the visibility of their bounding boxes. And, if the bounding box is defined bigger than the graphics, then this would give a retrieval system advanced warning that graphics will be required. Also, if the bounding box is not visible, then the management system has information to decide if it wants to deallocate the shape.
As for the 2 features you ask for: hm, admittedly neither of them are easy without significant changes to how the occlusion query works now. Read on for my thoughts and idea how to proceed:
Then for each shape we want to query, we do TOcclusionCullingUtilsRenderer.DrawBox. This sets RenderBox.ModelViewProjection, sets vertexes using RenderBox.SetVertexes, renders using RenderBox.Render.
The TCastleRenderUnlitMesh is deliberately a very thin wrapper over a “mesh” concept on GPU. We use it in simple+efficient way (only updating box vertexes and reusing the same TCastleRenderUnlitMesh instance to draw many boxes), because the overhead of drawing “boxes for collision query” has to be minimal. To maximize gains from occlusion query.
So this request would mean you need to supply custom data (indexes and vertexes) and change how TOcclusionCullingUtilsRenderer.DrawBox behaves.
Note that we don’t query (right now) whether the bounding box of the object was visible, if the actual object was visible. When the object was visible, we don’t render the box – we render the actual object, and this will be tested to determine “what to do in next frame”. This can actually cause some noticeable “flapping” in whether we render the object or not – see Occlusion Culling | Manual | Castle Game Engine notes from
Sometimes object state flips between “visible” and “not visible”, making uneven frame render times. This happens when the proper shape is obscured, but its bounding box is not obscured. The issue can in general be ignored — user doesn’t see any “flipping”.
Your request would mean we need to render the “box” (or your custom replacement of it) always, from what I understand.
My overall hesitation here (why I hesitate to “just do it in the engine”) is not really how to solve the 2 details mentioned above, but it’s that we’d somewhat expose to public things that we wanted to keep private. The user should not worry how it works – I like that it’s a matter of flipping the boolean TCastleViewport.OcclusionCulling now. This also allows us to change the rendering code easily, and we’ve done it in the past. Last time when doing this: Big renderer improvements: Correct and automatic blending sorting, more powerful batching (now cross-scene), easier and more reliable occlusion culling and occlusion sorting – Castle Game Engine . Having to maintain additional features is then problematic – they are not used in majority of cases, and they make such renderer refactors much harder, due to having to pass data that is only sometimes actually used.
My proposed solution is to instead delegate this problem to you That is, to be serious:
You can use TCastleRenderUnlitMesh on your side, setting them up for each required object you need to check. You are them free to reuse them or not reuse as much as your architecture allows.
Create query object using glGenQueries(1, @OcclusionQueryId) (you can ceate multiple objects in one go if you know you need them).
Render using
glBeginQuery(QueryTarget, OcclusionQueryId);
// render mesh, e.g. using MyUnlitMesh.Render(...);
glEndQuery(QueryTarget);
Sometime later (best: in the next rendering frame) read it using
function OcclusionQueryHit(const OcclusionQueryId: TGLint): Boolean;
var
SampleCount: TGLuint;
begin
glGetQueryObjectuiv(OcclusionQueryId, GL_QUERY_RESULT,
@SampleCount);
Result := SampleCount > 0;
end;
you are independent then from CGE features and how we expose occlusion culling.
You use TCastleRenderUnlitMesh, which is lean and fast, and you manage it yourself – having freedom to reuse it as much as it makes sense (e.g. if multiple queried object have the same shape).
You decide yourself when to query, and with what mesh to query.