Open Pixelmusement opened 4 months ago
Add my support for this.
Here's my use case (Similar to @Pixelmusement 's, but noting @Jesusemora 's confused emjoi, perhaps this usecase is easier to describe) :
Want to draw tile grid, created from say 4 types of tile meshes (T0, T1, T2..), with 20 materials (M0, M1, M2...)
Want to draw say 200 tiles. For each tile instance choose one tile mesh and one material. e.g. tileinstance[0,0]=T3,M11. tileinstance[0, 1]=T8,M6, tileinstance[0, 2]=T3,M19, etc...
Using MeshInstance3D this is staggeringly easy. Create one MeshInstance3D for each tileinstance, assigning the mesh and material as you go. Nice!
T = MeshInstance3D.new();
T.mesh = T3;
T.set_surface_override_material(0, M11);
add_child(T).
But once your grid gets large (say 2,000 tile instances or more), that's a lot of MeshInstance3Ds!
MultiMeshInstance3D should be able to do this, but the inability to override materials runs aground:
T = MultiMesh.new()
T.transform_format = MultiMesh.TRANSFORM_3D
T.instance_count = len(40) # example
T.visible_instance_count = T.instance_count
T.mesh = T3;
T.set_instance_transform(i, position[i]) # for loop
TI = MultiMeshInstance3D.new()
TI.multimesh = T
# PROBLEM HERE: You can't assign a specific material to the Multimesh.
TI.multimesh.mesh.surface_set_material(0, M11) #### BECAUSE THIS WORKAROUND MODIFIES ALL T3(*)
TI.multimesh.set_surface_override_material(0, M11) #### NO SUCH FUNCTION, BUT THIS WOULD BE HELPFUL!
add_child(TI)
Recapping: The problem is with MultiMeshInstance3D
because you can't specify a material as you can with MeshInstance3D::set_surface_override_material().
Workarounds aren't good: If you try with MultiMeshInstance3D::multimesh.mesh.surface_set_material()
( above) that modifies everything else using that mesh. So later when you try and draw T3 again but with M19, it clashes with M11. So all* instances of T3 end up with the last material you set.
The only workaround I found was to make separate copies of the meshes, either by duplicating the mesh file on disk (same file, different filenames) or duplicating as you load them, but either way, that's wasteful of memory and clunky (duplicating is non-trivial).
So support @Pixelmusement 's suggestion: Addingset_surface_override_material()
to MultiMeshInstance3D
, like it is on MeshInstance3D
, would be great.
Want to draw say 200 tiles. For each tile instance choose one tile mesh and one material. e.g. tileinstance[0,0]=T3,M11. tileinstance[0, 1]=T8,M6, tileinstance[0, 2]=T3,M19, etc...
That's not actually what I was proposing. The use case you're looking for is to be able to decide each material on a per-tile basis. That should definitely continue to be done by using standard MeshInstance3D nodes. Having 2,000 of them is not a problem; In a fully functional 3D game world your node count could easily exceed 10x that! ;)
What I am proposing is simply adding the Surface Material Override property to MultiMeshInstance3D to optimize node counts and memory use when using a MultiMeshInstance3D node to represent a customizable object. What this would do is make it so that if you apply a surface material override to such an object, ALL meshes rendered by that object will change appearance. For the use case I was indicating, the way I handle stairs is by allowing players to set their height, with each increase in height increasing the visible instance count on the MultiMesh. If players change the materials on the surfaces of the stairs the entire staircase should change, but there's presently no Surface Material Override property to do this with.
For the moment, my workaround to make this work is to make the actual Mesh resource in the MultiMesh Local to Scene, but what this means is every time the player adds a new staircase into their world it's placing another copy of the base mesh into memory, which would be unnecessary if a Surface Material Override property was present like it is with MeshInstance3D.
A different workaround would be to add or remove MeshInstance3D nodes as the staircase changes size, and with the granularity I've assigned (1 to 16 with 1 = 1 M tall) that wouldn't be that terrible, but I'm imagining an instance where someone may need finer granularity or larger runs for something like a ladder and could end up with 200+ nodes representing a single extra-tall ladder. Place 50 of those ladders in your world and that's suddenly 10,000 nodes representing only 50 ladders. In that event, using my current workaround would be better, but that still would mean 50 copies of the base mesh being loaded into memory.
Plus, part of why I proposed this change is because Surface Material Override properties already exist for MeshInstance3D. Porting that over to MultiMeshInstance3D should be relatively straightforward, though to be fair, MultiMeshInstance3D objects are kinda lagging behind in terms of their feature-set. To make my staircases work I had to go through and manually input all of the transform data for every visible instance in the MultiMesh, as the only tool for automatically assigning transform data right now does it through random scattering, and while I could've written a script to do it automatically it would've taken me longer to learn how to write it correctly than to input the data by hand. :P
Hey @Pixelmusement , I think we are proposing the same change, albeit for different use cases, though I do take note of @Jesusemora 's emoji these are hard to describe clearly.
@Pixelmusement: That's not actually what I was proposing. The use case you're looking for is to be able to decide each material on a per-tile basis. That should definitely continue to be done by using standard MeshInstance3D nodes. Having 2,000 of them is not a problem; In a fully functional 3D game world your node count could easily exceed 10x that! ;)
Nah. I've been testing a game on different hardware platforms. On an nVidia GPU, you can leave everything as MeshInstance3D and it's fine because the GPU is so beefy. But run it on an Intel GPU, even with a relatively low number of tiles, and it quickly becomes intolerably slow, unless you switch to MultiMeshInstance3D which significantly improves performance. The only drawback is needing to duplicate the meshes, which is wasteful of memory.
So despite the different use cases, I'm still onboard with your request:
@Pixelmusement: Plus, part of why I proposed this change is because Surface Material Override properties already exist for MeshInstance3D. Porting that over to MultiMeshInstance3D should be relatively straightforward, though to be fair, MultiMeshInstance3D objects are kinda lagging behind in terms of their feature-set.
Hmm... I suspect you might be running into a different performance bottleneck which just so happens to get resolved by using a MultiMesh instead of a standard Mesh. For instance, if you're making your shaders Local to Scene in order to adjust certain properties then the Intel GPU may be struggling with handling and switching between hundreds-to-thousands of copies of the same shader in memory when using MeshInstance3D in place of MultiMeshInstance3D, in which case you'd want to separate out shader values into Instance Parameters where you can and split off any shaders where you can't do that into separate shaders so as to avoid having to make the shaders themselves Local to Scene. A good test to perform would be to temporarily adjust your code to ONLY apply a single material and shader which is not Local to Scene to all tiles using MeshInstance3D and see what happens with performance. :B
Granted, I could be wrong; I've never attempted to develop for low-spec 3D hardware before so I don't actually know just how severe the limitations are; just seems weird to me that you would get such a dramatic drop in performance using only a small number of MeshInstance3D objects.
Hey @Pixelmusement, I don't think it's anything so magical. Intel GPUs are low-spec, but not so bad: You just need to write efficient code instead of relying on brute force as you can with nVidia. But even on an nVidia, if you have to render 500 identical tiles, it's much more efficient to use just a single MultiMeshInstance3D instead of creating 500 _MeshInstance3D_s to do the same thing.
So to get back to your original proposal, I'm agreeing: I want to use MultiMeshInstance3D, and I want to use it with _set_surface_overridematerial(), like MeshInstance3D. That's all.
@Pixelmusement I looked into implementing this, and got partway through (mostly for personal reasons, to learn about the rendering server). On the server side it looked like much of the infrastructure was already there - the RenderingServer representation of a multimeshinstance already has the required fields for per-surface overrides. It felt more like filling in a gap in the API than adding something new. I actually got stuck on the custom inspector code on the editor side.
So this seems technically feasible at least. It is a niche use case, but a valid one.
Describe the project you are working on
I am building world-editing tools into my current project which function from a first-person and VR perspective. These tools include the ability to move, rotate, and resize objects as the user sees fit, which then dynamically update the appearance and collision properties of those objects, with everything locked to grid units. This allows the user to rapidly create whole worlds suitable for the intended gameplay with minimal 3D development knowledge.
Describe the problem or limitation you are having in your project
In trying to implement stairs which can be dynamically resized by the player, my thought was to use Multimesh objects to avoid having to create/destroy numerous mesh instances on the fly, instead simply adjusting the number of Multimesh instances visible as the stairs are made taller or shorter. The problem I ran into though is that I use Surface Material Overrides extensively with objects which can be placed into the world so that the user can effortlessly select materials to apply to various material slots on the objects in the world. MultiMeshInstance3D does not expose Surface Material Overrides and as such, the only solution I have at the moment is to create a special case in my code for stairs whereby the mesh resource used is made Local to Scene, then have any materials applied to the stairs applied directly to its local mesh resource.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Having Surface Material Override present on MultiMeshInstance3D objects would allow overriding the individual material slots on the mesh without having to make the mesh used Local to Scene. It would better optimize what I am doing.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
The Surface Material Override would work just like with a MeshInstance3D object, except because the mesh in a Multimesh is repeating numerous times ALL instances of the mesh rendered by the Multimesh would gain the overrides selected.
If this enhancement will not be used often, can it be worked around with a few lines of script?
It can be, but requires using a Local to Scene mesh and overriding its material slots directly. It's not that what I need to do can't presently be done, it's just not as optimized as it could be with this proposed feature.
Is there a reason why this should be core and not an add-on in the asset library?
I suspect it would be much, much easier to implement this feature through the core than through an add-on given that MeshInstance3D already has a Surface Material Override feature; it simply needs to be propagated over to the MultiMeshInstasnce3D object.