Skip to content

Instantly share code, notes, and snippets.

@sinbad
Last active November 9, 2016 13:58
Show Gist options
  • Save sinbad/2458a95b5c7c524a4427bb478714a2ba to your computer and use it in GitHub Desktop.
Save sinbad/2458a95b5c7c524a4427bb478714a2ba to your computer and use it in GitHub Desktop.
Sprite recolouring approach

Approach

  1. Analyse the image and convert all colours to hue/saturation/value (HSV)
  2. Consolidate a list of all unique hue/saturation/value combinations
  3. Group HSV values with same H and similar SV; S & V ranges configurable
  4. Create a list of unique grouped reference colours, write this to a 1D texture and/or a list of shader constants
  5. Write another sprite texture the same size as the original, which we call the reference sprite. Set the colour components as follows:
    • R = index of reference colour
    • G = saturation offset (rescaled to 0..255, g=((soff/srange)*0.5+1.0) * 255)
    • B = brightness offset (rescaled to 0..255, b=((voff/vrange)*0.5+1.0) * 255)
    • A = original alpha

When rendering the sprite, we simply combine the reference sprite with either a modified texture or modified shader arguments to recolour it.

The recombination algorithm is:

float3 referenceRGB = GetReference(in.r);
float3 referenceHSV = RGBtoHSV(referenceRGB);
float3 outHSV = referenceHSV + float3(0, (in.g - 0.5) * 2.0 * srange, (in.b - 0.5) * 2.0 * vrange);
out.rgb = HSVtoRGB(outHSV)
out.a = in.a;

Where GetReference either samples a 1D texture (no filtering / mipmapping) or indexes an array of shader constants containing the replacement colour.

Advantages of this approach

The main advantage is that it can be run on any sprite; artists can author sprites with no constraints.

However with a bit of knowledge of the approach artists can create very efficient sprites. Varying saturation and brightness (but not hue) doesn't generate a new palette entry, which means you can use a lot more colours for free.

Let's say you wanted to keep the number of palette colours down to 32, both to keep it within a size that made sense for shader arguments, and also simply to have fewer colours to mess about with in each instance. In other systems you'd be limited to exactly that many colours. However with SpriteRecolour, a palette of 32 only means 32 hues - you can use any number of saturation / brightness variants within that. So you can have much more detailed sprites with only a small number of parameters required to change their look.

Limitations

No compression

Because the output reference sprite relies very heavily on correct indexing using the Red channel, lossy compression cannot be supported. The reference sprite is always output in PNG format right now, and you should not convert it to a lossy compressed format such as JPG or (sadly) ETC/DXT/S3TC.

Higher saturation/brightness ranges

By using higher ranges for saturation / brightness you can cut down the number of palette entries in a more shaded sprite. However the wider the range the higher the offsets for each pixel can be, meaning the harder it is to achieve the exact look you're trying to get in the recolouring, particularly at edge cases. It may for example be impossible to replace a reference colour with pure white or black without reducing the ranges, otherwise the graduated shading would leak in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment