Shaders Color-remapping Shaders w/ CLUT surfaces

So, in my game project, I made a shader setup that recolors characters according to a local surface that acts as a color lookup table. When drawn, the shader is called, a stage parameter is set as the texture of the CLUT surface and basically remaps the colors from the top row to the colors in the bottom row.

While it does work like it's supposed to, what worries me is that each CLUT is stored as its own texture page. Because of this, regardless of my texture group setup, each character drawn requires at least two textures swaps.

So according to the debug overlay, with say, 50 characters on screen, I'm clocking about 100 texture swaps with 100 vertex batches. If I don't pass any of the CLUTs, it goes back down to 10 swaps (still 100 batches).

I guess my question is, should I be looking for a more efficient way of implementing color remapping? I'm not 100% familiar with the impact of texture swaps. All I really understand is apparently any more than 20 swaps is bad, but I'm not really noticing any major difference in performance. Then again this is mid-performance machine (GeForce GT750M, 8GB RAM).
 
Last edited:
Why are there two rows? Can't you use the original color of the image as the key to look up in the color table?

Texture swaps should only be happening when you need to change the look up table. As long as you remain using the same one, there should be no reason to cause additional texture swaps.
 
Why are there two rows? Can't you use the original color of the image as the key to look up in the color table?

Texture swaps should only be happening when you need to change the look up table. As long as you remain using the same one, there should be no reason to cause additional texture swaps.
Hm. I'm not sure how I would go about referencing the original color in the shader. And yeah, the lookup table remains the same and ONLY updates if the surface somehow gets lost. But texture swaps ARE happening when I pass the texture into the shader. To test this, I removed the texture_set_stage() function from the draw event and end up with no extra swaps. (For 50 instances, only 10 swaps total vs. >100 with that one line.)

EDIT: Unless you're talking in the case of only one single CLUT for all characters. Each character has its own CLUT surface which it must pass to the shader when drawn. I probably should've mentioned that each character has their own random color map.
 
Last edited:
okay, question: are the character's base colors random? Or are the transformations they undergo through the look up table random?

If you MUST switch out the palette for every single instance, I'm not sure if there is a way around the texture swaps. What you might be able to do instead is to combine all the palettes into a single texture, and use some basis for selecting which row to reference for each instance. For example, if you aren't using the imabe_blend property, you could override it to carry which row to use.

By the way, I've done some color look up tables before, and you can absolutely use the original colors as the basis for looking up the new color. In fact I might as well copy paste my shader code here, since it's not very complicated, and it might be of some use to you possibly.
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec2 height; // (15/height_of_palletes_image, 0.5/height_of_palletes_image)
uniform float row;  ///palette_row/height_of_palletes_image
uniform sampler2D palettes;  //look up table (must be x=256, y=power of 2 in size)

const float A = 15.0 / 256.0; //15 divided by width of palletes image
const float B = 0.5 / 256.0; //0.5 divided by width of pallets image
const float C = 1.0 / 16.0;

void main(){
    //read base color
    vec4 base_color = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
  
    //compute palette texture coordinates
    float blue = base_color.b * 15.0;
    vec2 blues = vec2( floor( blue ), ceil( blue ) );
    vec2 uv = vec2( base_color.r * A + B, base_color.g * height.x + height.y + row );
  
    //unfortunately, must make 2 texture lookups in order to have smooth gradients in the blue channel.
    vec3 color1 = texture2D( palettes, vec2( uv.x + blues.x * C, uv.y ) ).rgb;
    vec3 color2 = texture2D( palettes, vec2( uv.x + blues.y * C, uv.y ) ).rgb;
  
    //output original color using whatever color palette transformations were used to make this palette
    gl_FragColor = vec4( mix( color1, color2, blue - blues.x ), base_color.a );
}
Here's an example of what a color pallete looks like... This has 16 different rows, each row is a different look-up table. The top row has no transformation, meaning if you use that as the look-up table, colors will appear unchanged. The other rows were generated by just taking the top row look-up table, and transforming it in some way (contrast, hue shift, etc...)
https://imgur.com/0dZxcE3
 
Last edited:
okay, question: are the character's base colors random? Or are the transformations they undergo through the look up table random?

If you MUST switch out the palette for every single instance, I'm not sure if there is a way around the texture swaps. What you might be able to do instead is to combine all the palettes into a single texture, and use some basis for selecting which row to reference for each instance. For example, if you aren't using the imabe_blend property, you could override it to carry which row to use.
Their base colors stay the same. The shader just uses that to determine what color to change to.

Basically the shader goes through the top row of the table one column at a time, and if that color matches the pixel being drawn, then it simply draws the color in that bottom position. (Ex. If the base pixel matches the color at position (10,0), then it draws the color at (10,1) instead.). This is just how I learned to do it from a tutorial, but if there's a better way to pass that base color to the shader, I'll try it.

I had considered the idea of a single master lookup table that would hold every single characters unique palette. The main challenge would be organizing it so the right palette is referenced for the right character at the right time, and without just ending up with even more overhead.

If I need to, I'll simply look for another way of implementing color remapping without the use of lookups, although the effect I'm achieving as is looks pretty nice IMHO.
 
Your method is going to be pretty slow because it involves a loop. Well, I suppose it depends on the length of the loop.

Anyway, it seems your main problem is just how to combine all the look up tables into one.

If each palette was on a different row, all you'd need to do is pass in which row to use, which can be done any number of ways. But the best way to do it would be without breaking the vertex batch, meaning you wouldn't want to use a shader uniform. Instead you can piggy back one of the vertex attributes. For example, like I mentioned before, if you are not using the blending color (image_blend) for anything else, you can use that to carry the information that lets the shader know which row to use. Within the shader, image_blend will be accessable through the in_Colour vertex attribute. Each channel can carry 256 colors, but it will be translated into the range 0.0 to 1.0 when inside of the shader, where 0.0=0, 1.0=255. Except possibly the alpha channel, not sure exactly whether that will be broken into a byte at some point or whether it will just remain a float.

By the way, I updated my previous post to include information about an alternate LUT method, that does not involve using loops, nor indexed colors.
 
Hm. Definitely didn't know about image_blend. I see where it comes in, and doesn't look like it's being used for anything else in the shader (somewhat new to shaders and this was built off the default shader so there's probably a lot of unnecessary parameters).

I guess the challenge there is simply the math in calculating how to convert the in_Colour attribute into a coordinate in the lookup. Making the lookup a flat 256 in height would probably solve it. Seriously doubt I or anyone will ever end up with that many characters in the world at one time.

If I go with this method though, with a master lookup table, I guess the next challenge is correctly updating the lookup table as characters are added and removed from the game (or if/when the lookup surface somehow gets purged from VRAM), without causing any stutter when there's potentially hundreds of characters to update. I guess the trick is simply assigning the right value to the image_blend at the right time.
 
P

pieterator

Guest
I've had to figure this out in the past as well, so I'll provide what I've learned. It may not be the best solution, but it has worked effectively for me ;)

First thing is moving you palette away from its own texture page, and put it on the same texture page as all the other sprites (so untick the "separate texture page" option in the sprite editor). However, now your palette texture will no longer run from 0 to 1, it will be a value somewhere on the texture page (something like say 0.21 to 0.27). This is additional information you have to pass into your shader to tell it where the palette is, and can be obtained by using the sprite_get_uvs(), to get the top left corner, and the width and height of your palette in texture space. Once you have this information, the lookup code should be very similar to what you had originally.

Also, I've had more success with square palette sprites that are power of 2. So 8x8, 16x16, 32x32 etc. They just seem to give more consistent results.

So now if your palette, and the sprites you wish to colour shift with it, are on the same texture page, there will not be any texture swaps.
Hope some of this made sense :D
 
about the image_blend idea. First of all, this will only work if you aren't already using image_blend for something else. Because you are using a palette swap, I imagine you aren't using it for anything at the mement. The first thing you will want to do is make sure you aren't already using the varying variable v_vColour, (part of the defualt shader), for anything, because we are going to override its function.

Now, I'm assuming you are going to keep using a modified version of your original method. First of all, the whole look-up-table map image needs to be on its own texture page for this to work. Both dimensions need to have a size of power of 2. It does not however need to be square.

Let's assume your look-up-table image is 512 pixels tall, and each pair of rows is a single look-up-table, the first row being the color to replace, and the second row being the color to replace it with. That means 256 look-up-tables can fit in the whole thing. This will be convenient because a single color channel has 8bit precision. The first look-up-table has a texture y coordinate of 0. The second has a texture y coordinate of 1/256, the last one has a texture y coordinate of 255/256.

So now an example, say you want an instance to use the third look-up-table from the top. Let's say we use the red channel of image_blend to carry this info. Set the red component of image_blend to (3-1) = 2 (subtracting 1 because first row has y coordinate zero). In the shader, you access this info using the in_Colour.r vertex attribute component, pass it into the fragment shader as a varying, you only need to pass a float (not a vec4) because we are only using the red component.

Now, the range of this float will be 0 to 1, but our look-up-table map does not have the same range, it has a range of 0 to 255/256. So we need to convert our float into this range by multiplying it by 255.0 / 256.0. Using the example above, where the red channel has a value of 2/255, in the shader this becomes a float with a value of 0.00784. Multiply that by 255.0/256.0 results in 0.00781. That final number is the y coordinate for the top of the first row for the third look-up-table.

The second row is offset by y 1/512.

If you want to sample the center of a pixel (instead of the edge), then you would want to add 0.5/512 (on the y axis). To elaborate on that, if you are using texture interpolation, you don't want to sample the edge of a pixel because then you will get a value interpolated between the pixels adjacent to that point, what you would want instead is to sample the exact center of a pixel, or else turn off texture interpolation.

P.S., the other method i wrote about earlier probably wont be ideal for you if you are doing a straight palette swap, it would be better for color alterations happening on a scale global to the whole color space. However, I would suggest you think about converting your images to indexed colors, because in case you have a lot of colors in each palette, your loops are going to cause an unecessary performance hit.
 
Top