Unable to change clothing 2D sprite colors in game using the ColorPickerButton, but it works in inspector (RBG Method)

Godot Version

4.3

Question

` This may belong in the programming section because I’m not sure what I did wrong. I am making a small character creator and everything has been working perfectly until this point.

I am trying to make it so that my clothes can be changed through RBG values so it can be recolored with multiple colors. (I used pure red, green, and blue colors so for example, the shirt itself is red, but the sleeves are green). I created the shader and attached it to a .tres ShaderMaterial file, which is called in my script using const Character = preload(“res://Shaders/ColorMaskMaterial.tres”)

This shader in the inspector works, and in the inspector I can change the clothes just fine. The problem is when I run in game and use the ColorPickerButton, the colors do not change at all. I am not sure if this is a shader issue or a programming issue, but this is the shader code:

shader_type canvas_item;

uniform vec4 red_color : source_color = vec4(1.0, 0.0, 0.0, 1.0);
uniform vec4 green_color : source_color = vec4(0.0, 1.0, 0.0, 1.0);
uniform vec4 blue_color : source_color = vec4(0.0, 0.0, 1.0, 1.0);

void fragment() {
vec4 tex_color = texture(TEXTURE, UV);
vec4 final_color = tex_color; // Start with the original texture color

// Check each channel independently and ONLY modify if the channel is NOT 0
if (tex_color.r > 0.0) {
    final_color.r = red_color.r;
    final_color.g = red_color.g;
    final_color.b = red_color.b;
}
if (tex_color.g > 0.0) {
    final_color.r = green_color.r;
    final_color.g = green_color.g;
    final_color.b = green_color.b;
}
if (tex_color.b > 0.0) {
    final_color.r = blue_color.r;
    final_color.g = blue_color.g;
    final_color.b = blue_color.b;
}

COLOR = final_color;

}

If I need to go into the programming section, I’m really sorry for placing this in the wrong section. I’ve been stumped on this for a while now and feel like I’m missing something very obvious.

Please let me know if you need further information! I’m desperate to get this working and feel like I’m missing a very obvious step.

Extra note:
The ColorPickerButtons do work for the skin color and eye color and hair color change, but those do not use a shader or the RBG method since they are just one color for the sprite. Since the colors of the shirt does change through the inspector, I suspect I missed something with the ColorPickerButton which is why I’m wondering if this may be a programming issue instead of a shader issue, but I wasn’t sure which category to place this question in.
I have the ColorPickerButtons set as RedColorPicker, GreenColorPicker, BlueColorPicker

`

We would need to see your script. The shader seems fine and you state it does work in the inspector.

I’ve created a similar shader for color picking 3D models but allowing blending

shader_type spatial;

render_mode cull_disabled;

uniform vec4 high_color: source_color = vec4(1);
uniform vec4 mid_color: source_color = vec4(1);
uniform vec4 low_color: source_color = vec4(1);

void vertex() {
	vec4 high_val = high_color * COLOR.r;
	vec4 mid_val = mid_color * COLOR.g;
	vec4 low_val = low_color * COLOR.b;
	
	COLOR = low_val + mid_val + high_val;
}

void fragment() {
	// Place fragment code here.
	ALBEDO = COLOR.rgb;
}

Sorry I’m a bit new at this, but should I send the full code or just snippets since it’s almost 300 lines? I’m more than happy to send it over through the forum or over DM if that’s the best approach. Thank you!

Paste the part of the script that changes the shader parameters / uniforms

const Character = preload("res://Shaders/ColorMaskMaterial.tres")
func _ready():
	if is_instance_valid(red_color_picker):
		red_color_picker.color_changed.connect(_on_clothes_color_changed.bind(shirt_sprite, Color.RED))
		red_color_picker.color_changed.connect(_on_clothes_color_changed.bind(pants_sprite, Color.RED))
		red_color_picker.color_changed.connect(_on_clothes_color_changed.bind(full_outfit_sprite, Color.RED))
		red_color_picker.color_changed.connect(_on_clothes_color_changed.bind(shoes_sprite, Color.RED))

	if is_instance_valid(green_color_picker):
		green_color_picker.color_changed.connect(_on_clothes_color_changed.bind(shirt_sprite, Color.GREEN))
		green_color_picker.color_changed.connect(_on_clothes_color_changed.bind(pants_sprite, Color.GREEN))
		green_color_picker.color_changed.connect(_on_clothes_color_changed.bind(full_outfit_sprite, Color.GREEN))
		green_color_picker.color_changed.connect(_on_clothes_color_changed.bind(shoes_sprite, Color.GREEN))

	if is_instance_valid(blue_color_picker):
		blue_color_picker.color_changed.connect(_on_clothes_color_changed.bind(shirt_sprite, Color.BLUE))
		blue_color_picker.color_changed.connect(_on_clothes_color_changed.bind(full_outfit_sprite, Color.BLUE))
		blue_color_picker.color_changed.connect(_on_clothes_color_changed.bind(pants_sprite, Color.BLUE))
		blue_color_picker.color_changed.connect(_on_clothes_color_changed.bind(shoes_sprite, Color.BLUE))

I think those are it!

Ah, can you show the definition of func _on_clothes_color_changed? That seems like where it should actually change the shader parameter/uniform.

func _on_clothes_color_changed(color, sprite: Sprite2D, mask_color: Color):
	if is_instance_valid(sprite):
		change_sprite_color(sprite, mask_color, color)

Here you go! Thank you so much for looking into this for me. If there are any other lines of code you need to see, please let me know!

So a shader will require the function set_shader_parameter. Where do you call set_shader_parameter on your Material in a GDScript?

Hi!
Sorry for the late reply. I forgot to do that initially and I did add this under my func _ready(): while getting rid of the const Character = preload(“res://Shaders/ColorMaskMaterial.tres”) up top since it looked like they were both preloading the same shader.

	var shader_material = ShaderMaterial.new()
	shader_material.shader = preload("res://Shaders/ColorMaskShader.gdshader")

The colors still haven’t changed with this line of code, so I may have written it wrong or I may need to add more to this. Sorry if this is super obvious, this is my first time using a shader since I’ve be intimidated of them in the past.

So I do not think you are ever changing the shader parameters through code if you cannot find a set_shader_parameter call.

Here’s an example using my pasted shader, how I load the values from a file, notice I have to call set_shader_parameter for each mix-in color.

func load_stats_10(file: FileAccess) -> bool:
	material.set_shader_parameter("high_color", file.get_var() as Color)
	material.set_shader_parameter("mid_color", file.get_var() as Color)
	material.set_shader_parameter("low_color", file.get_var() as Color)

In your case I have to assume this would be a good place to put the shader code, or inside of change_sprite_color would make sense.

Assuming sprite holds your ShaderMaterial, color is a string either “red”, “green”, or “blue”, and mask_color is your replaced color to submit to the shader uniform you could add this.

func _on_clothes_color_changed(color, sprite: Sprite2D, mask_color: Color):
	if is_instance_valid(sprite):
		change_sprite_color(sprite, mask_color, color)
		var shader_material: ShaderMaterial = sprite.material
		shader_material.set_shader_parameter("%s_color" % color, mask_color)
1 Like

It works! I cannot thank you enough! Now I just need to make it so it will change the shirt and pants and shoes separately, but the fact you were able to fix my spaghetti code means a lot to me.

Thank you so much again!