- Analyse the image and convert all colours to hue/saturation/value (HSV)
- Consolidate a list of all unique hue/saturation/value combinations
- Group HSV values with same H and similar SV; S & V ranges configurable
- Create a list of unique grouped reference colours, write this to a 1D texture and/or a list of shader constants
- 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.
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.
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.
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.