Unique Color is assigned to all instances of Object

Godot Version

4.3 (Steam)

Question

I have an object that creates n amount of objects. Each of these should be assigned a unique color.

for i in numberOfCables:
	var cableInstance = cable.instantiate()
	var color = Color(randf(),randf(),randf())
	cableInstance.set_color(color)
	[...]
	cables.append(cableInstance)
	add_child(cableInstance)

in this case, set_color(color) is run.

extends Node3D
[...]
func set_color(color):
	var mesh_instances = find_children("*", "MeshInstance3D", true, false)
	print(mesh_instances)
	for mi in mesh_instances:
		var mesh = mi.get_mesh()
		# Ensure there's a material on the surface before modifying
		if mesh and mesh.surface_get_material(0):
			# Duplicate the material to create a unique instance
			var material = mesh.surface_get_material(0).duplicate()
			# Set the albedo color
			if material is StandardMaterial3D:
				material.albedo_color = color
			# Apply the unique material to the mesh surface
			mesh.surface_set_material(0, material)

This script duplicates the color-changing material, and assigns it to the new instance, yet all of the created Cables have the same color in the end!
image

1 Like

Your code looks great BTW, a pleasure to read.

Are you sure you are using StandardMaterial3D?

In the docs it also says:

Alpha channel in albedo color and texture is also used for the object transparency. If you use a color or texture with alpha channel, make sure to either enable transparency or alpha scissoring for it to work.

Might be worth checking.

1 Like

Looks to be one!
image
I am loading the models in as .blend files, but as far as Iā€™m aware, this shouldnā€™t be causing this issue.

Additionally, the color is changed from the default (the red tone you see in the material preview) so it definitely is using StandardMaterial3D and applying the color. The color is just applied to all of the objects, despite me explicitly duplicating the materials to make them unique to each of them.

Just to check, how about adding a 1.0 alpha value to the colour.

var color = Color(randf(), randf(), randf(), 1.0)

I did some more testing. Even assigning an external Material directly seems to cause this issue.
I am guessing the issue is that each cable is instantiated, instead of being a unique object.

This seems to change nothing, unfortunately.

It was a long shot :slight_smile:

But even if they are instantiated, you have taken the material, copied it, made it unique and replaced it. I honestly canā€™t see anything wrong with your code.

How about having it without a material on instantiation. Then adding a unique material?

Have you tried checking ā€˜local to sceneā€™? I think that forces non sharing of resources.

Iā€™ve tried it with this as a test.

This part is responsible for choosing the to-be-applied materials.

var redRubberMaterial = preload("res://materials/redRubber.tres")
var whiteRubberMaterial = preload("res://materials/whiteRubber.tres")

func chooseMaterial(index):
	var material = redRubberMaterial;
	if (index%2==1):
		material = whiteRubberMaterial;
	return material;

Thos section acts like before, but instead of changing the color of a material, it instead replaces the material entirely with either redRubberMaterial or whiteRubberMaterial.

for i in numberOfCables:
	var cableInstance = cable.instantiate()
	cableInstance.set_material(chooseMaterial(i))
	cableInstance.cableId = i+1
	cableInstance.position = cableSpawn.position
	cableInstance.position.x += -0.3 + float((0.6/numberOfCables)*(i+0.5))
	cables.append(cableInstance)
	add_child(cableInstance)

This is the code responsible for changing the material.

func set_material(material):
	var mesh_instances = find_children("*", "MeshInstance3D", true, false)
	for mi in mesh_instances:
		var mesh = mi.get_mesh()
		if mesh and mesh.surface_get_material(0):
			mesh.surface_set_material(0, material)

However, no matter what, only the whiteRubberMaterial is ever shown.
image

Right, but how do I do this in code? These nodes are created at runtime through var cableInstance = cable.instantiate() and then added to the hierarchy by add_child(cableInstance)

Try this:

Doesnā€™t seem to change the result, unfortunately.
image

Did you try clicking ā€˜Local to sceneā€™?

I have no other ideas, sorry. This must be driving you up the wall! Your code looks spot on, I will have a look if there are any bug reports.

Right, but where? What should be local to scene?
All of the relevant nodes do not offer this as an option.
image
This is the hierarchy of the created Object. In this case, the materials on mdl_cable_origin, mdl_cable_destination, mdl_plug_origin and mdl_plug_destination should be changed to the desired one.

I think the issue is because I am using Blender files directly. This may be a hint?
image

I am guessing this answers my Question.
https://forum.godotengine.org/t/changing-material-of-child-scene/74605/8

1 Like

I was about to post you this:

To import Blender files into Godot, you can follow these steps:
In Godot 4.0 onwards, the editor can directly import .blend files by calling Blenderā€™s glTF export functionality transparently.
In Godot project settings, under Import Defaults, select ā€œSceneā€ from the Importer Dropdown.
Scroll down to ā€œImport Script Pathā€ and load the standard import path for the addon: res://addons/blender_godot_pipeline/GLTFImporter.gd. This ensures that all your GLTF or .blend files get the importer attached

This finally allowed me to set the plug model as local to the scene.
This should really be a default feature of Godot, lel.
Anyhow, that solved it. Thank you!

1 Like