Skip to content

GLTF: MultiMeshInstance3D import #107866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

2frac
Copy link

@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)

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
Copy link
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
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
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
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
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
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
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
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.

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

@2frac 2frac marked this pull request as draft July 6, 2025 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Work in progress
Development

Successfully merging this pull request may close these issues.

4 participants