I moved to Godot 4.0.2 yesterday and noticed strange behaviour with the COLOR value in the fragment() function of my shader. I have a GPUParticles2D particle system that has a basic shader applied to its material:

shader_type canvas_item;
render_mode blend_mix;

vec3 invert_color(vec3 col_in){
    return 1.0 - col_in;
}

void fragment() {
    vec4 img_col_rgba = texture(TEXTURE, UV);  // original image
    vec3 inv_img_col_rgb = invert_color(img_col_rgba.rgb); // inverted image

    vec3 tint_col_rgb = COLOR.rgb;  // tint color
    vec3 inv_tint_col_rgb = invert_color(tint_col_rgb);  // inverted tint color

    vec3 rgb = invert_color(inv_img_col_rgb * inv_tint_col_rgb);  // tint the image
    vec4 rgba = vec4(rgb, img_col_rgba.a);  // transfer old alpha value to the new image
	
    COLOR = rgba;  // apply changes
}

This shader is supposed to tint only the black part of the particle texture by inverting the texture colours, inverting the tint colour, mixing them together, and inverting the tinted texture once more. This results in the outer part of the particle being coloured, while the inside stays white.

This worked beautifully in Godot 3.5:

But in Godot 4.0.2 it seems to only tint the gray transition between white and black:

I noticed that the shader works as expected if I replace COLOR.rgb in line 12 with a static colour like vec3(1, 0, 0).
And since the exact same code works in older Godot versions as well as in other engines like Unity, I'm assuming that the COLOR changed its behaviour in Godot 4. Is this a bug or am I missing something? Is there maybe a better way to achieve this effect all together?

  • Sorry for the very long pause - lots of final exams.

    Anyways, after tinkering around some more I determined that the shader, or at least the very specific implementation that I want, is simply no longer possible in version 4. Instead I'm going to use a texture atlas of 8 frames containing pre-coloured versions of the same particle that I animate using frame interpolation.

    Visually it doesn't make a big difference, but it's definitely less convenient from the programming side of things as I now have to create a new atlas for every colour transition instead of switching colour ramps. But better than nothing, I guess.

What happens if you try

vec3 tint_col_rgb = textureLod(TEXTURE, UV, 0.0).rgb;

COLOR is an output AFAIK. What do you expect to be in there?

Yeah I'm new to shader programming and it shows. Anyways...
If i try vec3 tint_col_rgb = textureLod(TEXTURE, UV, 0.0).rgb; there is no colour change, just the plain texture.

I kinda expected COLOR.rgb to contain the tint colour coming from ParticleProcessMaterial, which it obviously doesn't. The error occurs when inverting the texture. I tested this using red colour coming from ParticleProcessMaterial and another red colour from a seperate uniform variable
uniform vec4 _tint_color : source_color = vec4(1, 0, 0, 1);.

In Godot 3, multiplying the negative texture with either COLOR.rgb or _tint_color results in the same behaviour - white becomes red
In Godot 4, this only works with _tint_color

As soon as I multiply the negative texture with COLOR.rgb in Godot 4, the colours become messed up

I could technically tint the particles using the uniform exclusively, but then all particles will have the same colour.
The data in COLOR.rgb must have changed somehow but it's hard to tell what exactly changed...

Thanks everyone for your reply!

Particle shaders do not provide the TEXTURE variable from what I've seen/tried. Maybe pass the texture via script to the shader? Unfortunately, I couldn't look much more into it as my 2D scene broke with a Vulkan pipeline error after restarting Godot and now it refuses to load.

I ran out of time for now, but I will definitely try again in a few days though.

9 days later
2 months later

Sorry for the very long pause - lots of final exams.

Anyways, after tinkering around some more I determined that the shader, or at least the very specific implementation that I want, is simply no longer possible in version 4. Instead I'm going to use a texture atlas of 8 frames containing pre-coloured versions of the same particle that I animate using frame interpolation.

Visually it doesn't make a big difference, but it's definitely less convenient from the programming side of things as I now have to create a new atlas for every colour transition instead of switching colour ramps. But better than nothing, I guess.