It is common in game developpement to store alpha masks for sprites, particles or billboards.
This mask is usually stored in an alpha channel. This alpha channel is usually anti-aliased, however more often than not, this anti-aliasing is sub-optimal because for performance reasons the final mask is a binary value.
This mask is usually stored in an alpha channel. This alpha channel is usually anti-aliased, however more often than not, this anti-aliasing is sub-optimal because for performance reasons the final mask is a binary value.
A common method to improve results is to use SDFs, first introduced in game developpement by Valve.
However, SDFs are more suited for curved, regular shapes such as fonts, and not more complex shapes like vegetation billboards.
The alpha channel stores one binary value, but contains 8 bits: 8 binary values are available to us. Can we make good use of these bits to store more information about the alpha mask?
Spoiler alert:
Altough the final result proves this works on paper, our method is unusable, because it breaks down when using linear filtering on the texture, which is an unacceptable trade-off.
My approach is to subdivide each pixel in 7 parts, what we will call our kernel. Below is an example for an 8x8 pixel grid:
For each kernel data point, we can store the information as a single bit. This means each pixel contains 7 bits of information, one for each kernel part.
The center of the kernel is bit 0, then bits 1 to 6 clockwise, starting from the top left.
Once encoded, our image looks like this. Although it seems like a regular image, there is actually 7 data points encoded per pixel:
Then in the shader, when sampling a pixel, we compute in which part of the kernel the sampling occurs, and we sample the corresponding bit.
This snippet returns a the kernel's bit values:
We then use the bit value to sample the correct bit.
The BitMask custom node simply executes:
return ((1 << ((int)Bit - 1)) & (int)Mask) != 0;
return ((1 << ((int)Bit - 1)) & (int)Mask) != 0;
credit: Morva Kristóf
This may look very lossy compared to our original texture, but keep in mind our original had 1M+ pixels and this image has 64 pixels.
Here is a comparaison with a regular downsample of our original texture:
NOTE: Although this result looks promising, keep in mind that this is only valid with nearest-neighboor sampling which is a huge tradeoff.
NOTE: This requires non-compressed data as well, but this is less of an issue since the alpha channel usually goes uncompressed.
Potential use for the 8th bit:
The eight bit can store a switch value, for example switching between two different kernels.