Using an if statement in a material fragment script

Hey everyone,

So I want to be able to modify specific pixel colors in a material through the material fragment script. The idea is for me to be able to change a specific pixel color in order to provide more color combinations for materials through modifying specific colors. Think of it like a green screen or a chroma key within the game where I can swap out colors during runtime.

What I ultimately want to use this for is a character creation system and being able to change eye / hair / skin colors without having to create entirely different spine skins for every possible color combination. I would just be able to use commands such as go.set(“my/url”, “color_eyes”, vmath.vector3(1,0,0)) instead of manually recoloring eyes and manually creating skins with every possibility.

My Questions are:

  • Can I use an if statement in a material fragment script? If so, how?
  • Is there another way I should be doing this?
  • For future reference, what scripting language is the material fragment script in?

Thank you! I hope to showcase my character creation system once I get this and a few other things ready!

1 Like

I think I’ve seen others do this and share their solutions on the forum. @Pawel did it for his game Witchcrafter: Empire Legends

It is GLSL

2 Likes

Yes, syntax is like in C++ :

if(){}

or

if(){}else if(){} else{}

To do what you want (pallete modifications) take a default sprite.fp as a base of your new fragment program:

varying mediump vec2 var_texcoord0;

uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

void main()
{
    // Pre-multiply alpha since all runtime textures already are
    lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
    gl_FragColor = texture2D(texture_sampler, var_texcoord0.xy) * tint_pm;
}

above, the color of the pixel is taken (from texture2D) and only multiplied by tint_pm and written to gl_FragColor which is an output color.

You want to take a color from texture and not write it to the output, but to a variable, that you can operate on, so make a vector called color_of_pixel and take a color from texture2D:

lowp vec4 color_of_pixel = texture2D(texture_sampler, var_texcoord0.xy);

Then you want to modify the color if it meets your requirement, so here you can write for example:

if (color_of_pixel == base_hair_color) {
    pixel_color = my_new_hair;
}

^ This will take the pixels that has the same color as base_hair_color (e.g. black) and change its color to my_new_hair e.g.white;

Don’t forget to define the colors you used in if statement on top of the program or pass it using a shader constant (more advanced).

lowp vec4 base_hair_color = vec4(0.0, 0.0, 0.0, 1.0); // black  - i just wrote it on top of program
lowp vec4 my_new_hair = vec4(1.0, 1.0, 1.0, 1.0); // white- i just wrote it on top of program

Write as many if statement as you need using else if construct
In the end write the color_of_pixel to the output variable called gl_FragColor and eventually multiply it by the tint as in the original sprite fragment program.

The whole program can look like this:

varying mediump vec2 var_texcoord0;

uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

lowp vec4 base_hair_color = vec4(0.0, 0.0, 0.0, 1.0); // black  - i just wrote it on top of program
lowp vec4 my_new_hair = vec4(1.0, 1.0, 1.0, 1.0); // white- i just wrote it on top of program

void main()
{
    // Pre-multiply alpha since all runtime textures already are
    lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
    lowp vec4 color_of_pixel = texture2D(texture_sampler, var_texcoord0.xy);
    if (color_of_pixel == base_hair_color) {
        pixel_color = my_new_hair;
    }
    gl_FragColor = color_of_pixel * tint_pm;
}

Then add such program to a new material and apply this material to a sprite (with black hair :smiley: )
Run it and hair should be white :wink:

This is the most simplest version and will work for all black pixels, so I;m using special colors for sprites I want such modifications to have (aka only hero sprite:)

png

You see here I used a specific color pallete - cloak, gloves, sword and jacket are modifiable and use basic colors (red, green, blue, magenta, cyan, yellow, black, white) so in shader program for that particular sprite there is no option that when I change color for cloak, some other colors will be changed, because there is no other part using pure red nor black color :wink: Tinker a little bit with it and maybe you won’t need to recolour your sprites :wink: just getting RGB values of your current sprites will be enough (I did that for the first version, something like: if (color == vec4(55/255, 60/255, 0/255, 1.0))

If you will have any questions, don’t hesitate to ask :wink:

8 Likes

I finally finished my Defoldmine huge tutorial about shaders and it could help you as well!

5 Likes

Thank you @Pawel ! That post has been extremely helpful and was exactly what I needed! With a little experimentation I was able to get it to look and more importantly function in the way I needed it to. Thank you again!

For others wanting to do this with spine instead of sprites, here’s something else I found:

In order to get the effect to work with the spine model, I had to modify the game.project graphics settings.

Under graphics there is “Default Texture Min / Mag filter” both of these need to be set to nearest instead of linear. If it is linear, the lines between the colors on the spine model will start to fade into each other as the engine tries to smooth it out. Since the fragment program only is looking for one specific color, it will ignore this faded color. This could be fixed with a complex if statement searching for pixels within a range. However, if you use nearest, this fading effect will not happen and the colors stay constant. This does cause animation to look a little less smooth but I feel as if that if a good cost to pay in order to exponentially increase the amount of use you can get out of a single spine model.

1 Like

I’m so glad that I could help you! :blush: Don’t hesitate to show off the effect! :wink:

1 Like

Hey, so I’ve run into some issues getting the effect to work within a Gui. I’ve created a custom gui fragment program but it only partially works. It displays the default values I give it but I can’t seem to modify it during runtime from within the gui script. In the API, the only thing I saw was go.set(), I’ve tried to implement this solution by messaging to another script to change the material values in the gui but that didn’t work. Even if it did work, this is not what I wanted as I want several spine models (all the same material) in a gui but with different appearances or colors keyed out. Going from the GO would change all of them at once.

Is there anyway to modify material values within a gui script?
Can I modify the material values for specific nodes from within a Gui Script?
If this is not possible, how would you recommend approaching this?

Hmm, I never used spine in GUI, but if it works like other nodes perhaps you can change a color of them using gui.set_color() - it should work probably like a shader you wanted to do anyway - so maybe you could give it a chance? You will be able to tint each bones probably, but maybe it is enough? If not I could take a look, but later this week :wink: