Shader Fun: Trading Accuracy for Variety

A little while ago, I was doing a bit of r&d for a potential alternative to one of our shader solutions. This particular approach never made it past gestation, though, since it would have required a fair amount of texture reauthoring and rejiggering to be viable. Long story short, I was looking at ways to maximise available variation in a single 8-bit channel. Bang for buck, if you will.

Now, I’d recalled Bronwen Grimes’ presentation on shader techniques in Left 4 Dead 2, and in particular that they had split their zombie color variation masks by value range. In a nutshell, they would split the masks in half (page 26), and then remapped those via a split gradient map, which ultimately gave them four value ranges to work with (page 38). I didn’t really see the split gradient map working for my purposes, and I also felt there should be a cleaner (and more versatile) method of splitting the value range.

After a bit of experimentation, and cracking my skull over a bit of math, I ended up with the following, surprisingly straightforward, function:

int bands = 4; //this is the number of value bands to split the range

float range = 1.0 / bands; //range of each band

float ReRamp(float val, int band)

//val is the pixel's luminance, band is the... band [index]


float minRange = range * band;

float maxRange = range * (band + 1);

float c = min(saturate(val - minRange) / (maxRange - minRange), 1);

c = c >= 1?0:c;

return c;


This effectively allows you to split your value range into as many discrete bands as you please (however keeping in mind the need for a buffer between ranges to account for compression; page 39).

The returned value has been reramped from its original value range (eg. 0.25, for 4 bands) to the full 0-1 range, allowing you to do whatever you please with it, be that as a color mask, uv distortion, etc.

You don’t even need to split a texture up to benefit from this approach, either. Assuming you have, say, 4 different UV warping algorithms modulated by this channel, you could have 4 different textures (for 4 different assets), just being sure to keep each one in the required value range.

Now there are obviously drawbacks to this approach. As mentioned in the presentation, the various masked areas can’t overlap. Additionally, you’re sacrificing value range for file size (texture/channel count). The more bands you want, the coarser the value ramp.

1 comment on this post.
  1. Brandon Newton:

    I’m using a similar masking technique on my angel model. (if I can ever finish the damn retexture) Though rather than color variations I’m using a single channel with 4 value ranges for material type definitions. One for flesh, one for lycra, one for metals and one for leather. It works great since they’re all hard edged transitions.