I noticed a few issues.
1) In your fragment shader, shouldn't vec2 index = vec2(color.r + paletteIndex, 0);
be vec2 index = vec2(color.r, paletteIndex);
, so the sprite texture tells you which row and the paletteIndex tells you which column of the color table to look at?
2) The palette index is being used as a texture coordinate, so it needs to be a number between 0 and 1. Set it like this:
int currentPalette = 2; //A number from 0 to (colorTable.getHeight() - 1)
float paletteIndex = (currentPalette + 0.5f) / colorTable.getHeight();
//The +0.5 is so you are sampling from the center of each texel in the texture
3) A call to shader.end
happens inside batch.end
so you need to set your uniforms on every frame, not in create
. Probably a good idea to also bind your secondary texture each frame as well, in case you do any multitexturing later.
@Override
public void render () {
Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
colorTable.bind(1);
//Must return active texture unit to default of 0 before batch.end
//because SpriteBatch does not automatically do this. It will bind the
//sprite's texture to whatever the current active unit is, and assumes
//the active unit is 0
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0);
batch.begin(); //shader.begin is called internally by this line
shader.setUniformi("colorTable", 1);
shader.setUniformf("paletteIndex", paletteIndex);
batch.draw(imgPixelGuy, 0, 0);
batch.end(); //shader.end is called internally by this line
}
4) The GPU might do this for you anyway, but since you aren't using vertex colors, you can remove the two lines involving v_color
from your vertex shader.
5) You probably also want transparent pixels to stay transparent. So I would change the last line of your fragment shader to preserve alpha:
gl_FragColor = vec4(indexedColor.rgb, color.a);
6) This one is probably the reason you were getting all white. In your fragment shader, you should be using v_texCoords
instead of gl_TexCoord[0].xy
, which is something from desktop OpenGL and not used in OpenGL ES. So your fragment shader should be:
uniform sampler2D texture; // Texture to which we'll apply our shader? (should its name be u_texture?)
uniform sampler2D colorTable; // Color table with 6x6 pixels (6 palettes of 6 colors each)
uniform float paletteIndex; // Index that tells the shader which palette to use (passed here from Java)
varying vec2 v_texCoords;
void main()
{
vec4 color = texture2D(texture, v_texCoords);
vec2 index = vec2(color.r, paletteIndex);
vec4 indexedColor = texture2D(colorTable, index);
gl_FragColor = vec4(indexedColor.rgb, color.a); // This way we'll preserve alpha
}
7) Your source sprite needs to be pre-processed to map to the columns of your palette lookup table. Your shader is pulling a coordinate from the red channel of the texture. Therefore you need to do a color replacement of each pixel color in your source image. You can do this by hand ahead of time. Here's an example:
Skin tone is index 2 out of the six columns (0-5). So just like we calculated the paletteIndex, we normalize it to the center of pixel: skinToneValue = (2+0.5) / 6 = 0.417
. The 6 is for the six columns. Then in your drawing program, you need the appropriate value of red.
0.417 * 255 = 106
, which is 6A in hex, so you want color #6A0000. Replace all the skin pixels with this color. And so on for the other hues.
Edit:
One more optimization is that you probably want to be able to batch all your sprites together. The way it is now, you will have to group all your sprites for each palette separately and call batch.end
for each of them. You can avoid this by putting paletteIndex
into the vertex color of each sprite, since we weren't using vertex color anyway.
So you could set it to any of the four color channels of the sprite, let's say the R channel. If using the Sprite class, you can call sprite.setColor(paletteIndex, 0,0,0);
Otherwise call batch.setColor(paletteIndex,0,0,0);
before calling batch.draw
for each of the sprites.
The vertex shader will need to declare varying float paletteIndex;
and set it like this:
paletteIndex = a_color.r;
The fragment shader will need to declare varying float paletteIndex;
instead of uniform float paletteIndex;
.
And of course you no longer should call shader.setUniformf("paletteIndex", paletteIndex);
.