39
\$\begingroup\$

I'm working on a Megaman-like game where I need to change the color of certain pixels at runtime. For reference: in Megaman when you change your selected weapon then main character's palette changes to reflect the selected weapon. Not all of the sprite's colors change, only certain ones do.

This kind of effect was common and quite easy to do on the NES since the programmer had access to the palette and the logical mapping between pixels and palette indices. On modern hardware, though, this is a bit more challenging because the concept of palettes is not the same.

All of my textures are 32-bit and do not use palettes.

There are two ways I know of to achieve the effect I want, but I'm curious if there are better ways to achieve this effect easily. The two options I know of are:

  1. Use a shader and write some GLSL to perform the "palette swapping" behavior.
  2. If shaders are not available (say, because the graphics card doesn't support them) then it is possible to clone the "original" textures and generate different versions with the color changes pre-applied.

Ideally I would like to use a shader since it seems straightforward and requires little additional work opposed to the duplicated-texture method. I worry that duplicating textures just to change a color in them is wasting VRAM -- should I not worry about that?

Edit: I ended up using the accepted answer's technique and here is my shader for reference.

uniform sampler2D texture;
uniform sampler2D colorTable;
uniform float paletteIndex;

void main()
{
        vec2 pos = gl_TexCoord[0].xy;
        vec4 color = texture2D(texture, pos);
        vec2 index = vec2(color.r + paletteIndex, 0);
        vec4 indexedColor = texture2D(colorTable, index);
        gl_FragColor = indexedColor;      
}

Both textures are 32-bit, one texture is used as lookup table containing several palettes which are all the same size (in my case 6 colors). I use the source pixel's red channel as an index to the color table. This worked like a charm for achieving Megaman-like palette swapping!

\$\endgroup\$

6 Answers 6

43
\$\begingroup\$

I wouldn't worry about wasting VRAM for a few character textures.

To me using your option 2. (with different textures or different UV offsets if that fits) is the way to go: more flexible, data-driven, less impact on the code, less bugs, less worries.


This put aside, if you start to accumulate tons of characters with tons of sprite animations in memory, maybe you could start using what's recommended by OpenGL, do-it-yourself palettes:

Paletted textures

Support for the EXT_paletted_texture extension has been dropped by the major GL vendors. If you really need paletted textures on new hardware, you may use shaders to achieve that effect.

Shader example:

//Fragment shader
#version 110
uniform sampler2D ColorTable;     //256 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;

void main()
{
  //What color do we want to index?
  vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
  //Do a dependency texture read
  vec4 texel = texture2D(ColorTable, myindex.xy);
  gl_FragColor = texel;   //Output the color
}

This is simply sampling in ColorTable (a palette in RGBA8), using MyIndexTexture (an 8 bits square texture in indexed colors). Just reproduces the way retro-style palettes work.


The above-quoted example uses two sampler2D, where it could actually use one sampler1D + one sampler2D. I suspect this is for compatibility reasons (no one-dimensional textures in OpenGL ES)... But nevermind, for desktop OpenGL this can be simplified to:

uniform sampler1D Palette;             // A palette of 256 colors
uniform sampler2D IndexedColorTexture; // A texture using indexed color
varying vec2 TexCoord0;                // UVs

void main()
{
    // Pick up a color index
    vec4 index = texture2D(IndexedColorTexture, TexCoord0);
    // Retrieve the actual color from the palette
    vec4 texel = texture1D(Palette, index.x);
    gl_FragColor = texel;   //Output the color
}

Palette is a one-dimensional texture of "real" colors (e.g. GL_RGBA8), and IndexedColorTexture is a two-dimensional textures of indexed colors (typically GL_R8, which gives you 256 indices). To create those, there are several authoring tools and image file formats out there that support paletted images, it should be doable to find the one that fits your needs.

\$\endgroup\$
10
  • 2
    \$\begingroup\$ Exactly the answer I would've given, only more detailed. Would +2 if I could. \$\endgroup\$ Nov 7, 2012 at 21:25
  • \$\begingroup\$ I'm having some problems getting this to work for me, mostly because I don't understand how the index in the color is determined -- could you possibly expand on that a bit more? \$\endgroup\$ Nov 8, 2012 at 18:00
  • \$\begingroup\$ You should probably read up on indexed colors. Your texture becomes a 2D array of indices, thoses indices corresponding to colors in a palette (typically a one-dimensional texture). I'll edit my answer to simplify the example given by the OpenGL website. \$\endgroup\$ Nov 8, 2012 at 22:01
  • \$\begingroup\$ I'm sorry, I wasn't clear in my original comment. I'm familiar with indexed colors and how they work, but I'm not clear on how they work within the context of a shader or a 32-bit texture (since those aren't indexed -- right?). \$\endgroup\$ Nov 8, 2012 at 22:31
  • \$\begingroup\$ Well, the thing is that here you have one 32-bit palette containing 256 colors, and one texture of 8-bit indices (going from 0 to 255). See my edit ;) \$\endgroup\$ Nov 8, 2012 at 22:36
10
\$\begingroup\$

There are 2 ways I can think to do this.

Way #1: GL_MODULATE

You can make the sprite in shades of gray, and GL_MODULATE the texture when it is drawn with a single solid color.

For example,

enter image description here

enter image description here

This doesn't allow you much flexibility however, basically you can only go lighter and darker of the same shade.

Way #2: if statementing (bad for performance)

Another thing you could do is color your textures with some key values involving R, G and B. SO for example, the first color is (1,0,0), the 2nd color is (0,1,0), the 3rd color is (0,0,1).

You declare a couple of uniforms like

uniform float4 color1 ;
uniform float4 color2 ;
uniform float4 color3 ;

Then in the shader, the final pixel color is something like

float4 pixelColor = texel.r*color1 + texel.g*color2 + texel.b*color3 ;

color1, color2, color3, are uniforms so they can be set before the shader runs (depending on what "suit" megaman is in), then the shader simply does the rest.

You could also use if statements if you needed more colors, but you would have to be the equality checks work correctly (textures are usually R8G8B8 between 0 and 255 each in the texture file, but in the shader, you have rgba floats)

Way #3: Just redundantly store the different colored images in the sprite map

enter image description here

This may be the easiest way, if you're not insanely trying to conserve memory

\$\endgroup\$
1
  • 2
    \$\begingroup\$ #1 can be neat, but #2 and #3 are horrifyingly bad pieces of advice. The GPU is capable of indexing from maps (of which a palette basically is) and does so better than both of those suggestions. \$\endgroup\$
    – Skrylar
    May 15, 2015 at 7:02
6
\$\begingroup\$

I'd use shaders. If you don't want to use them (or can't) I'd go with one texture (or part of a texture) per color and use clean white only. This will allow you to tint the texture (e.g. through glColor()) without you having to worry about creating additional textures for colors. You can even swap colors on the run without additional texture work.

\$\endgroup\$
2
  • \$\begingroup\$ This is a pretty good idea. I actually had thought of this a while back but it didn't come to mind when I posted this question. There is some extra complexity with this since any sprite that uses this kind of composite-texture will need a more complex drawing logic. Thanks for this awesome suggestion! \$\endgroup\$ Nov 7, 2012 at 0:57
  • \$\begingroup\$ Yes, you'll also need x passes per sprite, which can add quite some complexity. Considering today's hardware usually supporting at least basic pixel shaders (even netbook GPUs), I'd go with these instead. It can be neat if you just want to tint specific things though, e.g. health bars or icons. \$\endgroup\$
    – Mario
    Nov 7, 2012 at 13:04
3
\$\begingroup\$

"Not all of the sprite's colors change, only certain ones do."

Old games did palette tricks because that was all they had. These days, you can use multiple textures and combine them in various ways.

You can create a separate grayscale mask texture which you use to decide which pixels will change color. White areas in the texture receieve the full color modification, and black areas remain unchanged.

In shader languague:

vec3 baseColor = texture(BaseSampler, att_TexCoord).rgb;
float amount = texture(MaskSampler, att_TexCoord).r;
vec3 color = mix(baseColor, baseColor * ModulationColor, amount);

Or you can use fixed-function multi-texturing with various texture combiner modes.

There are obviously many different ways you can go about this; you can put masks for different colors in each of the RGB channels, or modulate the colors by tint shifting in an HSV colorspace.

Another, perhaps simpler, option is to simply draw the sprite using a separate texture for each component. One texture for the hair, one for the armor, one for the boots, etc. Each one color tintable separately.

\$\endgroup\$
2
\$\begingroup\$

Firstly it's generally referred to as cycling (pallet cycling), so that might help your Google-fu.

This will depend on exactly what effects you want to produce and if you are dealing with static 2D content or dynamic 3D stuff (ie do you just want to deal with spites/textures, or render a whole 3D scene then pallet swap that). Also if you are limiting yourself to a number of colours or using full colour.

A more complete approach, using shaders, would be to actually produce colour indexed images with a pallet texture. Make a texture but instead of having colours, have a colour the represents an index to lookup then have another texture of colours that is the plate. For example red could be the textures t coordinate, and green could be the s coordinate. Use a shader to do the lookup. And Animate/Modify the colours of that pallet texture. This would be quite close to the retro effect but would only work for static 2D content such as sprites or textures. If you doing genuine retro graphics then you might be able to produce actual indexed colour sprites and write something to import them and the pallets.

You will also want to work out if you are going to use a 'global' pallet. Like how old hardware would work, less memory for pallets as you only need one but harder to produce your artwork for as you have to synchronize the pallet across all the images) or give each image it's own pallet. That would give you more flexibility but use more memory. Of course you could do both, also you could only use pallets for the things you are colour cycling. Also work out to what extent you wan't to limit your colors to, old games would use 256 colors for example. If you are using a texture then you will be getting the number of pixels so a 256*256 will give you

If you just want a cheap fake effect, such as flowing water, you could create a 2nd texture that contain a greyscale mask the area you want to be modified, then use the masted textures modify the hue/saturation/brightness by the amount in the greyscale mask texture. You could be even lazier and just change the hue/brightness/saturation of anything in a colour range.

If you did need it in full 3D you could try generating the indexed image on the fly but it would be slow as you would have to look up the pallet, you would also likely be limited in the range of colours.

Otherwise you could just fake it in the shader, if color == swapping color, swap it. That would be slow and would only work with a limited number of swapping colors but shouldn't stress any of today hardware, particularly if you just dealing with 2D graphics and you can always mark which sprites have colour swapping and use a different shader.

Otherwise really fake them, make a bunch of animated textures for each state.

Here is a great demo of pallet cycling done in HTML5.

\$\endgroup\$
0
\$\begingroup\$

I believe ifs are fast enough for the color space [0...1] to be divided into two:

if (color.r > 0.5) {
   color.r = (color.r-0.5) * uniform_r + uniform_base_r;
} else {
  color.r *=2.0;
} // this could be vectorised for all colors

In this way color values between 0 and 0.5 are reserved for normal color values; and 0.5 - 1.0 are reserved for "modulated" values, where 0.5..1.0 maps to any range selected by the user.

Only the color resolution is truncated from 256 values per pixel to 128 values.

Probably one can use builtin functions like max(color-0.5f,0.0f) to remove the ifs completely (trading them to multiplications by zero or one...).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .