Skip to content

Commit b4863e7

Browse files
authored
Merge pull request #6160 from Calinou/add-occlusion-culling
Add a page on occlusion culling
2 parents d233074 + ce6daa0 commit b4863e7

File tree

7 files changed

+314
-3
lines changed

7 files changed

+314
-3
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
@@ -23,3 +23,4 @@
2323
3d_text
2424
mesh_lod
2525
visibility_ranges
26+
occlusion_culling

tutorials/3d/mesh_lod.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Mesh level of detail (LOD)
44
==========================
55

66
Level of detail (LOD) is one of the most important ways to optimize rendering
7-
performance in a 3D project, along with occlusion culling.
7+
performance in a 3D project, along with :ref:`doc_occlusion_culling`.
88

99
On this page, you'll learn:
1010

tutorials/3d/occlusion_culling.rst

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

tutorials/3d/visibility_ranges.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
Visibility ranges (HLOD)
44
========================
55

6-
Along with mesh LOD and occlusion culling, visibility ranges are another tool
7-
to improve performance in large, complex 3D scenes.
6+
Along with :ref:`doc_mesh_lod` and :ref:`doc_occlusion_culling`,
7+
visibility ranges are another tool to improve performance in large,
8+
complex 3D scenes.
89

910
On this page, you'll learn:
1011

0 commit comments

Comments
 (0)