mirror of
https://github.com/stan220/godot-docs.git
synced 2026-09-08 18:29:11 +00:00
Add a page on occlusion culling
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
@@ -23,3 +23,4 @@
|
||||
3d_text
|
||||
mesh_lod
|
||||
visibility_ranges
|
||||
occlusion_culling
|
||||
|
||||
@@ -4,7 +4,7 @@ Mesh level of detail (LOD)
|
||||
==========================
|
||||
|
||||
Level of detail (LOD) is one of the most important ways to optimize rendering
|
||||
performance in a 3D project, along with occlusion culling.
|
||||
performance in a 3D project, along with :ref:`doc_occlusion_culling`.
|
||||
|
||||
On this page, you'll learn:
|
||||
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
.. _doc_occlusion_culling:
|
||||
|
||||
Occlusion culling
|
||||
=================
|
||||
|
||||
In a 3D rendering engine, **occlusion culling** is the process of performing
|
||||
hidden geometry removal.
|
||||
|
||||
On this page, you'll learn:
|
||||
|
||||
- What are the advantages and pitfalls of occlusion culling.
|
||||
- How to set up occlusion culling in Godot.
|
||||
- Troubleshooting common issues with occlusion culling.
|
||||
|
||||
Why use occlusion culling
|
||||
-------------------------
|
||||
|
||||
In this example scene with hundreds of rooms stacked next to each other, a
|
||||
dynamic object (red sphere) is hidden behind the wall in the lit room (on the
|
||||
left of the door):
|
||||
|
||||
.. figure:: img/occlusion_culling_scene_example.png
|
||||
:align: center
|
||||
:alt: Example scene with an occlusion culling-friendly layout
|
||||
|
||||
Example scene with an occlusion culling-friendly layout
|
||||
|
||||
With occlusion culling disabled, all the rooms behind the lit room have to be
|
||||
rendered. The dynamic object also has to be rendered:
|
||||
|
||||
.. figure:: img/occlusion_culling_disabled.png
|
||||
:align: center
|
||||
:alt: Example scene with occlusion culling disabled (wireframe)
|
||||
|
||||
Example scene with occlusion culling **disabled** (wireframe)
|
||||
|
||||
With occlusion culling enabled, only the rooms that are actually visible have to
|
||||
be rendered. The dynamic object is also occluded by the wall, and therefore no
|
||||
longer has to be rendered:
|
||||
|
||||
.. figure:: img/occlusion_culling_enabled.png
|
||||
:align: center
|
||||
:alt: Example scene with occlusion culling enabled (wireframe)
|
||||
|
||||
Example scene with occlusion culling **enabled** (wireframe)
|
||||
|
||||
Since the engine has less work to do (fewer vertices to render and fewer draw calls),
|
||||
performance will increase as long as there are enough occlusion culling opportunities
|
||||
in the scene. This means occlusion culling is most effective in indoor scenes,
|
||||
preferably with many smaller rooms instead of fewer larger rooms. Combine
|
||||
this with :ref:`doc_mesh_lod` and :ref:`doc_visibility_ranges` to further improve
|
||||
performance gains.
|
||||
|
||||
.. note::
|
||||
|
||||
When using the Clustered Forward rendering backend, the engine already
|
||||
performs a *depth prepass*. This consists in rendering a depth-only version
|
||||
of the scene before rendering the scene's actual materials. This is used to
|
||||
ensure each opaque pixel is only shaded once, reducing the cost of overdraw
|
||||
significantly.
|
||||
|
||||
The greatest performance benefits can be observed when using the Forward
|
||||
Mobile or Compatibility rendering backends, as neither of those feature a
|
||||
depth prepass for performance reasons. As a result, occlusion culling will
|
||||
actively decrease shading overdraw with those rendering backends.
|
||||
|
||||
Nonetheless, even when using a depth prepass, there is still a noticeable
|
||||
benefit to occlusion culling in complex 3D scenes. However, in scenes with
|
||||
few occlusion culling opportunities, occlusion culling may not be worth the
|
||||
added setup and CPU usage.
|
||||
|
||||
How occlusion culling works in Godot
|
||||
------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
*"occluder" refers to the shape blocking the view, while "occludee" refers to the object being hidden.*
|
||||
|
||||
In Godot, occlusion culling works by rasterizing the scene's occluder geometry
|
||||
to a low-resolution buffer on the CPU. This is done using
|
||||
the software raytracing library `Embree <https://github.com/embree/embree>`__.
|
||||
|
||||
The engine then uses this low-resolution buffer to test occludees'
|
||||
:abbr:`AABB (Axis-Aligned Bounding Box)` against the occluder shapes.
|
||||
The occludee's :abbr:`AABB (Axis-Aligned Bounding Box)` must be *fully occluded*
|
||||
by the occluder shape to be culled.
|
||||
|
||||
As a result, smaller objects are more likely to be effectively culled than
|
||||
larger objects. Larger occluders (such as walls) also tend to be much more
|
||||
effective than smaller ones (such as decoration props).
|
||||
|
||||
Setting up occlusion culling
|
||||
----------------------------
|
||||
|
||||
The first step to using occlusion culling is to enable the
|
||||
**Rendering > **Occlusion Culling > Use Occlusion Culling** project setting.
|
||||
(Make sure the **Advanced** toggle is enabled in the Project Settings dialog to
|
||||
be able to see it.)
|
||||
|
||||
This project setting applies immediately, so you don't need to restart the editor.
|
||||
|
||||
After enabling the project setting, you still need to create some occluders. For
|
||||
performance reasons, the engine doesn't automatically use all visible geometry
|
||||
as a basis for occlusion culling. Instead, the engine requires a simplified
|
||||
representation of the scene with only static objects to be baked.
|
||||
|
||||
There are two ways to set up occluders in a scene:
|
||||
|
||||
.. _doc_occlusion_culling_baking:
|
||||
|
||||
Automatically baking occluders (recommended)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. note::
|
||||
|
||||
Only MeshInstance3D nodes are currently taken into account in the *occluder*
|
||||
baking process. MultiMeshInstance3D, GPUParticles3D, CPUParticles3D and CSG
|
||||
nodes are **not** taken into account when baking occluders. If you wish
|
||||
those to be treated as occluders, you have to manually create occluder
|
||||
shapes that (roughly) match their geometry.
|
||||
|
||||
This restriction does not apply to *occludees*. Any node type that inherits
|
||||
from GeometryInstance3D can be occluded.
|
||||
|
||||
After enabling the occlusion culling project setting mentioned above, add an
|
||||
OccluderInstance3D node to the scene containing your 3D level.
|
||||
|
||||
Select the OccluderInstance3D node, then click **Bake Occluders** at the top of
|
||||
the 3D editor viewport. After baking, the OccluderInstance3D node will contain
|
||||
an Occluder3D resource that stores a simplified version of your level's
|
||||
geometry. This occluder geometry appears as purple wireframe lines in the 3D view
|
||||
(as long as **View Gizmos** is enabled in the **Perspective** menu).
|
||||
This geometry is then used to provide occlusion culling for both static and
|
||||
dynamic occludees.
|
||||
|
||||
After baking, you may notice that your dynamic objects (such as the player,
|
||||
enemies, etc…) are included in the baked mesh. To prevent this, set the
|
||||
**Bake > Cull Mask** property on the OccluderInstance3D to exclude certain visual
|
||||
layers from being baked.
|
||||
|
||||
For example, you can disable layer 2 on the cull mask, then configure your
|
||||
dynamic objects' MeshInstance3D nodes to be located on the visual layer 2
|
||||
(instead of layer 1). To do so, select the MeshInstance3D node in question, then
|
||||
on the **VisualInstance3D > Layers** property, uncheck layer 1 then check layer
|
||||
2. After configuring both cull mask and layers, bake occluders again by
|
||||
following the above process.
|
||||
|
||||
Manually placing occluders
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This approach is more suited for specialized use cases, such as creating occlusion
|
||||
for MultiMeshInstance3D setups or CSG nodes (due to the aforementioned limitation).
|
||||
|
||||
After enabling the occlusion culling project setting mentioned above, add an
|
||||
OccluderInstance3D node to the scene containing your 3D level. Select the
|
||||
OccluderInstance3D node, then choose an occluder type to add in the **Occluder**
|
||||
roperty:
|
||||
|
||||
- QuadOccluder3D (a single plane)
|
||||
- BoxOccluder3D (a cuboid)
|
||||
- SphereOccluder3D (a sphere-shaped occluder)
|
||||
- PolygonOccluder3D (a 2D polygon with as many points as you want)
|
||||
|
||||
There is also ArrayOccluder3D, whose points can't be modified in the editor but
|
||||
can be useful for procedural generation from a script.
|
||||
|
||||
.. _doc_occlusion_culling_preview:
|
||||
|
||||
Previewing occlusion culling
|
||||
----------------------------
|
||||
|
||||
You can enable a debug draw mode to preview what the occlusion culling is
|
||||
actually "seeing". In the top-left corner of the 3D editor viewport, click the
|
||||
**Perspective** button (or **Orthogonal** depending on your current camera
|
||||
mode), then choose **Display Advanced… > Occlusion Culling Buffer**. This will
|
||||
display the low-resolution buffer that is used by the engine for occlusion
|
||||
culling.
|
||||
|
||||
In the same menu, you can also enable **View Information** and **View Frame
|
||||
Time** to view the number of draw calls and rendered primitives (vertices +
|
||||
indices) in the bottom-right corner, along with the number of frames per second
|
||||
rendered in the top-right corner.
|
||||
|
||||
If you toggle occlusion culling in the project settings while this information
|
||||
is displayed, you can see how much occlusion culling improves performance in
|
||||
your scene. Note that the performance benefit highly depends on the 3D editor
|
||||
camera's view angle, as occlusion culling is only effective if there are
|
||||
occluders in front of the camera.
|
||||
|
||||
To toggle occlusion culling at run-time, set ``use_occlusion_culling`` on the
|
||||
root viewport as follows:
|
||||
|
||||
::
|
||||
|
||||
get_tree().root.use_occlusion_culling = true
|
||||
|
||||
Toggling occlusion culling at run-time is useful to compare performance on a
|
||||
running project.
|
||||
|
||||
Performance considerations
|
||||
--------------------------
|
||||
|
||||
Design your levels to take advantage of occlusion culling
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**This is the most important guideline.** A good level design is not just about
|
||||
what the gameplay demands; it should also be built with occlusion in mind.
|
||||
|
||||
For indoor environments, add opaque walls to "break" the line of sight at
|
||||
regular intervals and ensure not too much of the scene can be seen at once.
|
||||
|
||||
For large open scenes, use a pyramid-like structure for the terrain's elevation
|
||||
when possible. This provides the greatest culling opportunities compared to any
|
||||
other terrain shape.
|
||||
|
||||
Avoid moving OccluderInstance3D nodes during gameplay
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This includes moving the parents of OccluderInstance3D nodes, as this will cause
|
||||
the nodes themselves to move in global space, therefore requiring the :abbr:`BVH
|
||||
(Bounding Volume Hierarchy)` to be rebuilt.
|
||||
|
||||
Toggling an OccluderInstance3D's visibility (or one of its parents' visibility)
|
||||
is not as expensive, as the update only needs to happen once (rather than
|
||||
continuously).
|
||||
|
||||
For example, if you have a sliding or rotating door, you can make the
|
||||
OccluderInstance3D node not be a child of the door itself (so that the occluder
|
||||
never moves), but you can hide the OccluderInstance3D visibility once the door
|
||||
starts opening. You can then reshow the OccluderInstance3D once the door is
|
||||
fully closed.
|
||||
|
||||
If you absolutely have to move an OccluderInstance3D node during gameplay, use a
|
||||
primitive Occluder3D shape for it instead of a complex baked shape.
|
||||
|
||||
Use the simplest possible occluder shapes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you notice low performance or stuttering in complex 3D scenes, it may mean
|
||||
that the CPU is overloaded as a result of rendering detailed occluders.
|
||||
Select the OccluderInstance3D node,
|
||||
increase the **Bake > Simplification** property then bake occluders again.
|
||||
|
||||
Remember to keep the simplification value reasonable. Values that are too high
|
||||
for the level's geometry may cause incorrect occlusion culling to occur, as in
|
||||
:ref:`doc_occlusion_culling_troubleshooting_false_negative`.
|
||||
|
||||
If this still doesn't lead to low enough CPU usage,
|
||||
you can try adjusting the **Rendering > Occlusion Culling > BVH Build Quality**
|
||||
project setting and/or decreasing
|
||||
**Rendering > Occlusion Culling > Occlusion Rays Per Thread**.
|
||||
You'll need to enable the **Advanced** toggle in the Project Settings dialog to
|
||||
see those settings.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
My occludee isn't being culled when it should be
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**On the occluder side:**
|
||||
|
||||
First, double-check that the **Bake > Cull Mask** property in the
|
||||
OccluderInstance3D is set to allow baking the meshes you'd like. The visibility
|
||||
layer of the MeshInstance3D nodes must be present within the cull mask for the
|
||||
mesh to be included in the bake.
|
||||
|
||||
Also note that occluder baking only takes meshes with *opaque* materials into
|
||||
account. Surfaces will *transparent* materials will **not** be included in the
|
||||
bake, even if the texture applied on them is fully opaque.
|
||||
|
||||
Lastly, remember that MultiMeshInstance3D, GPUParticles3D, CPUParticles3D and CSG
|
||||
nodes are **not** taken into account when baking occluders. As a workaround, you
|
||||
can add OccluderInstance3D nodes for those manually.
|
||||
|
||||
**On the occludee side:**
|
||||
|
||||
Make sure **Extra Cull Margin** is set as low as possible (it should usually be
|
||||
``0.0``), and that **Ignore Occlusion Culling** is disabled in the object's
|
||||
GeometryInstance3D section.
|
||||
|
||||
Also, check the AABB's size (which is represented by an orange box when
|
||||
selecting the node). This axis-aligned bounding box must be *fully* occluded by
|
||||
the occluder shapes for the occludee to be hidden.
|
||||
|
||||
.. _doc_occlusion_culling_troubleshooting_false_negative:
|
||||
|
||||
My occludee is being culled when it shouldn't be
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The most likely cause for this is that objects that were included in the
|
||||
occluder bake have been moved after baking occluders. For instance, this can
|
||||
occur when moving your level geometry around or rearranging its layout. To fix
|
||||
this, select the OccluderInstance3D node and bake occluders again.
|
||||
|
||||
This can also happen because dynamic objects were included in the bake, even
|
||||
though they shouldn't be. Use the
|
||||
:ref:`occlusion culling debug draw mode <doc_occlusion_culling_preview>` to look
|
||||
for occluder shapes that shouldn't be present, then
|
||||
:ref:`adjust the bake cull mask accordingly <doc_occlusion_culling_baking>`.
|
||||
|
||||
The last possible cause for this is overly aggressive mesh simplification during
|
||||
the occluder baking process. Select the OccluderInstance3D node,
|
||||
decrease the **Bake > Simplification** property then bake occluders again.
|
||||
|
||||
As a last resort, you can enable the **Ignore Occlusion Culling** property on
|
||||
the occludee. This will negate the performance improvements of occlusion culling
|
||||
for that object, but it makes sense to do this for objects that will never be
|
||||
culled (such as a first-person view model).
|
||||
@@ -3,8 +3,9 @@
|
||||
Visibility ranges (HLOD)
|
||||
========================
|
||||
|
||||
Along with mesh LOD and occlusion culling, visibility ranges are another tool
|
||||
to improve performance in large, complex 3D scenes.
|
||||
Along with :ref:`doc_mesh_lod` and :ref:`doc_occlusion_culling`,
|
||||
visibility ranges are another tool to improve performance in large,
|
||||
complex 3D scenes.
|
||||
|
||||
On this page, you'll learn:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user