0

In my libgdx project I use a custom shader to create different color variations of a single grayscale character. I have a class "Shader" which holds static String variables for the vertex and fragment shaders. For each person I basically create a new shader, by replacing strings in the fragment shader string. For some reason, my characters end up looking good except for an addition at their heads, that looks like its a head turned sideways:

Weird head

The character itself does not have this "additional head" (I've checked in all images):

Character still

What's even weirder, is if I remove the part of the shader that changes the skin color, the problem is gone. All other colors get replaced fine, only the skin seems to be the problem.

My Shader class:

public class Shader {

public static final String vertexShader = "attribute vec4 a_position;    \n" +
        "attribute vec4 a_color;\n" +
        "attribute vec2 a_texCoord0;\n" +
        "uniform mat4 u_projTrans;\n" +
        "varying vec4 v_color;" +
        "varying vec2 v_texCoords;" +
        "void main()                  \n" +
        "{                            \n" +
        "   v_texCoords = a_texCoord0; \n" +
        "   gl_Position =  u_projTrans * a_position;  \n"      +
        "}                            \n" ;

private static final String fragmentShader = "#ifdef GL_ES\n" +
        "precision mediump float;\n" +
        "#endif\n" +
        "varying vec4 v_color;\n" +
        "varying vec2 v_texCoords;\n" +
        "uniform sampler2D u_texture;\n" +
        "void main()                                  \n" +
        "{                                            \n" +
        "vec4 color = texture2D(u_texture, v_texCoords).rgba; \n" +
        "vec4 newColor; \n" +
        "if (color.r == {{trousersold}} && color.g == {{trousersold}} && color.b == {{trousersold}}){ //trousers \n" +
        "newColor = vec4({{trousers}}, 1.0); \n" +
        "}else if (color.r == {{shirtold}} && color.g == {{shirtold}} && color.b == {{shirtold}}){ \n" +
        "newColor = vec4({{shirt}}, 1.0); \n" +
        "} \n" +
        "else if (color.r == {{skinold}} && color.g == {{skinold}} && color.b == {{skinold}}){ \n" +
        "newColor = vec4({{skin}}, 1.0); \n" +
        "} \n" +
        "else if (color.r == {{hairold}} && color.g == {{hairold}} && color.b == {{hairold}}){ \n" +
        "newColor = vec4({{hair}}, 1.0); \n" +
        "} \n" +
        "else if (color.r == {{shoeold}} && color.g == {{shoeold}} && color.b == {{shoeold}}){ \n" +
        "newColor = vec4({{shoe}}, 1.0); \n" +
        "} \n" +
        "else if (color.r == {{eyeold}} && color.g == {{eyeold}} && color.b == {{eyeold}}){ \n" +
        "newColor = vec4({{eye}}, 1.0); \n" +
        "} \n" +
        "else{ \n" +
        "newColor = color; \n" +
        "} \n" +
        "  gl_FragColor = vec4(newColor);\n" +
        "}";

public static String getFragmentShader(Color hairColor, Color skinColor, Color shirtColor, Color trousersColor,
                                Color eyeColor, Color shoeColor){
    String trousers = trousersColor.r + "," +trousersColor.g + "," + trousersColor.b;
    String hair = hairColor.r + "," + hairColor.g + "," + hairColor.b;
    String skin = skinColor.r + "," + skinColor.g + "," + skinColor.b;
    String shirt = shirtColor.r + "," + shirtColor.g + "," + shirtColor.b;
    String eye = eyeColor.r + "," + eyeColor.g + "," + eyeColor.b;
    String shoe = shoeColor.r + "," + shoeColor.g + "," + shoeColor.b;

    String fragment = fragmentShader.replace("{{hairold}}", ColorHolder.Hair);
    fragment = fragment.replace("{{trousersold}}", ColorHolder.Trousers);
    fragment = fragment.replace("{{skinold}}", ColorHolder.Skin);
    fragment = fragment.replace("{{shirtold}}", ColorHolder.Shirt);
    fragment = fragment.replace("{{eyeold}}", ColorHolder.Eyes);
    fragment = fragment.replace("{{shoeold}}", ColorHolder.Shoes);

    fragment = fragment.replace("{{hair}}", hair);
    fragment = fragment.replace("{{trousers}}", trousers);
    fragment = fragment.replace("{{skin}}", skin);
    fragment = fragment.replace("{{shirt}}", shirt);
    fragment = fragment.replace("{{eye}}", eye);
    fragment = fragment.replace("{{shoe}}", shoe);

    return fragment;
}    

Also my ColorHolder class where I store the color values (grayscale) and possible new Colors:

public class ColorHolder {

public static String Trousers = "64.0/255.0";
public static String Shoes = "45.0/255.0";
public static String Shirt = "113.0/255.0";
public static String Skin = "210.0/255.0";
public static String Eyes = "124.0/255.0";
public static String Hair = "150.0/255.0";
public static List<String> TrouserColors = new ArrayList<String>(Arrays.asList("4649c3ff", "4687c3ff", "6e4208ff", "291004ff"));
public static List<String> ShoesColors = new ArrayList<String>(Arrays.asList("440849ff", "3e1c09ff", "d30000ff", "3e3e3eff"));
public static List<String> ShirtColors = new ArrayList<String>(Arrays.asList("839fdfff", "28caa2ff", "9eca28ff", "7428caff"));
public static List<String> SkinColors = new ArrayList<String>(Arrays.asList("e9c38cff", "dfca83ff", "eec39aff", "3b1d04ff"));
public static List<String> EyesColors = new ArrayList<String>(Arrays.asList("2c86ebff", "2ceba1ff", "611500ff", "248711ff"));
public static List<String> HairColors = new ArrayList<String>(Arrays.asList("e9df3eff", "e9a33eff", "543202ff", "ae4512ff"));

}

And finally, in the class where I instantiate the person, I get a shader like this:

private void setUpShader() {
    String vertexShader = Shader.vertexShader;
    String fragmentShader = Shader.getFragmentShader(
            Color.valueOf(ColorHolder.HairColors.get(MathUtils.random(ColorHolder.HairColors.size() - 1))),
            Color.valueOf(ColorHolder.SkinColors.get(MathUtils.random(ColorHolder.SkinColors.size() - 1))),
            Color.valueOf(ColorHolder.ShirtColors.get(MathUtils.random(ColorHolder.ShirtColors.size() - 1))),
            Color.valueOf(ColorHolder.TrouserColors.get(MathUtils.random(ColorHolder.TrouserColors.size() - 1))),
            Color.valueOf(ColorHolder.EyesColors.get(MathUtils.random(ColorHolder.EyesColors.size() - 1))),
            Color.valueOf(ColorHolder.ShoesColors.get(MathUtils.random(ColorHolder.ShoesColors.size() - 1))));
    this.shader = new ShaderProgram(vertexShader, fragmentShader);
}

EDIT: I've found out, that if I change the texture of the person so that the whole head has a black 1px border, the head won't get drawn in a weird way. The same for the hair, which also gives me a weird look, if the grayscale part is not fully enclosed by black pixels. Can somebody explain this?

Cuddl3s
  • 163
  • 1
  • 12
  • 1
    You probably have some alpha=0 pixels in your source image whose red level matches that of the skin tone. Anyway it would be much simpler to use a color lookup table to do this, and it would perform far better than all those branches in your fragment shader. See here https://stackoverflow.com/q/26132160/506796 – Tenfour04 Jun 04 '17 at 23:53
  • @Tenfour04 Thanks for the link, I'll incorporate that, didn't like the many branches either... Did you see my edit? I guessed something like that too at first, but considering that it works flawlessly if I enclose the head in a 1 px black line, that could not have been the problem. Do you have an idea what might have happened? – Cuddl3s Jun 05 '17 at 09:05
  • Did you save the file using a different image editor? Did you originally use TexturePacker, and then simply edit the output document of TexturePacker manually? Maybe when you resaved after adding the one pixel border, the alpha=0 pixels were also changed to RGB = 0. Some image editors do that, since it helps shrink the file. – Tenfour04 Jun 05 '17 at 14:11
  • 1
    If you eliminate `newColor` and the last branch else statement, and replace all the uses of `newColor` with `v_color.rgb = {{trousersold}}` (for example), then you won't be replacing alpha with 1, so it won't matter anyway. – Tenfour04 Jun 05 '17 at 14:13

0 Answers0