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 Material). This can greatly increase performance when used with care.

Explanation

Usually individual meshes in Redot are represented by their own VisualInstance in the SceneTree, such as 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.

../../_images/merge_group_house.webp

You can change the transform of the MergeGroup at runtime, and the whole group will move as a unit (MergeGroup is derived from 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.

Redot 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.

../../_images/bake_merge_group.webp

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. CSGBox) and GridMap nodes can optionally be converted to a regular MeshInstance. This allows them to be merged like any other mesh.