Skip to content

Commit b681396

Browse files
committed
Add a page on occlusion culling
1 parent 3fae841 commit b681396

File tree

5 files changed

+308
-0
lines changed

5 files changed

+308
-0
lines changed
313 KB
Loading
294 KB
Loading
166 KB
Loading

tutorials/3d/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
csg_tools
2121
procedural_geometry/index
2222
3d_text
23+
occlusion_culling

tutorials/3d/occlusion_culling.rst

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
.. _doc_occlusion_culling:
2+
3+
Occlusion culling
4+
=================
5+
6+
In a 3D rendering engine, **occlusion culling** is the process of performing
7+
hidden geometry removal.
8+
9+
On this page, you'll learn:
10+
11+
- What are the advantages and pitfalls of occlusion culling.
12+
- How to set up occlusion culling in Godot.
13+
- Troubleshooting common issues with occlusion culling.
14+
15+
Why use occlusion culling
16+
-------------------------
17+
18+
In this example scene with hundreds of rooms stacked next to each other, a
19+
dynamic object (red sphere) is hidden behind the wall in the lit room (on the
20+
left of the door):
21+
22+
.. figure:: img/occlusion_culling_scene_example.png
23+
:align: center
24+
:alt: Example scene with an occlusion culling-friendly layout
25+
26+
Example scene with an occlusion culling-friendly layout
27+
28+
With occlusion culling disabled, all the rooms behind the lit room have to be
29+
rendered. The dynamic object also has to be rendered:
30+
31+
.. figure:: img/occlusion_culling_disabled.png
32+
:align: center
33+
:alt: Example scene with occlusion culling disabled (wireframe)
34+
35+
Example scene with occlusion culling **disabled** (wireframe)
36+
37+
With occlusion culling enabled, only the rooms that are actually visible have to
38+
be rendered. The dynamic object is also occluded by the wall, and therefore no
39+
longer has to be rendered:
40+
41+
.. figure:: img/occlusion_culling_enabled.png
42+
:align: center
43+
:alt: Example scene with occlusion culling enabled (wireframe)
44+
45+
Example scene with occlusion culling **enabled** (wireframe)
46+
47+
Since the engine has less work to do (fewer vertices to render and fewer draw calls),
48+
performance will increase as long as there are enough occlusion culling opportunities
49+
in the scene. This means occlusion culling is most effective in indoor scenes,
50+
preferably with many smaller rooms instead of fewer larger rooms.
51+
52+
.. note::
53+
54+
When using the Clustered Forward rendering backend, the engine already
55+
performs a *depth prepass*. This consists in rendering a depth-only version
56+
of the scene before rendering the scene's actual materials. This is used to
57+
ensure each opaque pixel is only shaded once, reducing the cost of overdraw
58+
significantly.
59+
60+
The greatest performance benefits can be observed when using the Forward
61+
Mobile or Compatibility rendering backends, as neither of those feature a
62+
depth prepass for performance reasons. As a result, occlusion culling will
63+
actively decrease shading overdraw with those rendering backends.
64+
65+
Nonetheless, even when using a depth prepass, there is still a noticeable
66+
benefit to occlusion culling in complex 3D scenes. However, in scenes with
67+
few occlusion culling opportunities, occlusion culling may not be worth the
68+
added setup and CPU usage.
69+
70+
How occlusion culling works in Godot
71+
------------------------------------
72+
73+
.. note::
74+
75+
*"occluder" refers to the shape blocking the view, while "occludee" refers to the object being hidden.*
76+
77+
In Godot, occlusion culling works by rasterizing the scene's occluder geometry
78+
to a low-resolution buffer on the CPU. This is done using
79+
the software raytracing library `Embree <https://github.com/embree/embree>`__.
80+
81+
The engine then uses this low-resolution buffer to test occludees'
82+
:abbr:`AABB (Axis-Aligned Bounding Box)` against the occluder shapes.
83+
The occludee's :abbr:`AABB (Axis-Aligned Bounding Box)` must be *fully occluded*
84+
by the occluder shape to be culled.
85+
86+
As a result, smaller objects are more likely to be effectively culled than
87+
larger objects. Larger occluders (such as walls) also tend to be much more
88+
effective than smaller ones (such as decoration props).
89+
90+
Setting up occlusion culling
91+
----------------------------
92+
93+
The first step to using occlusion culling is to enable the
94+
**Rendering > **Occlusion Culling > Use Occlusion Culling** project setting.
95+
(Make sure the **Advanced** toggle is enabled in the Project Settings dialog to
96+
be able to see it.)
97+
98+
This project setting applies immediately, so you don't need to restart the editor.
99+
100+
After enabling the project setting, you still need to create some occluders. For
101+
performance reasons, the engine doesn't automatically use all visible geometry
102+
as a basis for occlusion culling. Instead, the engine requires a simplified
103+
representation of the scene with only static objects to be baked.
104+
105+
There are two ways to set up occluders in a scene:
106+
107+
.. _doc_occlusion_culling_baking:
108+
109+
Automatically baking occluders (recommended)
110+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
111+
112+
.. note::
113+
114+
Only MeshInstance3D nodes are currently taken into account in the *occluder*
115+
baking process. MultiMeshInstance3D, GPUParticles3D, CPUParticles3D and CSG
116+
nodes are **not** taken into account when baking occluders. If you wish
117+
those to be treated as occluders, you have to manually create occluder
118+
shapes that (roughly) match their geometry.
119+
120+
This restriction does not apply to *occludees*. Any node type that inherits
121+
from GeometryInstance3D can be occluded.
122+
123+
After enabling the occlusion culling project setting mentioned above, add an
124+
OccluderInstance3D node to the scene containing your 3D level.
125+
126+
Select the OccluderInstance3D node, then click **Bake Occluders** at the top of
127+
the 3D editor viewport. After baking, the OccluderInstance3D node will contain
128+
an Occluder3D resource that stores a simplified version of your level's
129+
geometry. This occluder geometry appears as purple wireframe lines in the 3D view
130+
(as long as **View Gizmos** is enabled in the **Perspective** menu).
131+
This geometry is then used to provide occlusion culling for both static and
132+
dynamic occludees.
133+
134+
After baking, you may notice that your dynamic objects (such as the player,
135+
enemies, etc…) are included in the baked mesh. To prevent this, set the
136+
**Bake > Cull Mask** property on the OccluderInstance3D to exclude certain visual
137+
layers from being baked.
138+
139+
For example, you can disable layer 2 on the cull mask, then configure your
140+
dynamic objects' MeshInstance3D nodes to be located on the visual layer 2
141+
(instead of layer 1). To do so, select the MeshInstance3D node in question, then
142+
on the **VisualInstance3D > Layers** property, uncheck layer 1 then check layer
143+
2. After configuring both cull mask and layers, bake occluders again by
144+
following the above process.
145+
146+
Manually placing occluders
147+
^^^^^^^^^^^^^^^^^^^^^^^^^^
148+
149+
This approach is more suited for specialized use cases, such as creating occlusion
150+
for MultiMeshInstance3D setups or CSG nodes (due to the aforementioned limitation).
151+
152+
After enabling the occlusion culling project setting mentioned above, add an
153+
OccluderInstance3D node to the scene containing your 3D level. Select the
154+
OccluderInstance3D node, then choose an occluder type to add in the **Occluder**
155+
roperty:
156+
157+
- QuadOccluder3D (a single plane)
158+
- BoxOccluder3D (a cuboid)
159+
- SphereOccluder3D (a sphere-shaped occluder)
160+
- PolygonOccluder3D (a 2D polygon with as many points as you want)
161+
162+
There is also ArrayOccluder3D, whose points can't be modified in the editor but
163+
can be useful for procedural generation from a script.
164+
165+
.. _doc_occlusion_culling_preview:
166+
167+
Previewing occlusion culling
168+
----------------------------
169+
170+
You can enable a debug draw mode to preview what the occlusion culling is
171+
actually "seeing". In the top-left corner of the 3D editor viewport, click the
172+
**Perspective** button (or **Orthogonal** depending on your current camera
173+
mode), then choose **Display Advanced… > Occlusion Culling Buffer**. This will
174+
display the low-resolution buffer that is used by the engine for occlusion
175+
culling.
176+
177+
In the same menu, you can also enable **View Information** and **View Frame
178+
Time** to view the number of draw calls and rendered primitives (vertices +
179+
indices) in the bottom-right corner, along with the number of frames per second
180+
rendered in the top-right corner.
181+
182+
If you toggle occlusion culling in the project settings while this information
183+
is displayed, you can see how much occlusion culling improves performance in
184+
your scene. Note that the performance benefit highly depends on the 3D editor
185+
camera's view angle, as occlusion culling is only effective if there are
186+
occluders in front of the camera.
187+
188+
To toggle occlusion culling at run-time, set ``use_occlusion_culling`` on the
189+
root viewport as follows:
190+
191+
::
192+
193+
get_tree().root.use_occlusion_culling = true
194+
195+
Toggling occlusion culling at run-time is useful to compare performance on a
196+
running project.
197+
198+
Performance considerations
199+
--------------------------
200+
201+
Design your levels to take advantage of occlusion culling
202+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
203+
204+
**This is the most important guideline.** A good level design is not just about
205+
what the gameplay demands; it should also be built with occlusion in mind.
206+
207+
For indoor environments, add opaque walls to "break" the line of sight at
208+
regular intervals and ensure not too much of the scene can be seen at once.
209+
210+
For large open scenes, use a pyramid-like structure for the terrain's elevation
211+
when possible. This provides the greatest culling opportunities compared to any
212+
other terrain shape.
213+
214+
Avoid moving OccluderInstance3D nodes during gameplay
215+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
216+
217+
This includes moving the parents of OccluderInstance3D nodes, as this will cause
218+
the nodes themselves to move in global space, therefore requiring the :abbr:`BVH
219+
(Bounding Volume Hierarchy)` to be rebuilt.
220+
221+
Toggling an OccluderInstance3D's visibility (or one of its parents' visibility)
222+
is not as expensive, as the update only needs to happen once (rather than
223+
continuously).
224+
225+
For example, if you have a sliding or rotating door, you can make the
226+
OccluderInstance3D node not be a child of the door itself (so that the occluder
227+
never moves), but you can hide the OccluderInstance3D visibility once the door
228+
starts opening. You can then reshow the OccluderInstance3D once the door is
229+
fully closed.
230+
231+
If you absolutely have to move an OccluderInstance3D node during gameplay, use a
232+
primitive Occluder3D shape for it instead of a complex baked shape.
233+
234+
Use the simplest possible occluder shapes
235+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
236+
237+
If you notice low performance or stuttering in complex 3D scenes, it may mean
238+
that the CPU is overloaded as a result of rendering detailed occluders.
239+
Select the OccluderInstance3D node,
240+
increase the **Bake > Simplification** property then bake occluders again.
241+
242+
Remember to keep the simplification value reasonable. Values that are too high
243+
for the level's geometry may cause incorrect occlusion culling to occur, as in
244+
:ref:`doc_occlusion_culling_troubleshooting_false_negative`.
245+
246+
If this still doesn't lead to low enough CPU usage,
247+
you can try adjusting the **Rendering > Occlusion Culling > BVH Build Quality**
248+
project setting and/or decreasing
249+
**Rendering > Occlusion Culling > Occlusion Rays Per Thread**.
250+
You'll need to enable the **Advanced** toggle in the Project Settings dialog to
251+
see those settings.
252+
253+
Troubleshooting
254+
---------------
255+
256+
My occludee isn't being culled when it should be
257+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
258+
259+
**On the occluder side:**
260+
261+
First, double-check that the **Bake > Cull Mask** property in the
262+
OccluderInstance3D is set to allow baking the meshes you'd like. The visibility
263+
layer of the MeshInstance3D nodes must be present within the cull mask for the
264+
mesh to be included in the bake.
265+
266+
Also note that occluder baking only takes meshes with *opaque* materials into
267+
account. Surfaces will *transparent* materials will **not** be included in the
268+
bake, even if the texture applied on them is fully opaque.
269+
270+
Lastly, remember that MultiMeshInstance3D, GPUParticles3D, CPUParticles3D and CSG
271+
nodes are **not** taken into account when baking occluders. As a workaround, you
272+
can add OccluderInstance3D nodes for those manually.
273+
274+
**On the occludee side:**
275+
276+
Make sure **Extra Cull Margin** is set as low as possible (it should usually be
277+
``0.0``), and that **Ignore Occlusion Culling** is disabled in the object's
278+
GeometryInstance3D section.
279+
280+
Also, check the AABB's size (which is represented by an orange box when
281+
selecting the node). This axis-aligned bounding box must be *fully* occluded by
282+
the occluder shapes for the occludee to be hidden.
283+
284+
.. _doc_occlusion_culling_troubleshooting_false_negative:
285+
286+
My occludee is being culled when it shouldn't be
287+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
288+
289+
The most likely cause for this is that objects that were included in the
290+
occluder bake have been moved after baking occluders. For instance, this can
291+
occur when moving your level geometry around or rearranging its layout. To fix
292+
this, select the OccluderInstance3D node and bake occluders again.
293+
294+
This can also happen because dynamic objects were included in the bake, even
295+
though they shouldn't be. Use the
296+
:ref:`occlusion culling debug draw mode <doc_occlusion_culling_preview>` to look
297+
for occluder shapes that shouldn't be present, then
298+
:ref:`adjust the bake cull mask accordingly <doc_occlusion_culling_baking>`.
299+
300+
The last possible cause for this is overly aggressive mesh simplification during
301+
the occluder baking process. Select the OccluderInstance3D node,
302+
decrease the **Bake > Simplification** property then bake occluders again.
303+
304+
As a last resort, you can enable the **Ignore Occlusion Culling** property on
305+
the occludee. This will negate the performance improvements of occlusion culling
306+
for that object, but it makes sense to do this for objects that will never be
307+
culled (such as a first-person view model).

0 commit comments

Comments
 (0)