|
| 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). |
0 commit comments