Skip to content

GLTF: MultiMeshInstance3D import#107866

Closed
2frac wants to merge 14 commits into
godotengine:masterfrom
2frac:gltf_import_multimesh
Closed

GLTF: MultiMeshInstance3D import#107866
2frac wants to merge 14 commits into
godotengine:masterfrom
2frac:gltf_import_multimesh

Conversation

@2frac
Copy link
Copy Markdown
Contributor

@2frac 2frac commented Jun 22, 2025

Allows to import MultiMeshInstance3D objects through EXT_mesh_gpu_instancing GLTF extension: https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md

Depends on #107672 for blender. Will work by itself for pure GLTF files(if exporter option enabled)

Bugsquad edit: Depends on #108853 and #109103.

Implementation notes:

  • Adds 3 fields to GLTFNode - multimesh_translation, multimesh_rotation, multimesh_scale. All of this is referencing accessors indices in Gltf document.

  • Adds ImporterMeshInstance3D::multimesh. It is created and filled in GLTFDocument::_generate_mesh_instance - nearby ImporterMeshInstance3D creation.
    (I am not sure about that decision - almost every resource parsed/created separately from nodes(and assigned later), but instances info stored in node description and not in json's root, so maybe its ok)

  • Actual MultiMeshInstance3D creation happens in ResourceImporterScene::_pre_fix_node - nearby other nodes creation(and suffix parsing)
    Currenly there is no condition to create MultiMeshInstance3D - it just checks whether it has or not valid multimesh.
    I think ideally there should be suffix condition, OR at least code should check other conditions first - like col/colonly - whatever might be more important to have.
    (for that reason MultiMeshInstance3D is not created early in GLTFDocument)(there is also some mesh material manipulation which duplication we might want to avoid)
    Suffixes might be tricky, however - blender will set name of instance node based on name of main node - not on the name of children node in editor.

  • to trigger multimesh creation, code need at least one of three accessors. Since blender creates all of them, i only tested that case.

  • On import there is some warnings like "Instance count must be 0 to change the transform format". I think it is related more to copying MultimeshInstance3D and/or scene creation, than to creation code itself.

  • There is also some work on GDScript properties and docs left(if rest is even desirable implementation)

@2frac 2frac requested review from a team as code owners June 22, 2025 21:21
@2frac 2frac marked this pull request as draft June 22, 2025 21:27
@AThousandShips AThousandShips added this to the 4.x milestone Jun 23, 2025
@AThousandShips
Copy link
Copy Markdown
Member

Please set up pre-commit hooks locally to ensure style correctness so you don't run CI needlessly to fix style

@2frac 2frac marked this pull request as ready for review June 24, 2025 19:16
@2frac 2frac requested a review from a team as a code owner June 24, 2025 19:16
Copy link
Copy Markdown
Contributor

@lyuma lyuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. Just needs some changes to where the node conversion happens.

The conversion from ImporterMeshInstance3D to MultiMeshInstance3D should also be performed in GLTFDocumentExtensionConvertImporterMesh (modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp). That is where the translation from an ImporterMesh* node into the corresponding MeshInstance3D (or in this case, MultiMeshInstance3D) is happens in runtime import.

Finally, I'd love to see export code also added either here or in a future PR, so when a user exports a scene with a MultiMeshInstance3D, it creates the accessors and everything. I can't ask you to volunteer more of your time, but having the export logic would make round-trip testing much easier (import -> export -> import). For export, it would need a setting added to the export dialog whether to use the extension (required / optional / disabled), or use the current code which converts it to a normal gltf nodes.

Comment on lines +703 to +706
if (Object::cast_to<ImporterMeshInstance3D>(p_node)) {
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node);

if (mi->get_multimesh().is_valid()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is converting the node type too early. This code should be moved into ResourceImporterScene::_generate_meshes(). It should become an if/else statement that creates a MultiMeshInstance3D if mi->get_multimesh().is_valid() and a normal MeshInstance3D otherwise.

Copy link
Copy Markdown
Contributor Author

@2frac 2frac Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current code:
2025_07_04_15_09_52
_generate_meshes:
2025_07_04_15_11_55

Moving creation to _generate_meshes() makes is look like regular mesh in Advanced Import Settings. While creating inherited scene from same file will actually have MultiMeshes. Is this correct behavior?

Copy link
Copy Markdown
Contributor Author

@2frac 2frac Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If current is more correct, however, it ideally should display its mesh info.

edit: displayed this anyway, for multimeshes created by potential user extensions.

@lyuma lyuma modified the milestones: 4.x, 4.6 Jul 1, 2025
@lyuma lyuma moved this to Work in progress in Asset Pipeline Issue Triage Jul 1, 2025
@lyuma lyuma requested a review from aaronfranke July 1, 2025 06:06
@fire fire requested a review from a team July 1, 2025 06:26
@@ -49,6 +49,9 @@ class GLTFNode : public Resource {
GLTFCameraIndex camera = -1;
GLTFSkinIndex skin = -1;
GLTFSkeletonIndex skeleton = -1;
int multimesh_translation = -1;
int multimesh_rotation = -1;
int multimesh_scale = -1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done with an extension, instead of adding properties to GLTFNode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean i have to create new GLTFDocumentExtension subclass for parsing(and maybe processing extension's things), or just to use has/get/set_additional_data() in GLTFDocument instead of accessing those fields?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new GLTFDocumentExtension subclass, which uses the additional data functions. However, this isn't currently possible to do in a safe way with accessors. I've been working on refactoring these to support this, and opened PRs #108302 and #108320 so far, with more still to come... sorry to still ask you to wait longer.

Comment thread modules/gltf/gltf_document.cpp Outdated
return nullptr;
}
Ref<GLTFBufferView> buffer_view = p_state->buffer_views[accessor->buffer_view];
translation_ptr = (Vector3 *)(p_state->buffers[buffer_view->get_buffer()].ptr() + buffer_view->get_byte_offset());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a safe usage of accessors, and will fail for many many cases. However, currently we don't expose APIs for using accessors safely. Coincidentally, I was planning on working on that as my next PR very soon. I know this is unfortunate to hear, but I'm going to ask you to please wait a few weeks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, i can wait. It is actually less blocker for my workflow than option to enable extension itself.

What exactly those unsafe cases? Can buffer in accessor be not aligned with its description? Or is it about pointer generally not being safe, for threading and similar things?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, this will fail if the accessor primitive isn't float32, and will fail if Godot is compiled with doubles.

@2frac
Copy link
Copy Markdown
Contributor Author

2frac commented Jul 3, 2025

That is where the translation from an ImporterMesh* node into the corresponding MeshInstance3D (or in this case, MultiMeshInstance3D) is happens in runtime import.

Is GLTFDocumentExtensionConvertImporterMesh meant to do main conversion and code in _generate_meshes - whatever multimeshes been created by user extensions?

Finally, I'd love to see export code also added either here or in a future PR, so when a user exports a scene with a MultiMeshInstance3D, it creates the accessors and everything.

I might try to look export, since i've got some time for that(but probably after main fixes)
This is not expected to be in my pipeline, however, so testing will be rather simple.

@RichardJECooke
Copy link
Copy Markdown

Will this enhancement fix 109280? The file example there doesn't use EXT_mesh_gpu_instancing, but is just a few nodes that point to the same mesh in the gltf file. My example seems simpler/different, but I don't know enough about 3D files to know if this pull request will cover it or if I should look into another fix.

@2frac
Copy link
Copy Markdown
Contributor Author

2frac commented Aug 4, 2025

Will this enhancement fix 109280? The file example there doesn't use EXT_mesh_gpu_instancing

It should, actually. "GPU instances"+"GN instances" in blender option should create EXT_mesh_gpu_instancing entries in GLTF file - but for godot specifically this option added separately in #107672 is semi-prerequisite to that PR).

Not sure why is this not the case for example - it looks like it created without "GPU instances" option.

@passivestar
Copy link
Copy Markdown
Contributor

There needs to be a way to disable this in case the user wants to do own import logic via an addon (i.e to implement auto proximity clustering for better culling). It's fine if it's enabled by default though

@aaronfranke
Copy link
Copy Markdown
Member

@passivestar This extension would only exist in a glTF file if the asset author explicitly indicated that they want control over batching. I think it would be best to re-export such a file with different settings if this wasn't wanted.

However, even so, you'll be able to disable this (and any other GLTFDocumentExtension) implicitly by registering a new extension with higher priority, overriding the behavior.

@passivestar
Copy link
Copy Markdown
Contributor

@passivestar This extension would only exist in a glTF file if the asset author explicitly indicated that they want control over batching. I think it would be best to re-export such a file with different settings if this wasn't wanted.

you'd want the extension enabled on export to make sure you write instances to .bin but have custom import logic to have control over how that binary data is converted into multimesh nodes

However, even so, you'll be able to disable this (and any other GLTFDocumentExtension) implicitly by registering a new extension with higher priority, overriding the behavior.

good

@2frac
Copy link
Copy Markdown
Contributor Author

2frac commented Nov 19, 2025

Closing PR for now, as there is better implementation now.

@2frac 2frac closed this Nov 19, 2025
@github-project-automation github-project-automation Bot moved this from Work in progress to Done in Asset Pipeline Issue Triage Nov 19, 2025
@aaronfranke aaronfranke removed this from the 4.6 milestone Nov 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

6 participants