diff --git a/tutorials/3d/img/bake_merge_group.webp b/tutorials/3d/img/bake_merge_group.webp new file mode 100644 index 000000000..1bcfdab35 Binary files /dev/null and b/tutorials/3d/img/bake_merge_group.webp differ diff --git a/tutorials/3d/index.rst b/tutorials/3d/index.rst index 542dee9bc..7ecc09e20 100644 --- a/tutorials/3d/index.rst +++ b/tutorials/3d/index.rst @@ -21,4 +21,5 @@ procedural_geometry/index occluders portals/index + merge_groups 3d_text diff --git a/tutorials/3d/merge_groups.rst b/tutorials/3d/merge_groups.rst new file mode 100644 index 000000000..9207e88fd --- /dev/null +++ b/tutorials/3d/merge_groups.rst @@ -0,0 +1,271 @@ +.. _doc_merge_groups: + +MergeGroups +=========== + +The ``MergeGroup`` node allows the user to merge similar meshes so that they can +be grouped into a smaller number of nodes, and render using a single draw call +(per :ref:`Material `). This can greatly increase performance when +used with care. + +Explanation +----------- + +Usually individual meshes in Godot are represented by their own +:ref:`VisualInstance ` in the +:ref:`SceneTree `, such as :ref:`MeshInstance `. +These: + +- Move independently +- Cull independently +- Render individually + +In terms of performance however, each ``VisualInstance`` adds a cost, both in +terms of management, and in terms of rendering. Each mesh (and each surface on a +``MeshInstance`` is rendered as an individual mesh) will require an expensive +draw call to the graphics API, and often incur expensive rendering state changes. + +If instead we can identify groups of meshes that could be potentially be +rendered together, and merge them ahead of time (either at design time, or +during level load), this can potentially lighten the load on the CPU and GPU, +and increase frame rates. + +The merging trade-off +^^^^^^^^^^^^^^^^^^^^^ + +Lowering draw calls and reducing state changes tends to increase performance. +However, merged meshes are culled as a unit, and therefore if any part of the +merged mesh is visible in the view frustum, the cost of rendering all parts of +the mesh are incurred. For this reason it is recommended that you evaluate +performance both before and after merging, in order to find the best balance in +each case. + +.. tip:: + Remember that the bottlenecks may be different on different hardware. + Be sure to test on the lowest-end platforms you're targeting for your + project, especially mobile devices and integrated graphics. + +Requirements +------------ + +There are some requirements for any group of meshes to be merged. + +The most important requirement is that the source meshes must not move in +relation to each other. They must be either be completely static (non-moving) +or move together only as a single unit. This is because once several meshes are +merged into one, the relative transforms of the source meshes cannot change, +the transforms are baked into the new geometry. + +The simplest candidate for merging is static level geometry, such as walls, +floors, tables etc. But groups of meshes that move "as one" are also good +candidates. Examples might be a pirate ship, or spacecraft. + +Using MergeGroups +----------------- + +After adding a ``MergeGroup`` node into your ``SceneTree``, move all the +VisualInstances you want to be merged so that they are parented by the +``MergeGroup`` (either as children, or grandchildren). By default, if you now +run the scene, the engine will automatically merge suitable meshes as the +``MergeGroup`` enters the ``SceneTree``. + +You can change the transform of the ``MergeGroup`` at runtime, and the whole +group will move as a unit (``MergeGroup`` is derived from +:ref:`Spatial `, so can be manipulated in all the regular ways, +such as changing transform or visibility). + +Mesh similarity +--------------- + +It is important to note that not all meshes (or more accurately, mesh surfaces, +because merging takes place at the level of mesh surfaces) can be merged +together. There are strict limitations for the mesh surfaces to match. + +All the following properties must match for merging to take place: + +- Material +- Shadow casting settings +- Visibility +- Layer mask +- Portal settings +- Cull margin +- Lightmap settings + +Usually this means only multiple instances of the same mesh will be merged (such +as a group of walls), but similar variations of mesh (e.g. different sized +tables) can sometimes be merged. Additionally, if you reuse materials (using +e.g. a "wood" material in several different meshes), these can often be merged. + +Exceptions +---------- + +When you place a hierarchy of nodes underneath a ``MergeGroup``, such as a +pirate ship, there may be exceptions - meshes that you *do not* want merged. +This may be because, for instance, you may want them to move independently of +the ``MergeGroup``. An example might be a steering wheel, or sail. + +Godot allows you fine control over exactly which nodes should be considered for +merging, via the ``merging_mode`` property which is present in the inspector +for every ``Spatial``, inside the ``Misc`` tab. + +Merging modes: + +- ``Inherit`` - Inherit the setting from the parent. The default for the scene root node is ``On``. +- ``On`` - Change the mode of the node (and any inheriting children) to allow merging. +- ``Off`` - Change the mode of the node (and any inheriting children) to disallow merging. + +This means that if you, e.g. set the mode of a steering wheel to ``Off``, it +will not be considered for merging, and neither will any children or +grandchildren of the steering wheel (unless one of them explicitly reactivates +merging with an ``On`` ``merge_mode``). + + +Ways to use MergeGroups +----------------------- + +There are three ways to use MergeGroups: + +AutoMerge +^^^^^^^^^ + +The ``MergeGroup`` will attempt to merge any descendent merges as it enters the +``SceneTree``. +This is the simplest method, and the best introduction to merging. + +Baking in the Editor +^^^^^^^^^^^^^^^^^^^^ + +When the ``MergeGroup`` is selected in the editor, a new ``bake`` button should +appear in the toolbar. This allows you to bake the entire merged scene out to a +file (.tscn (text scene) or .scn (binary scene)) at *design time* rather than at +*runtime*. This can be used creatively to build sensible composite objects which +are later used to compose a game level (this general approach is often known as +"kitbashing"). + +Additionally, baking in advance offers a great way to preview what will happen +when merging at runtime. It allows you to open the merged scene, and see which +meshes were successfully merged, and which were problematic. + +.. figure:: img/bake_merge_group.webp + :align: center + +Pros +~~~~ + +- Allows you easily see the results of merging +- Allows you to further build levels using these merged scenes +- No time is taken by the merging process at runtime + +Cons +~~~~ + +- Merged scenes typically require substantially more storage than the original meshes +- The size of your exported game will likely increase +- Larger merged scenes may take longer to load +- Larger merged scenes may take up more RAM at runtime, especially on the GPU + +If you merge, e.g. 10 boxes, the merged scene will have to store 10 times as +much geometry data, as the polygons are duplicated. More storage means your +exported game will be larger, the data will take longer to load, and consume +more RAM at runtime. For this reason, baking in advance tends to be more +practical with low poly meshes and art styles. + +.. tip:: + Due to the increased storage requirements, it is recommended that, wherever possible, + you bake scenes in binary format (``.scn``) rather than text (``.tscn``). + This is because binary scenes are much more compact in terms of storage, + while also being faster to load and save. + +Manually at runtime +^^^^^^^^^^^^^^^^^^^ + +If the ``automerge`` property of a ``MergeGroup`` is disabled, then the node +will do nothing at runtime until you call its ``merge_meshes()`` function. +Triggering merging manually in this way has two major use cases: + +1. Procedural levels. If you place objects (e.g. trees, boxes) at runtime using + script, rather than placing them at design time, then you want a way to *delay* + merging until after your placement is complete. Manually calling + ``merge_meshes()`` allows this. +2. Merging parameters. These can be set via the ``MergeGroup::set_param()`` and + ``MergeGroup::set_param_enabled()`` functions, prior to calling + ``merge_meshes()``. + +Merging parameters +------------------ + +Although the default operation of the ``MergeGroup`` works well in many +circumstances, there are a number of parameters which can be altered prior to +merging in order to access more advanced features. These can be set via the +``MergeGroup::set_param()`` functions, however the easiest way to visualize them +is via the ``bake`` method, which displays a dialog allowing you to modify +parameters. These are described in detail in the documentation for +``MergeGroup``. + +Merging by locality - grouping and splitting +-------------------------------------------- + +When merging large numbers of meshes across a large map, sometimes the merging +goes too far, and results in a huge mesh that is too difficult to cull (as part +is always in the view frustum). + +For example if you merge every tree in a forest, regardless of your viewpoint, +the whole forest will be rendered. Although rendering each tree individually +would be inefficient, rendering the whole forest in all cases is also +inefficient, but in a different way. + +You may think instead to create several ``MergeGroups`` spread across the +forest, and only merge the trees in the local area. This would create an ideal +balance between reduced drawcalls, but still allowing broad scale culling to +take effect. The downside is that this kind of thing could be extremely labour +intensive in terms of scene design. + +For this reason, ``MergeGroup`` has built in functionality for helping deal with +this problem automatically. + +.. note:: + Grouping and splitting is considered advanced, so is only available via the + manual method (setting parameters and calling ``merge_meshes()`` explicitly), + or via the bake method. + +Grouping +^^^^^^^^ + +One of the simplest ways to get the advantages of merging without the +disadvantages of loss of culling resolution is grouping. The ``group_size`` +parameter defaults to zero, which indicates that all suitable meshes be merged. +But if you set ``group_size`` to e.g. 2, then only the closest pairs of meshes +will be merged. + +If you have 10 trees, it will try to merge 5 pairs of trees, with each pair +being as close together as possible (so that they can be culled efficiently). +This same principle works even in a forest of 1,000 trees. + +Splitting +^^^^^^^^^ + +The alternative approach to grouping is splitting. Splitting takes place as a +post-step, *after* all the meshes have been merged. + +Following our forest example, if we assume our 1,000 tree forest has been merged +into 1 huge mesh, splitting allows us to specify a 3D grid (with a horizontal +number of splits and vertical number of splits) and the ``MergeGroup`` will +attempt to split the large mesh into smaller ones more suitable for culling. + +For example, with 5 horizontal splits and 1 vertical split we should get 25 +final meshes (5 x 5 x 1) which should give us enough resolution for some decent +culling for our forest. + +.. note:: + The reason for allowing separate horizontal and vertical splits is that + many games are based on flat ground, where horizontal splits may be more + important than vertical. Increasing vertical splits may be counter-productive. +.. tip:: + Splitting can make a lot of sense if you are building a world with voxels. + +Other functionality +^^^^^^^^^^^^^^^^^^^ + +Also via the ``MergeGroup`` parameters, ``CSG`` nodes (e.g. :ref:`CSGBox `) +and :ref:`GridMap ` nodes can optionally be converted to a regular +:ref:`MeshInstance `. This allows them to be merged like any other mesh.