godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Allow importing a 3D model file as a mesh resource (or material/etc) instead of a scene #7494

Open aaronfranke opened 1 year ago

aaronfranke commented 1 year ago

Describe the project you are working on

I am working on GLTF support in Godot.

Describe the problem or limitation you are having in your project

Many people use OBJ files in Godot because they import as a Mesh resource. However, OBJ is an ancient format with many limitations like not having materials in the same file. It would be nice to fully replace OBJ with GLTF.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Allow importing a GLTF file as a mesh resource instead of a scene. This would only import one mesh, we could probably just hard-code it to grab the mesh at index 0. We could also allow importing a GLTF file as a material.

Note that the GLTF standard supports being used as a data storage file format for meshes/materials/etc. A GLTF file is allowed to contain zero nodes, zero scenes, etc.^1 We could auto-detect this situation and import such files as a mesh without the user having to configure anything, while also giving the user the option to override this.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

In the import dialog, there is currently a dropdown that allows a 3D model to be imported as a scene or an animation library. The proposal is to add "Mesh" and "Material" (and maybe others?) to this list.

Screenshot 2023-08-14 at 4 41 29 AM

We would also need to replace the animation_importer boolean with an enum. At the moment it does seem a bit weird that the 3D model import code is hard-coded for "animation or scene":

String ResourceImporterScene::get_visible_name() const {
    return animation_importer ? "Animation Library" : "Scene";
}

As for auto-detection, I'd imagine that we have a method on the scene importer code that can be overridden by each derived class and would return the suggested import setting(s) for the specified file, before import even starts. So the GLTF importer could quickly read the JSON, look for "nodes", if it exists then suggest importing as a scene, otherwise if there's no nodes but a mesh suggest importing as a mesh, etc.

If this enhancement will not be used often, can it be worked around with a few lines of script?

It's unclear how often this will be used. I expect it will not be used very often, but some users prefer the workflow of importing meshes. Those users are currently using OBJ files, but that's not ideal.

It could likely be worked around with GLTF in GDScript with a lot of custom import code. Godot's import system is quite extensible so I think I can confidently say this even though I have not tried it.

It can be worked around with GLTF with no lines of script if you have a scene with a MeshInstance, then copy it and make it unique. However this work-around would result in the mesh data being copied into the Godot scene, so it would not automatically update with the GLTF's contents if the GLTF file was updated.

Is there a reason why this should be core and not an add-on in the asset library?

Importing 3D model files is a core part of the engine.

Calinou commented 1 year ago

Sounds good to me, as long as it prints a warning on import if the scene contains more than one mesh (with subsequent meshes being ignored). The selected mesh's name could also be printed in that warning.

Note that in the meantime, you can import a glTF scene and use the Advanced Import Settings dialog to save a specific mesh to a file. This requires committing the .mesh file to version control though, unlike the approach suggested here. For complex meshes, this can save a significant amount of space in the VCS history, so I think this is worthwhile.

A GLTF file is allowed to contain zero nodes, zero scenes, etc.1 We could auto-detect this situation and import such files as a mesh without the user having to configure anything, while also giving the user the option to override this.

I wouldn't change the underlying import type based on the glTF's contents, as it can lead to difficult-to-debug situations.

Mickeon commented 1 year ago

In our own project we did have several occasions where this would've been marginally useful, over exporting .obj files every time. This could theoretically be done by an import addon, but it's not one I've ever seen, and it'd be nice to have in core.

Okxa commented 1 year ago

It is indeed possible to do this with an import addon! Here is my import function from my WIP addon as an example (consider this snippet as MIT or whatever). It simply exports all resources as meshes or shapes (depending on object name in the file, plan is to not force usage of godot specific import hints):

func _import(source_file, save_path, options, platform_variants, gen_files):
    var err = OK # store error
    print("Importing from: " + source_file)
    # Set output paths
    var output_path = options["models/output_location"]
    var output_path_phys = options["physics_models/output_location"]

    if output_path == ".": # todo validate / maybe use export hints for path type
        output_path = source_file.get_base_dir() + "/"
    if output_path_phys == "":
        output_path_phys = output_path
    elif output_path_phys == ".":
        output_path = source_file.get_base_dir() + "/"

    var phys_postfix = options["physics_models/physics_mesh_postfix"]
    if phys_postfix == "":
        print("No physics mesh postfix, treating all as meshes.")

    # Open GLTF file
    var state = GLTFState.new()
    var doc = GLTFDocument.new()
    if doc.append_from_file(source_file, state) != Error.OK:
        print("Failed")
        return FAILED
    print("Found scene: ", state.scene_name, ", uniques_names: ", state.get_unique_names())
    var meshes = state.get_meshes() # get meshes from the GLTFState

    # !! scene name in .gltf file (== blender) should not be "Scene"
    # or same as any other name in file
    # if you want consistent naming !!
    #
    # GLTFDocument::_parse_scenes(Ref<GLTFState> p_state)
    # creates in-engine node names and if the scene name in the file starts with "Scene" (or is empty/null)
    # it sets the scene name to filename, which would be fine...
    #
    # IF NOT FOR:
    # GLTFDocument::_gen_unique_name(Ref<GLTFState> p_state, const String &p_name)
    # which checks if there are duplicate names and adds extra numbers to duplicates
    #
    # The stock import dialog shows this behauviour too (as it uses the same functions I think)

    # Iterate through nodes in the gltf
    var first_mesh
    for node in state.nodes:
        var filename = ""
        var mesh = meshes[node.mesh].mesh
        print("Found node: ", node.resource_name, ", mesh: ", mesh.resource_name)
        mesh.resource_name = node.resource_name # set name according to node, not per mesh
        # if node name has correct postfix, save it as shape for collisions
        if node.resource_name.ends_with(phys_postfix):
            var shape = mesh.get_mesh().create_convex_shape() # create shape from the mesh
            shape.resource_name = mesh.resource_name
            filename = output_path_phys + shape.resource_name + ".tres"
            err = ResourceSaver.save(shape, filename, ResourceSaver.FLAG_CHANGE_PATH)
            if err != OK:
                return err
        else:
            # if no postfix, save as mesh
            filename = output_path + node.resource_name + "." + _get_save_extension()
            err = ResourceSaver.save(mesh.get_mesh(), filename, ResourceSaver.FLAG_CHANGE_PATH)
            if err != OK:
                return err
            # save first mesh also to .godot/imported to supress error
            if node.mesh == 0:
                err = ResourceSaver.save(mesh.get_mesh(), save_path + "." + _get_save_extension(), ResourceSaver.FLAG_CHANGE_PATH)
                if err != OK:
                    return err

        gen_files.push_back(filename) # add to gen_files array, see docs
        print("Exported to: " + filename)
    return err

The above script is not finished and has some issues, like if you try to reimport the same glb file, it will output an error. I have not yet tested it with rigged models or animations. Note that some variables are not included above (they are EditorImportPlugin options), but you'll get the gist of it. Might eventually release this addon fully if I get it to satisfactory state.

As for what the comment means in the above script, godot gltf importer renames nodes (imo too keenly): https://github.com/godotengine/godot/issues/56447#issuecomment-1456578947


As for the proposal, I still think that this would be a good use case to support by default, As it would allow using models more freely out of the box.

Also if file contains multiple meshes, it should probably extract all of them, and leave the handling of the meshes to the game dev.
Maybe even allow selecting which to extract, either with the current import gui, or preferably a gui that shows the gltf file as is, and allows you to save objects from it in any way you want. (Such as selecting if saving as mesh or shape, within reason of course, no saving materials as meshes etc.)

xzbobzx commented 1 year ago

I've been talking about something similar with a bunch of folks in the Godot Discord and I made a mockup image of what, ideally, importing would look like to me:

2023-09-30_01-02-58_(Photoshop)_Candid_Huemul

You drop a .gltf/.fbx/.blend/whatever file into Godot, and you can access the individual meshes inside of the model file as both mesh resources and individual scenes.

Technically (beyond the file acting as a folder), all of the steps for separating files into meshes/scenes already exists, it's just that it always takes multiple steps and can get quite tedious if you have a lot of files that you often update.

If Godot were to do those steps automatically each time you update your file, you could speed up some people's workflows a lot.

It wouldn't even need to break existing workflows, as you could also still allow the original .gltf/etc file to also be accessed as a scene.

aaronfranke commented 1 year ago

@xzbobzx Trying to treat files as folders to allow accessing multiple resources is a neat idea but I think would require a lot of work. What I was initially thinking in this proposal is just a dropdown, and you only get one resource. What you propose is certainly more flexible but I don't know if it's necessary.

xzbobzx commented 1 year ago

@aaronfranke It's how I'm used to dealing with model files in Unity, and in the few days I've been hanging around on the Godot Discord I've also seen new coming-from-Unity developers asking where Godot puts the mesh resources after you import a file.

Treating files as folders might be (probably definitely is) overkill for that though, even putting them in a folder next to the file would do the trick.

And all of the functionality already exists inside of Godot anyway. For instance the generation of mesh resource files is hidden in the advanced input settings:

2023-09-30_00-41-33_(Godot_v4 1 1-stable_mono_win64)_Adorable_Viper

It always takes like 5-6 clicks to skip through all of the menus to get it done, so it's a bit tedious to have to do that every time you update you model. (I used that method to generate the mesh resources for the mockup in my previous post.)

But the code is all there, all it would require is a setting or two in the project settings to do all of those steps automatically.

For the people who don't use it, they don't have to use it. For the people who do (and I think there's more than people might expect?), it would be a really really nice QoL feature.

Edit:

For reference, my personal workflow when making prefabs/scenes is always:

(translated to Godot terms)

I like doing it that way because it gives me a lot of control of exactly how I structure my scenes from the ground up, instead of having to edit existing scenes that were generated by the engine that maybe don't quite 100% align exactly with what I'm going for.

Uradamus commented 1 year ago

I don't know if perhaps this should be a separate proposal, but seems related enough to bring it up here first. But I think a huge pain point with GLTF imports right now is the complete lack of support for rearranging the scene from within the advanced import window.

As an example. I'm currently working on some segmented character models, where each body part is a separate mesh parented directly to a bone, some of those parts are interchangeable. So the importer is creating a new bone attachment node for every single part, even when several parts may share the same bone. I want to be able to rename one of those bone attachment nodes to match the bone name and then take all the meshes from all the others that are attaching to the same bone and parent them to that single bone attachment node and delete the other redundant ones. But the only way I can do that is to dump all the parts as separate resources and rebuild the scene from the ground up, adding a ton of extra work and making the project directory structure so much more messy in the process.

That is just one example, but there are so many times in other situations where most of my problems would disappear if I could make changes directly to the scene structure right there in the advanced import window, instead of having to jump through hoops to clear up the importer's messy, and often wrong, guesses as to how I want a given scene structured.

aaronfranke commented 1 year ago

@Uradamus For the bone attachment thing, I think we should just use at most one bone attachment per bone. I am planning to rework this code in the near future (next few months or so). Can you send me a test model, or possibly several test models?

For the rest, it's not related to this proposal. In general, the advanced import settings dialog should not be a general-purpose scene editor. We don't want to maintain 2 scene editors in Godot. If your model files are not being imported correctly, please open issues with minimal reproduction projects and ping me. If you want to customize how the importer works, you can also do so through script.

FeatherAntennae commented 1 year ago

I think this could be made useful for a lot of other use cases if this was split in two features. One to allow multiple imports of the same asset file, and one to allow selecting only parts of a gltf file for import (instead of coding the whole thing as specific to gltf import and to the first model in the file).

This would allow everyone to choose their own pipeline for asset import. Someone could import multiple separate models, materials, and an animation library from a single GLTF file, while someone else could import the whole scene as-is.

When there is more than one import variation of the same asset, they could be grouped as children of the file in the filesystem dock.

elvisish commented 1 year ago

Sounds good to me, as long as it prints a warning on import if the scene contains more than one mesh (with subsequent meshes being ignored). The selected mesh's name could also be printed in that warning.

As I mentioned in https://github.com/godotengine/godot-proposals/issues/8069 could the meshes be auto merged into the same mesh resource if multiple are detected? If Godot finally adds mesh merging natively (currently adding meshes to Ed's CSG Mesh node, childing then to an Ed's CSG Merge node and destructively merging is the only way) could this method be reused for this purpose?

fire commented 1 year ago

Was discussed at the godot animation asset meeting a few days ago.

fire commented 8 months ago

https://github.com/godotengine/godot-proposals/issues/8750 (Add ability to reference subresources and inspect them in filesystem) mentions https://github.com/godotengine/godot/pull/86779 as a way to treating files as folders.

ogapo commented 6 months ago

I like that this is a MVP sort of change that builds on an existing system. @xzbobzx proposal to expose the entire file structure sounds awesome (and I hope it gets built as a v2) but would be nice to have this in the meantime.

That said, I do think that extending that existing dialog a little bit to allow you to pick which mesh/material you want to grab would be huge (could just be a dropdown identify the mesh by name, if it's named, and also index). I think this would work for mesh and material both and as a bonus could probably easily be extended to support Scene too so we can support multi-scene files to boot.

aaronfranke commented 3 months ago

@xzbobzx There is a proposal from @reduz implementing the feature you want: https://github.com/godotengine/godot-proposals/issues/8750

image

This would be really nice to have, and solves some use cases of my proposal here, but is technically perpendicular to it.

xzbobzx commented 2 months ago

@aaronfranke Yes! I've been passively keeping track of this issue, and the proposed changes look extremely useful!! I'm very excited for it!

aaronfranke commented 2 months ago

@xzbobzx Thanks! If you want to see this proposal progress, check out this PR, which is a prerequisite: https://github.com/godotengine/godot/pull/87787

You can click the 👍 button on it to show your support, and/or share it with your friends so they can click it too.