Skip to content

Commit

Permalink
Add MergeGroup node to simplify merging Meshes at runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
lawnjelly committed Jan 31, 2024
1 parent a81d96c commit 8b79135
Show file tree
Hide file tree
Showing 34 changed files with 4,168 additions and 591 deletions.
3 changes: 2 additions & 1 deletion doc/classes/CullInstance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<members>
<member name="allow_merging" type="bool" setter="set_allow_merging" getter="get_allow_merging" default="true">
This allows fine control over the mesh merging feature in the [RoomManager].
Setting this option to [code]false[/code] can be used to prevent an instance being merged.
Setting this option to [code]false[/code] can be used to prevent an instance being merged. When set to [code]true[/code] (the default), merging will be determined by [member Spatial.merging_mode].
[i]Deprecated.[/i] This property has been deprecated and is only included for backward compatibility. Please use [member Spatial.merging_mode] instead.
</member>
<member name="autoplace_priority" type="int" setter="set_portal_autoplace_priority" getter="get_portal_autoplace_priority" default="0">
When set to [code]0[/code], [CullInstance]s will be autoplaced in the [Room] with the highest priority.
Expand Down
114 changes: 114 additions & 0 deletions doc/classes/MergeGroup.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MergeGroup" inherits="Spatial" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
MergeGroups allow merging of suitable meshes, which can enhance performance.
</brief_description>
<description>
[MergeGroup] is a way of grouping nodes into logical blocks that contain meshes that are suitable for joining together, in order to increase rendering efficiency and reduce the number of nodes to simplify the scene.
Only children and descendants will be considered for merging. [MergeGroup] has no effect on parents or siblings.
Meshes must be static (non-moving) in relation to one another to be joined. For instance, a level background is often intended to be static. However, logical blocks that [b]move[/b] together, such as a ship, or car, are also good candidates for merging.
Within these blocks you will often want to prevent certain nodes or branches from being merged, because they [b]are[/b] intended to move, or change visibility, in relation to the main block. An example might be a steering wheel on a ship. You can finely control this with [member Spatial.merging_mode]. Be aware that [member Spatial.merging_mode] will be inherited from parents and ancestors of the [MergeGroup].
There are two ways of performing merging:
- At runtime, using [method merge_meshes] or [member auto_merge].
- Baking at design time to a separate scene, using the [code]bake[/code] button in the Editor inspector.
Merging at runtime is usually best, because it is non-destructive, and will minimize the binary size of the [code]pck[/code] file. It can however take a small amount of time to merge the meshes (usually during level load), but this will usually be well under a second.
Baking ahead of time allows fastest possible load times, but it is by nature a [i]destructive[/i] operation - you should keep a copy of the source scene for later editing, because you cannot reconstruct an unmerged scene from a baked scene. It can also bloat the size of the [code]pck[/code] file considerably, as for example storing 10 merged trees will have 10x the geometry of the scene before merging.
On the other hand, baking ahead of time is very useful for previewing what will happen after merging, and diagnosing problems. It is also convenient for some workflows such as constructing a scene out of merged modular units.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_param">
<return type="int" />
<argument index="0" name="param" type="int" enum="MergeGroup.Param" />
<description>
Returns the value of the specified [enum MergeGroup.Param] parameter.
</description>
</method>
<method name="get_param_enabled">
<return type="bool" />
<argument index="0" name="param" type="int" enum="MergeGroup.ParamEnabled" />
<description>
Gets the value of the specified [enum MergeGroup.ParamEnabled] parameter.
</description>
</method>
<method name="merge_meshes">
<return type="void" />
<description>
You can choose to either automatically merge when the [MergeGroup] enters the scene (usually during loading) using [member auto_merge], or you can manually trigger merging by calling this function.
Manually activating merging is especially useful when you are [i]procedurally generating[/i] your level, and when you want to set advanced parameters prior to merging at runtime.
</description>
</method>
<method name="set_param">
<return type="void" />
<argument index="0" name="param" type="int" enum="MergeGroup.Param" />
<argument index="1" name="value" type="int" />
<description>
Sets the value of the specified [enum MergeGroup.Param] parameter.
</description>
</method>
<method name="set_param_enabled">
<return type="void" />
<argument index="0" name="param" type="int" enum="MergeGroup.ParamEnabled" />
<argument index="1" name="value" type="bool" />
<description>
Sets the value of the specified [enum MergeGroup.ParamEnabled] parameter.
</description>
</method>
</methods>
<members>
<member name="auto_merge" type="bool" setter="set_param_enabled" getter="get_param_enabled" default="true">
Activates merging automatically when the [MergeGroup] enters the scene (usually during loading).
Alternatively you can switch this off and use [method merge_meshes] to manually activate merging.
</member>
<member name="shadow_proxy" type="bool" setter="set_param_enabled" getter="get_param_enabled" default="true">
If [code]true[/code], a [b]shadow proxy[/b] will be generated. This is a merged mesh that is a duplicate of the existing opaque geometry, set to cast shadows only. The source meshes will have shadow casting switched off.
This can be more efficient for rendering shadows, because the requirements for merging a [b]shadow mesh[/b] are far lower than for regular merging. Providing materials are opaque, meshes with different materials can often be merged together for the purposes of shadow casting. This can reduce drawcalls.
[b]Tip:[/b] Try running with and without a [b]shadow proxy[/b] and measure performance, sometimes it will be faster, sometimes not.
</member>
</members>
<constants>
<constant name="PARAM_ENABLED_AUTO_MERGE" value="0" enum="ParamEnabled">
Activates merging automatically when the [MergeGroup] enters the scene (usually during loading).
Alternatively you can switch this off and use [method merge_meshes] to manually activate merging.
</constant>
<constant name="PARAM_ENABLED_SHADOW_PROXY" value="1" enum="ParamEnabled">
If [code]true[/code], a [b]shadow proxy[/b] will be generated. This is a merged mesh that is a duplicate of the existing opaque geometry, set to cast shadows only. The source meshes will have shadow casting switched off.
This can be more efficient for rendering shadows, because the requirements for merging a [b]shadow mesh[/b] are far lower than for regular merging. Providing materials are opaque, meshes with different materials can often be merged together for the purposes of shadow casting. This can reduce drawcalls.
[b]Tip:[/b] Try running with and without a [b]shadow proxy[/b] and measure performance, sometimes it will be faster, sometimes not.
</constant>
<constant name="PARAM_ENABLED_CONVERT_CSGS" value="2" enum="ParamEnabled">
If [code]true[/code], [code]CSG[/code] nodes will be converted to [MeshInstance]s. These [MeshInstance]s can then be merged if suitable matches are found.
</constant>
<constant name="PARAM_ENABLED_CONVERT_GRIDMAPS" value="3" enum="ParamEnabled">
If [code]true[/code], [GridMap]s will be converted to [MeshInstance]s. These [MeshInstance]s can then be merged if suitable matches are found.
[b]Note:[/b] [GridMap]s are usually rendered as [MultiMesh]es very efficiently, so converting these will often be counterproductive. Exceptions include when using the [code]GLES2[/code] backend, which can be inefficient at rendering [MultiMesh].
</constant>
<constant name="PARAM_ENABLED_COMBINE_SURFACES" value="4" enum="ParamEnabled">
If [code]true[/code], as a final step, matching [MeshInstance]s can be joined by combining their surfaces to form an [i]"uber mesh instance"[/i].
While this is convenient, it does have the downside that all the constituent meshes will be culled as one unit, which can make culling less efficient in some situations.
</constant>
<constant name="PARAM_ENABLED_CLEAN_MESHES" value="5" enum="ParamEnabled">
Cleans and removes degenerate triangles from meshes, which can make them more suitable for later processing, such as generating secondary UVs for lightmapping.
[b]Note:[/b] This step can be slow and should typically only be used when [i]baking[/i] the [MergeGroup].
</constant>
<constant name="PARAM_GROUP_SIZE" value="0" enum="Param">
When set to [code]0[/code], all matching meshes will be merged within the [MergeGroup].
If set to [code]1[/code] or above, only groups of a maximum of [code]group_size[/code] meshes will be merged together. These groups will be chosen by locality. This enables getting some of the benefits of merging, while still allowing some culling to take place.
[b]Tip:[/b] Use [i]baking[/i] to preview what the scene will look like after merging.
</constant>
<constant name="PARAM_SPLITS_HORIZONTAL" value="1" enum="Param">
When set to a value above [code]1[/code], mesh geometry will be [i]split by locality[/i] into a grid of [MeshInstance]s.
For instance a value of [code]2[/code] will split meshes into a grid of 2x2 (on the [code]x[/code] and [code]z[/code] axes), for greater culling efficiency.
[b]Note:[/b] Greater culling efficiency must be balanced against a greater number of drawcalls.
</constant>
<constant name="PARAM_SPLITS_VERTICAL" value="2" enum="Param">
This setting acts exactly as [constant PARAM_SPLITS_HORIZONTAL], except it determines the grid split on the vertical axis.
A grid with [constant PARAM_SPLITS_HORIZONTAL] [code]3[/code], and [constant PARAM_SPLITS_VERTICAL] [code]2[/code] will produce a grid of 3x2x3 (on the [code]x[/code] and [code]y[/code] and [code]z[/code] axes respectively).
</constant>
<constant name="PARAM_MIN_SPLIT_POLY_COUNT" value="3" enum="Param">
When using [i]split by locality[/i] using [constant PARAM_SPLITS_HORIZONTAL] and / or [constant PARAM_SPLITS_VERTICAL], you can specify that the split will only occur for meshes above this specified poly count.
There is often little to gain by splitting meshes with low poly count.
</constant>
</constants>
</class>
4 changes: 3 additions & 1 deletion doc/classes/MeshInstance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,18 @@
<method name="is_mergeable_with" qualifiers="const">
<return type="bool" />
<argument index="0" name="other_mesh_instance" type="Node" />
<argument index="1" name="shadows_only" type="bool" default="false" />
<description>
Returns [code]true[/code] if this [MeshInstance] can be merged with the specified [code]other_mesh_instance[/code], using the [method MeshInstance.merge_meshes] function.
In order to be mergeable, properties of the [MeshInstance] must match, and each surface must match, in terms of material, attributes and vertex format.
</description>
</method>
<method name="merge_meshes">
<return type="bool" />
<argument index="0" name="mesh_instances" type="Array" default="[ ]" />
<argument index="0" name="mesh_instances" type="Array" />
<argument index="1" name="use_global_space" type="bool" default="false" />
<argument index="2" name="check_compatibility" type="bool" default="true" />
<argument index="3" name="shadows_only" type="bool" default="false" />
<description>
This function can merge together the data from several source [MeshInstance]s into a single destination [MeshInstance] (the MeshInstance the function is called from). This is primarily useful for improving performance by reducing the number of drawcalls and [Node]s.
Merging should only be attempted for simple meshes that do not contain animation.
Expand Down
14 changes: 14 additions & 0 deletions doc/classes/Spatial.xml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@
<member name="global_translation" type="Vector3" setter="set_global_translation" getter="get_global_translation">
Global position of this node. This is equivalent to [code]global_transform.origin[/code].
</member>
<member name="merging_mode" type="int" setter="set_merging_mode" getter="get_merging_mode" enum="Spatial.MergingMode" default="0">
The merging mode determines whether merging features of the engine ([MergeGroup] and [RoomManager]) will attempt to operate on branches of the scene tree.
The default mode inherited from the scene tree root is [constant MERGING_MODE_ON].
[b]Note:[/b] Merging mode determines whether the merging is [b]allowed[/b] to be performed. It does not guarantee that merging will occur, which depends on whether there are suitable matching objects.
</member>
<member name="position" type="Vector3" setter="set_translation" getter="get_translation">
Local position of this node. This is a forward-compatible alias for [member translation].
</member>
Expand Down Expand Up @@ -328,5 +333,14 @@
<constant name="NOTIFICATION_EXIT_GAMEPLAY" value="46">
Spatial nodes receives this notification if the portal system gameplay monitor detects they have exited the gameplay area.
</constant>
<constant name="MERGING_MODE_INHERIT" value="0" enum="MergingMode">
Inherits merging mode from the node's parent. For the root node, it is equivalent to [constant MERGING_MODE_ON]. Default.
</constant>
<constant name="MERGING_MODE_OFF" value="1" enum="MergingMode">
Turn off merging in this node and children set to [constant MERGING_MODE_INHERIT].
</constant>
<constant name="MERGING_MODE_ON" value="2" enum="MergingMode">
Turn on merging in this node and children set to [constant MERGING_MODE_INHERIT].
</constant>
</constants>
</class>
2 changes: 2 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
#include "editor/plugins/light_occluder_2d_editor_plugin.h"
#include "editor/plugins/line_2d_editor_plugin.h"
#include "editor/plugins/material_editor_plugin.h"
#include "editor/plugins/merge_group_editor_plugin.h"
#include "editor/plugins/mesh_editor_plugin.h"
#include "editor/plugins/mesh_instance_editor_plugin.h"
#include "editor/plugins/mesh_library_editor_plugin.h"
Expand Down Expand Up @@ -7063,6 +7064,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(OccluderEditorPlugin(this)));
add_editor_plugin(memnew(PortalEditorPlugin(this)));
add_editor_plugin(memnew(PackedSceneEditorPlugin(this)));
add_editor_plugin(memnew(MergeGroupEditorPlugin(this)));
add_editor_plugin(memnew(Path2DEditorPlugin(this)));
add_editor_plugin(memnew(PathEditorPlugin(this)));
add_editor_plugin(memnew(Line2DEditorPlugin(this)));
Expand Down
1 change: 1 addition & 0 deletions editor/editor_property_name_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["commentfocus"] = "Comment Focus";
capitalize_string_remaps["cpu"] = "CPU";
capitalize_string_remaps["csg"] = "CSG";
capitalize_string_remaps["csgs"] = "CSGs";
capitalize_string_remaps["db"] = "dB";
capitalize_string_remaps["defaultfocus"] = "Default Focus";
capitalize_string_remaps["defaultframe"] = "Default Frame";
Expand Down
1 change: 1 addition & 0 deletions editor/icons/icon_merge_group.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8b79135

Please sign in to comment.