If you use a point sampler for your mask texture, then you can store as many IDs as bits in your texture: for a 32-bit RGBA texture you'd be able to store 128 different IDs. In such a case though, as you have a single bit per ID, there's virtually no blending as you have only 0 or 1.
The more bits you devote per ID, the greater the blending granularity, but of course that reduces the number of total IDs. If you go with RGBA32 textures, you have 128 bits at your disposal. You can store 128 IDs with blending value "full" or "none", or 64 IDs with "full", "half", "none" and so on - it's up to you what you write to those 128 bits and how you interpret them.
I can't point you to a whitepaper/link, as this is something that I'd do (and might have done in the past). I will also not provide any detailed code, as it's just bit fiddling; nothing else. The function that you'd need in your shader would be like:
/* Extract the bits that refer to a particular texture id and return the blend factor.
E.g. if you have 4 bits per id and the bits in the texture are 1001,
then the blend value would be 9/15 */
float unpack_id_blend_factor_from_texture_value( uint4 lookup_value, int id, int bits_per_id)
{
const int total_ids = 128/bits_per_id;
const int total_ids_per_component = total_ids / 4;
// get the component that contains the id, and modify
int value = 0;
if( id < total_ids_per_component )
{
value = lookup_value.x;
}
else if( id < (2*total_ids_per_component) )
{
value = lookup_value.y;
id -= total_ids_per_component;
}
else if( id < (3*total_ids_per_component) )
{
value = lookup_value.z;
id -= 2*total_ids_per_component;
}
else
{
value = lookup_value.w;
id -= 3*total_ids_per_component;
}
// shift the value data so that the bits used by the id are in the front
uint used_bits = value >> (bits_per_id*id);
// make the rest of the bits -- after the used ones -- zero
used_bits &= (1 << bits_per_id) -1;
// get and return the blend value
return used_bits / float((1 << bits_per_id) -1);
}
Now the above should give you an idea how you extract the bits. You use the inverse process to fill the lookup texture.