Changing Mesh's Material in _ready

Godot Version

4.2

Question

Some context before I get into the meat of the question: I am using Compatibility rendering so I can deploy in a browser (and will target other platforms too). The nodes I am talking about below roughly have this structure:

Tree (Node3D)
- TreeTrunk (MeshInstance3D)
- Leaves (MultiMeshInstance3D)

There are many instances of this tree across the map.

I am implementing a “fade out” function via Tweening an alpha shader material parameter.

Clearly I will have many instances which use the same material and shader parameters (e.g. I have many Tree mesh instance 3Ds, which all use the same material).

The obvious issue is that when I set the shader parameter, it sets the alpha on ALL instances of this material, not just that MeshInstance3D.

To get around this, I do the following (instance uniforms don’t work in compatibility mode btw…)

func _ready() -> void:
	var tree_mat: ShaderMaterial = tree.get_surface_override_material(0).duplicate()
	tree_mat.set_shader_parameter("albedo", BARK)
	tree.set_surface_override_material(0, tree_mat)

So, effectively, as soon as the node is loaded (actually, its parent), the material is duplicated and then I can tweak the shader parameters however I want for that individual MeshInstance3D.

The Problem

This works great, until I get to my MultiMeshInstance3D nodes.

I have tried a million different combinations of this (includingduplicate() with deep copy set to true), but this is the simplest one that I would expect to work:

func _ready() -> void:
	multimesh.mesh = multimesh.mesh.duplicate()
	var mesh_mat: ShaderMaterial = multimesh.mesh.surface_get_material(0).duplicate()
	multimesh.mesh.surface_set_material(0, mesh_mat)

It simply duplicates the mesh that the multimesh uses, then sets that as the multimesh.mesh (to decouple it from the original resource), then I’m doing a similar thing with the surface material. The material is duplicated (once again, to decouple it from the original resource) then set as the surface.

Despite my best efforts, this does not work, and setting shader parameters via:

	var leaves_mat: ShaderMaterial = leaves.multimesh.mesh.surface_get_material(0)
	leaves_mat.set_shader_parameter("alpha", alpha)

Sets the shader parameter on all instances of that multimesh.

Help plz. Trying to release a game soon ish.

If you’re trying to set the alpha per instance within the multimesh, I think you can use the multimesh.set_instance_color method.

Also, for the non-multimesh instances, to set the alpha independently without duplicating the material, you can use per-instance uniforms. Only thing is you would need to create a custom shader which may or may not be easy depending on your material.

multimesh.set_instance_color overrides the mesh’s vertex colors. I’m using textures for the albedo channel, so that won’t work.

Per-instance uniforms don’t work in compatibility mode and it’s not really what I’m after - the multimesh instances 3D itself is reused in the scene many times, and the fadeout behaviour needs to apply to each multimesh 3D instance (and its mesh instances) independently. I don’t need the fade out to apply to each mesh of the multimesh uniquely.

I got it to work by setting the multimesh “Local to scene”.