godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.07k stars 69 forks source link

Expose method `LightmapGI.bake()` in editor and exported projects #8656

Open guerro323 opened 6 months ago

guerro323 commented 6 months ago

Describe the project you are working on

I'm working on a procedural and UGC (User Generated Content) game where users can create their own maps from premade blocks and custom models.

Describe the problem or limitation you are having in your project

I need to have lightmaps for performance and look reasons as I'm targeting both low and high end PCs.
As it is a game where users can create their own maps from premade blocks (or their own) in a map editor ingame this mean I cannot use the editor for that.

Since GPUs become faster and faster everyday and that LightmapGI uses GPU for baking, this is perfect for this proposal!

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

Exposing the method help for procedural and UGC games:

Having the bake method exposed also help for the editor side:

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

Code A:

Based on how LightmapGIEditorPlugin does it:

func on_bake_step(step_progress: float, step_description: String) -> bool:
    return Input.is_key_pressed(KEY_ESCAPE) # Cancel if the user pressed the escape key

# LightmapGI.bake(bake_step_callback: Callable, from_node: Node = null)
var err: BakeError = lm.bake(on_bake_step)

Code B:

More understandable and traditional for developers:

func start_baking():
    # LightmapGI.bake_start(from_node: Node = null)
    var err: BakeError = lm.bake_start() # If there is an error, the baking will stop immediately

func _process(delta):
    if lm.is_baking:
        # LightmapGI.bake_status() -> {step_progress=0.0, step_description="", error=BakeError}
        var status: Dictionary = lm.bake_status()
        if Input.is_key_pressed(KEY_ESCAPE): # Cancel if the user pressed the escape key
            lm.bake_abort()

Code B is my preferred choice and make more sense for developers, especially if you check the next note ↓

Note: as of right now, I'm not sure how Godot does to call process while the lightmap is baking as it should be blocking by looking at the code (but throwing a tool script it does call process...), so the method where you call it is blocking which could cause problems if badly implemented on the game code (such as an infinite loop).
Note: the bake or bake_start require a LightmapGIData to be set in the node or else it should return an error. Perhaps passing one in the method parameter could be done.

As of right now, it's not possible to cancel lightmapping (even thought it has a dedicated error) but I think canceling it should be implemented, mainly for those reasons:

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

No.

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

LightmapGI is core.

Calinou commented 6 months ago

To solve that, a feature would be added in the Build Configuration Profile window with the name Lightmapping. It would be disabled by default and would require the game maker to enable it manually.

This should also be done for UV2 unwrapping using xatlas, which you'll probably need if you wish to bake lightmaps in an exported project.

Calinou commented 2 months ago

I tried my hand at exposing LightmapGI runtime baking (only in editor builds for now), but I couldn't get it to work: https://github.com/Calinou/godot/tree/lightmapgi-expose-baking

Feel free to continue this branch if you'd like :slightly_smiling_face:

RisingThumb commented 2 months ago

I tried my hand at exposing LightmapGI runtime baking (only in editor builds for now), but I couldn't get it to work: https://github.com/Calinou/godot/tree/lightmapgi-expose-baking

Feel free to continue this branch if you'd like 🙂

Hey @Calinou , I spent a little bit of time digging into why this doesn't work, and I've narrowed the issue down a little bit. The issue seems to happen on this particular line: https://github.com/godotengine/godot/blob/master/scene/3d/lightmap_gi.cpp#L1168

I haven't had the time yet to go through the ResourceLoader object and see why imports might be failing when running the game, but is fine while in the editor. I suspect the issue is to do with the ResourceLoader/importing and not the approach taken here. EDIT: I've looked a little into the ResourceLoader from the docs. Importing can't be done at runtime, so I'm fairly confident this is the issue here

RisingThumb commented 2 months ago

Hey folks, I've thrown together something that works for runtime baking. See this commit: https://github.com/RisingThumb/RisingThumb-Godot/commit/c969526d0f93781abfd1e6e3af948aa830a7b9cf It's not perfect mind, there's several changes here that I'm unhappy with. First was using .png instead of .exr. This was only because it was easier to work with, but I should be able to add it back(I think I had some weird import issues with it, but I can't remember). Secondly is with this spot of code

    // We can't use resource saver for the gi_data in-game
    // I'd like to save it though... TODO: Add an else condition!
    if (Engine::get_singleton()->is_editor_hint()) {
        Error err = ResourceSaver::save(gi_data);

        if (err != OK) {
            return BAKE_ERROR_CANT_CREATE_IMAGE;
        }
    }

Ideally we can save the resource while in-game, but that doesn't currently seem to be possible. Am I missing something here with ResourceSaver?

Calinou commented 2 months ago

This was only because it was easier to work with, but I should be able to add it back(I think I had some weird import issues with it, but I can't remember).

Godot can't save OpenEXR in release builds; this is only enabled on editor builds for binary size reasons. We could add a SCons option to allow saving OpenEXR in exported projects: https://github.com/godotengine/godot/pull/73003

That said, for runtime baking, you could save to Godot's own texture format (that is, a .ctex or .res file). This works in exported projects, even for HDR pixel formats.

Note that in both cases, exported projects can't perform VRAM texture compression (they can only decompress). This is also reserved to editor builds for binary size reasons. VRAM compression is currently not used by default in lightmapping, but https://github.com/godotengine/godot/pull/91535 would make it fast enough to be viable as a default.

RisingThumb commented 2 months ago

Thanks for the pointers on this Calinou, you can see all the changes so far in this commit: https://github.com/V-Sekai/godot/commit/1ef3cdbca33330cbc80adfc8db6cd87f6994821d I reverted back to using .exr files for baking in-editor, however baking in-game it now saves to a .res file. Since the importer can't be used in-game or in exported binaries(unless I'm missing something...). I also fixed the resource saving for .lmbake files, it seems to be something to do with the path getting mangled at runtime? Getting the basename and adding .lmbake on the end and passing it to the ResourceSaver works for this.

There is 1 issue with this commit that I've not been able to narrow down yet. When a lightmapGI has environment mode set to Scene(the default) or Custom Sky, the lightmap texture ends up mangled, BUT only when baking at runtime(in editor this still ends up fine). When set to disabled, this doesn't seem to happen 😕