Changing a specific color in a sprite using shaders in godot 3

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By rustyStriker
:warning: Old Version Published before Godot 3 was released.

Im trying to make a fantasy game which the user can choose from a multiple types of abilities and i wanted to make it so for each type some colors on the player’s sprite will change, for example from white to red(for fire).

I went through the docs but couldn’t make it work, and barely understood the way the shaders in godot 3 works…

can anyone help me with this one or hook me up with a good tutorial about it?

:bust_in_silhouette: Reply From: Zylann

I played around with color replacement and started with this simple shader which is able to replace only a specific color:

shader_type canvas_item;

uniform vec4 u_color_key : hint_color;
uniform vec4 u_replacement_color : hint_color;

void fragment() {
	vec4 col = texture(TEXTURE, UV);
	vec4 d4 = abs(col - u_color_key);
	float d = max(max(d4.r, d4.g), d4.b);
	if(d < 0.01) {
		col = u_replacement_color;
	}
	COLOR = col;
}

Then I went further and made a shader that can replace a color by another while keeping the shading and transparency intact:

shader_type canvas_item;

// Which color you want to change
uniform vec4 u_color_key : hint_color;
// Which color to replace it with
uniform vec4 u_replacement_color : hint_color;
// How much tolerance for the replacement color (between 0 and 1)
uniform float u_tolerance;

void fragment() {
	// Get color from the sprite texture at the current pixel we are rendering
	vec4 original_color = texture(TEXTURE, UV);
	vec3 col = original_color.rgb;
	// Get a rough degree of difference between the texture color and the color key
	vec3 diff3 = col - u_color_key.rgb;
	float m = max(max(abs(diff3.r), abs(diff3.g)), abs(diff3.b));
	// Change color of pixels below tolerance threshold, while keeping shades if any (a bit naive, there may better ways)
	float brightness = length(col);
	col = mix(col, u_replacement_color.rgb * brightness, step(m, u_tolerance));
	// Assign final color for the pixel, and preserve transparency
	COLOR = vec4(col.rgb, original_color.a);
}

But I found a bug when trying it, I hope it will gets fixed: If two sprites have the same shader but different materials, the second one's parameters are ignored · Issue #14012 · godotengine/godot · GitHub

thanks, it really helped me understand how to shade what i want, anyway hope they will fix that bug(even tho it doesn’t matter to me right now)

rustyStriker | 2017-12-06 11:08

Thanks! Why can’t I just compare the color values directly like:

if (original_color.rgb == u_color_key.rgb)

I tried and it did not work, and I see that you do some math, I think comparing the difference between the colors to a very small quantity, but… why? The color I get from the gimp color picker tool is not “really” the color shown on screen by godot?

tonyrh | 2018-11-12 14:41

This is to rule out floating point error. Shaders work with floating point math, and colors you set in shader parameters are 32-bit floats, not 8-bit RGB values like GIMP shows them. So unless you make sure that every color at any point is clamped to the 8-bit value range, it’s just safer to handle a threshold because comparing two floats for equality doesn’t alway works out.

Zylann | 2018-11-12 19:21