godotengine / godot-proposals

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

Allow Baking OccluderInstances from script #4316

Open timshannon opened 2 years ago

timshannon commented 2 years ago

Describe the project you are working on

A 3D FPS with procedurally generated levels.

Describe the problem or limitation you are having in your project

With procedurally generated geometry you have to manually add the occluder geometry to the ArrayOccluder3D by setting the Arrays value.

This works fine, but you miss out on the mesh simplification done during the Editor Bake. Also the simplification used isn't exposed anywhere you can access it in a script (that I can find, please correct me if I'm wrong here).

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

Expose the bake_scene(Node *p_from_node, String p_occluder_path = "") method to scripts.

From there you could call it on the fly, or via a scene import script.

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

Expose the bake_scene(Node *p_from_node, String p_occluder_path = "") method to scripts.

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

A 1 to 1 behavior can't be replicated from scripts because we don't have access to the simplification features in the SurfaceTool.

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

It could probably be done in an addon, but it'd be an addon that simply exposes an existing method to scripts.

Calinou commented 2 years ago

cc @JFonS, who implemented occluder simplification on bake in https://github.com/godotengine/godot/pull/57627.

Is meshoptimizer compiled in release export templates? If not, mesh simplification at run-time will not work in an exported project. This might be worth checking with SurfaceTool as well. If this isn't done already, I think meshoptimiser should be available in exported projects – it probably doesn't impact binary size too much.

timshannon commented 2 years ago

FWIW, It looks like SurfaceTool exposes a generate_lod method that I think possibly uses the same meshoptimizer library.

Calinou commented 2 years ago

FWIW, It looks like SurfaceTool exposes a generate_lod method that I think possibly uses the same meshoptimizer library.

Indeed, it uses meshoptimizer behind the scenes.

timshannon commented 1 year ago

If anyone is trying to do something similar, this is the solution I came up with that appears to be working fine for me:

private void AddLevelOccluderInstance()
    {
        var instance = new OccluderInstance3D();
        instance.Name = "OccluderInstance3D";
        _root.AddChild(instance);
        instance.Owner = _root;
        var occluder = new ArrayOccluder3D();
        var occSt = new SurfaceTool();

        RecurseTreeOfType<MeshInstance3D>(_root, (mesh) =>
        {
            if (!IsPartialStructureMesh(mesh))
                return;

            var st = new SurfaceTool();

            var m = mesh.Mesh;
            var blnFound = false;

            for (var i = 0; i < m.GetSurfaceCount(); i++)
            {
                var mat = m.SurfaceGetMaterial(i) as StandardMaterial3D;
                if (mat == null)
                    continue;
                if (mat.Transparency != StandardMaterial3D.TransparencyEnum.Disabled)
                    continue;

                st.AppendFrom(m, i, Transform3D.Identity);
                blnFound = true;
            }

            if (!blnFound)
                return;

            var arr = st.CommitToArrays();
            var simplified = st.GenerateLod(0.1f);
            arr[(int)ArrayMesh.ArrayType.Index] = simplified;
            var smesh = new ArrayMesh();
            smesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arr);

            for (var i = 0; i < smesh.GetSurfaceCount(); i++)
                occSt.AppendFrom(smesh, i, GetLocalRootTransform(mesh));
        });

        var arr = occSt.CommitToArrays();
        var vertices = (Vector3[])arr[(int)ArrayMesh.ArrayType.Vertex];
        var indices = (int[])arr[(int)ArrayMesh.ArrayType.Index];

        occluder.SetArrays(vertices, indices);
        instance.Occluder = occluder;
    }
cybernaut4 commented 1 year ago

Would be great to have this implemented in one built-in function like BakeOccluders()

Because it currently asks where to save it, a path parameter would suffice:

occluderInstance3D.BakeOccluders("res://Assets/LevelMaps/Level1.occ")

Tools that auto-generate geometry (like Qodot) would benefit from this: I would press the "Full Build" button so that it generates lots of custom-defined groups of geometry from a .map file (a level designing format) and automatically bake their occluders while at it.

The current state would have me add each OccluderInstance child and hit the Bake Occluders button on every single wrapping Node3D in every re-generation. In advanced map prototyping, I'd have to repeat this about 100 times if I wanted the occluders working after every change.

TrueJole commented 1 week ago

Would be great to at least be able to bake via a @tool script so that I don't have to open the large world scene anytime I change geometry.