Skip to content

Commit 59636f7

Browse files
authored
Merge pull request godotengine#6161 from Calinou/add-mesh-lod-visibility-ranges
Add pages on mesh LOD and visibility ranges
2 parents 41ce4f8 + a4391f1 commit 59636f7

7 files changed

+368
-0
lines changed
Loading
Loading
108 KB
Loading
22.8 KB
Loading

tutorials/3d/index.rst

+2
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@
2121
csg_tools
2222
procedural_geometry/index
2323
3d_text
24+
mesh_lod
25+
visibility_ranges

tutorials/3d/mesh_lod.rst

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
.. _doc_mesh_lod:
2+
3+
Mesh level of detail (LOD)
4+
==========================
5+
6+
Level of detail (LOD) is one of the most important ways to optimize rendering
7+
performance in a 3D project, along with occlusion culling.
8+
9+
On this page, you'll learn:
10+
11+
- How mesh LOD can improve your 3D project's rendering performance.
12+
- How to set up mesh LOD in Godot.
13+
- How to measure mesh LOD's effectiveness in your project
14+
(and alternatives you can explore if it doesn't meet your expectations).
15+
16+
Introduction
17+
-------
18+
19+
Historically, level of detail in 3D games involved manually authoring meshes
20+
with lower geometry density, then configuring the distance thresholds at which
21+
these lower-detailed meshes should be drawn. This approach is still used today
22+
when increased control is needed.
23+
24+
However, in projects that have a large amount of detailed 3D assets, setting up
25+
LOD manually can be a very time-consuming process. As a result, automatic mesh
26+
decimation and LOD configuration is becoming increasingly popular.
27+
28+
Godot provides a way to automatically generate less detailed meshes for LOD
29+
usage on import, then use those LOD meshes when needed automatically. This is
30+
completely transparent to the user.
31+
The `meshoptimizer <https://meshoptimizer.org/>`__ library is used for LOD mesh
32+
generation behind the scenes.
33+
34+
Mesh LOD works with any node that draws 3D meshes. This includes MeshInstance3D,
35+
MultiMeshInstance3D, GPUParticles3D and CPUParticles3D.
36+
37+
Visual comparison
38+
-----------------
39+
40+
Here is an example of LOD meshes generated on import. Lower detailed meshes
41+
will be used when the camera is far away from the object:
42+
43+
.. figure:: img/mesh_lod_comparison_shaded.png
44+
:align: center
45+
:alt: From most detailed (left) to least detailed (right), shaded view
46+
47+
From most detailed (left) to least detailed (right), shaded view
48+
49+
Here's the same image with wireframe rendering to make the decimation easier to see:
50+
51+
.. figure:: img/mesh_lod_comparison_wireframe.png
52+
:align: center
53+
:alt: From most detailed (left) to least detailed (right), wireframe view
54+
55+
From most detailed (left) to least detailed (right), wireframe view
56+
57+
.. seealso::
58+
59+
If you need to manually configure level of detail with artist-created meshes,
60+
use :ref:`doc_visibility_ranges` instead of automatic mesh LOD.
61+
62+
Generating mesh LOD
63+
-------------------
64+
65+
By default, mesh LOD generation happens automatically for imported 3D scenes
66+
(glTF, .blend, Collada, FBX). Once LOD meshes are generated, they will
67+
automatically be used when rendering the scene. You don't need to configure
68+
anything manually.
69+
70+
However, mesh LOD generation does **not** automatically happen for imported 3D
71+
meshes (OBJ). This is because OBJ files are not imported as full 3D scenes by
72+
default, but only as individual mesh resources to load into a MeshInstance3D
73+
node (or GPUParticles3D, CPUParticles3D, ...).
74+
75+
To make an OBJ file have mesh LOD generated for it, select it in the FileSystem
76+
dock, go to the Import dock, change its **Import As** option to **Scene** then
77+
click **Reimport**:
78+
79+
.. figure:: img/mesh_lod_obj_import.png
80+
:align: center
81+
:alt: Changing the import type on an OBJ file in the Import dock
82+
83+
Changing the import type on an OBJ file in the Import dock
84+
85+
This will require restarting the editor after clicking **Reimport**.
86+
87+
Comparing mesh LOD visuals and performance
88+
------------------------------------------
89+
90+
To disable mesh LOD in the editor for comparison purposes, use the
91+
**Disable Mesh LOD** advanced debug draw mode. This can be done using the menu
92+
in the top-left corner of the 3D viewport (labeled **Perspective** or
93+
**Orthogonal** depending on camera mode):
94+
95+
.. figure:: img/mesh_lod_disable_lod.png
96+
:align: center
97+
:alt: Disabling mesh LOD in the 3D viewport's top-left menu
98+
99+
Disabling mesh LOD in the 3D viewport's top-left menu
100+
101+
Enable **View Frame Time** in the same menu to view FPS in the top-right corner.
102+
Also enable **View Information** in the same menu to view the number of primitives
103+
(vertices + indices) rendered in the bottom-right corner.
104+
105+
If mesh LOD is working correctly in your scene and your camera is far away
106+
enough from the mesh, you should notice the number of drawn primitives
107+
decreasing and FPS increasing when mesh LOD is left enabled (unless you are
108+
CPU-bottlenecked).
109+
110+
To see mesh LOD decimation in action, change the debug draw mode to
111+
**Display Wireframe** in the menu specified above, then adjust the
112+
**Rendering > Mesh LOD > LOD Change > Threshold Pixels** project setting.
113+
114+
Configuring mesh LOD performance and quality
115+
--------------------------------------------
116+
117+
You can adjust how aggressive mesh LOD transitions should be in the root viewport
118+
by changing the **Rendering > Mesh LOD > LOD Change > Threshold Pixels** project
119+
setting. To change this value at run-time, set ``mesh_lod_threshold`` on the
120+
root viewport as follows:
121+
122+
::
123+
124+
get_tree().root.mesh_lod_threshold = 4.0
125+
126+
Each viewport has its own ``mesh_lod_threshold`` property, which can be set
127+
independently from other viewports.
128+
129+
The default mesh LOD threshold of 1 pixel is tuned to look *perceptually*
130+
lossless; it provides a significant performance gain with an unnoticeable loss
131+
in quality. Higher values will make LOD transitions happen sooner when the
132+
camera moves away, resulting in higher performance but lower quality.
133+
134+
If you need to perform per-object adjustments to mesh LOD, you can adjust how
135+
aggressive LOD transitions should be by adjusting the **LOD Bias** property on
136+
any node that inherits from GeometryInstance3D. Positive values will make LOD
137+
transitions happen sooner than usual (resulting in lower quality but higher
138+
performance). Negative values will make LOD transitions happen earlier than
139+
usual (resulting in higher quality but lower performance).
140+
141+
Additionally, ReflectionProbe nodes have their own **Mesh LOD Threshold** property
142+
that can be adjusted to improve rendering performance when the reflection probe
143+
updates. This is especially important for ReflectionProbes that use the **Always**
144+
update mode.
145+
146+
.. note::
147+
148+
When rendering the scene, mesh LOD selection uses a screen-space metric.
149+
This means it automatically takes camera field of view and viewport
150+
resolution into account. Higher camera FOV and lower viewport resolutions
151+
will make LOD selection more aggressive; the engine will display heavily
152+
decimated models earlier when the camera moves away.
153+
154+
As a result, unlike :ref:`doc_visibility_ranges`, you don't need to do
155+
anything specific in your project to take camera FOV and viewport resolution
156+
into account.
157+
158+
Using mesh LOD with MultiMesh and particles
159+
-------------------------------------------
160+
161+
For LOD selection, the point of the node's :abbr:`AABB (Axis-Aligned Bounding Box)`
162+
that is the closest to the camera is used as a basis. This applies to any kind
163+
of mesh LOD (including for individual MeshInstance3D)s, but this has some implications
164+
for nodes that display multiple meshes at once, such as MultiMeshInstance3D,
165+
GPUParticles3D and GPUParticles3D. Most importantly, this means that all
166+
instances will be drawn with the same LOD level at a given time.
167+
168+
If you are noticing incorrect LOD selection with GPUParticles3D, make sure
169+
the node's visibility AABB is configured by selecting the GPUParticles3D
170+
node and using **GPUParticles3D > Generate AABB** at the top of the 3D
171+
viewport.
172+
173+
If you have instances in a MultiMesh that are far away from each other, they
174+
should be placed in a separate MultiMeshInstance3D node. Doing so will also
175+
improve rendering performance, as frustum and occlusion culling will be able to
176+
cull individual nodes (while they can't cull individual instances in a
177+
MultiMesh).

tutorials/3d/visibility_ranges.rst

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
.. _doc_visibility_ranges:
2+
3+
Visibility ranges (HLOD)
4+
========================
5+
6+
Along with mesh LOD and occlusion culling, visibility ranges are another tool
7+
to improve performance in large, complex 3D scenes.
8+
9+
On this page, you'll learn:
10+
11+
- What visibility ranges can do and which scenarios they are useful in.
12+
- How to set up visibility ranges (manual LOD) in Godot.
13+
- How to tune visibility ranges for best performance and quality.
14+
15+
.. seealso::
16+
17+
If you only need meshes to become less detailed over distance but don't have
18+
manually authored LOD meshes, consider relying on automatic
19+
:ref:`doc_mesh_lod` instead.
20+
21+
Note that automatic mesh LOD and visibility ranges can be used at the same
22+
time, even on the same mesh.
23+
24+
How it works
25+
------------
26+
27+
Visibility ranges can be used with any node that inherits from GeometryInstance3D.
28+
This means they can be used not only with MeshInstance3D and MultiMeshInstance3D
29+
for artist-controlled :abbr:`HLOD (Hierarchical Level of Detail)`, but also
30+
GPUParticles3D, CPUParticles3D, Label3D, Sprite3D, AnimatedSprite3D and CSGShape3D.
31+
32+
Since visibility ranges are configured on a per-node basis, this makes it possible
33+
to use different node types as part of a :abbr:`LOD (Level of Detail)` system.
34+
For example, you could display a MeshInstance3D representing a tree when up close,
35+
and replace it with a Sprite3D impostor in the distance to improve performance.
36+
37+
The benefit of :abbr:`HLOD (Hierarchical Level of Detail)` over a traditional
38+
:abbr:`LOD (Level of Detail)` system is its hierarchical nature. A single larger
39+
mesh can replace several smaller meshes, so that the number of draw calls can be
40+
reduced at a distance, but culling opportunities can be preserved when up close.
41+
For example, you can have a group of houses that uses individual MeshInstance3D
42+
nodes (one for each house) when up close, but turns into a single MeshInstance3D
43+
that represents a less detailed group of houses (or use a MultiMeshInstance3D).
44+
45+
Lastly, visibility ranges can also be used to fade certain objects entirely when
46+
the camera gets too close or too far. This can be used for gameplay purposes,
47+
but also to reduce visual clutter. For example, Label3D nodes can be faded using
48+
visibility ranges when they're too far away to be readable or relevant to the
49+
player.
50+
51+
Setting up visibility range
52+
---------------------------
53+
54+
This is a quick-start guide for configuring a basic LOD system. After following
55+
this guide, this LOD system will display a SphereMesh when up close and a
56+
BoxMesh when the camera is far away enough. A small hysteresis margin is also
57+
configured via the **Begin Margin** and **End Margin** properties. This prevents
58+
LODs from popping back and forth too quickly when the camera is moving at the
59+
"edge" of the LOD transition.
60+
61+
The visibility range properties can be found in the **Visibility Range** section
62+
of the GeometryInstance3D inspector after selecting the MeshInstance3D Node.
63+
64+
- Add a Node3D node that will be used to group the two MeshInstance3D nodes
65+
together.
66+
- Add a first MeshInstance3D node as a child of the Node3D. Assign a new
67+
SphereMesh to its Mesh property.
68+
- Set the first MeshInstance3D's visibility range **End** to ``10.0`` and **End
69+
Margin** to ``1.0``.
70+
- Add a second MeshInstance3D node as a child of the Node3D. Assign a new
71+
BoxMesh to its Mesh property.
72+
- Set the second MeshInstance3D's visibility range **Begin** to ``10.0`` and
73+
**Begin Margin** to ``1.0``.
74+
- Move the camera away and back towards the object. Notice how the object will
75+
transition from a sphere to a box as the camera moves away.
76+
77+
Visibility range properties
78+
---------------------------
79+
80+
In the inspector of any node that inherits from GeometryInstance3D, you can adjust
81+
the following properties in the GeometryInstance3D's **Visibility Range** section:
82+
83+
- **Begin:** The instance will be hidden when the camera is closer to the
84+
instance's *origin* than this value (in 3D units).
85+
- **Begin Margin:** The hysteresis or alpha fade transition distance to use for
86+
the close-up transition (in 3D units). The behavior of this property depends
87+
on **Fade Mode**.
88+
- **End:** The instance will be hidden when the camera is further away from the
89+
instance's *origin* than this value (in 3D units).
90+
- **End Margin:** The hysteresis or alpha fade transition distance to use for
91+
the far-away transition (in 3D units). The behavior of this property depends
92+
on **Fade Mode**.
93+
- **Fade Mode:** Controls how the transition between LOD levels should be performed.
94+
See below for details.
95+
96+
Fade mode
97+
^^^^^^^^^
98+
99+
.. note::
100+
101+
The fade mode chosen only has a visible impact if either
102+
**Visibility Range > Begin Margin** or **Visibility Range > End Margin** is
103+
greater than ``0.0``.
104+
105+
In the inspector's **Visibility Range** section, there are 3 fade modes to
106+
choose from:
107+
108+
- **Disabled:** Uses hysteresis to switch between LOD levels instantly. This
109+
prevents situations where LOD levels are switched back and forth quickly when
110+
the player moves forward and then backward at the LOD transition point. The
111+
hystereis distance is determined by **Visibility Range > Begin Margin** and
112+
**Visibility Range > End Margin**. This mode provides the best performance as
113+
it doesn't force rendering to become transparent during the fade transition.
114+
- **Self:** Uses alpha blending to smoothly fade between LOD levels. The fade
115+
transition distance is determined by **Visibility Range > Begin Margin** and
116+
**Visibility Range > End Margin**. This mode forces transparent rendering on
117+
the object during its fade transition, so it has a performance impact.
118+
- **Dependencies:** This is intended for hierarchical LOD systems, and acts the
119+
same as **Self** if visibility ranges are used to perform non-hierarchical
120+
LOD.
121+
122+
Configuration tips
123+
------------------
124+
125+
Use simpler materials at a distance to improve performance
126+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
127+
128+
One way to further improve performance is to use simpler materials for distant
129+
LOD meshes. While using LOD meshes will reduce the number of vertices that need
130+
to be rendered, the per-pixel shading load for materials remains identical.
131+
However, per-pixel shading load is regularly a bottleneck on the GPU in complex
132+
3D scenes. One way to reduce this shading load on the GPU is to use simpler
133+
materials when they don't make much of a visual difference.
134+
135+
Performance gains when doing so should be carefully measured, as
136+
increasing the number of *unique* materials in a scene has a performance cost on
137+
its own. Still, using simpler materials for distant LOD meshes can still result
138+
in a net performance gain as a result of the fewer per-pixel calculations
139+
required.
140+
141+
For example, on the materials used by distant LOD meshes, you can disable
142+
expensive material features such as:
143+
144+
- Normal Map (especially on mobile platforms)
145+
- Rim
146+
- Clearcoat
147+
- Anisotropy
148+
- Height
149+
- Subsurface Scattering
150+
- Back Lighting
151+
- Refraction
152+
- Proximity Fade
153+
154+
Use dithering for LOD transitions
155+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
156+
157+
Godot currently only supports alpha-based fading for visibility ranges. You can
158+
however use dithering instead by using several different materials for different
159+
LOD levels.
160+
161+
There are two advantages to using dithering over alpha blending for LOD transitions:
162+
163+
- Higher performance, as dithering transparency is faster to render compared to
164+
alpha blending.
165+
- No visual glitches due to
166+
:ref:`transparency sorting issues <doc_3d_rendering_limitations_transparency_sorting>`
167+
during LOD transitions.
168+
169+
The downside of dithering is that a "noisy" pattern is visible during LOD fade
170+
transitions. This may not be as noticeable at higher viewport resolutions or
171+
when temporal antialiasing is enabled.
172+
173+
Also, as distance fade in BaseMaterial3D only supports fading up close *or*
174+
fading when far away, this setup is best used with only two LODs as part of the
175+
setup.
176+
177+
- Ensure **Begin Margin** and **End Margin** is set to ``0.0`` on both
178+
MeshInstance3D nodes, as hysteresis or alpha fade are not desired here.
179+
- On both MeshInstance3D nodes, *decrease* **Begin** by the desired fade transition
180+
distance and *increase* **End** by the same distance. This is required for the
181+
dithering transition to actually be visible.
182+
- On the MeshInstance3D that is displayed up close, edit its material in the inspector.
183+
Set its **Distance Fade** mode to **Object Dither**. Set **Min Distance** to
184+
the same value as the visibility range **End**. Set **Max Distance** to the
185+
same value *minus* the fade transition distance.
186+
- On the MeshInstance3D that is displayed far away, edit its material in the inspector.
187+
Set its **Distance Fade** mode to **Object Dither**. Set **Min Distance** to
188+
the same value as the visibility range **Begin**. Set **Max Distance** to the
189+
same value *plus* the fade transition distance.

0 commit comments

Comments
 (0)