I coded this in C++ but it's easy portable to GLSL or HLSL.
In this example I store only 2 normal components: X and Y in range 0 to 255. Remember that you must extract third component as follows: sqrt( 1.0 - nx*nx + ny*ny );
Heightmap has custom precision values, due big imprecision of floating point conversion. With 11 bits of precision we can store heights from 0 to 2043. This happened because we loose 4 numbers with this precision. The formula is:
unsigned char hPrecisionBits = 11; // (1<<11) = 2048
unsigned char expectedError = 1 << (hPrecisionBits - 9); // 4
unsigned short maxHeight = (1 << hPrecisionBits) - expectedError; // 2044
//! Gives fractional number
inline float frac( float _value ) {
return _value - floorf( _value );
}
// NX | NY | HEIGHT -> float32
// 8 | 8 | 16
inline float packNormalAndHeight( unsigned char _nx, unsigned char _ny, unsigned short _height, char _hPrecisionBits )
{
return
(_nx / 256.0) +
((_ny / 256.0) / 256.0) +
(_height / ((float)(1<<_hPrecisionBits)) / 65536.0);
}
// float32 -> NX | NY | HEIGHT
// 8 | 8 | 16
inline void unpackNormalAndHeight( float _value, char _hPrecisionBits, float* nx_, float* ny_, float* height_ )
{
*nx_ = floorf( frac( _value ) * 256.0 );
*ny_ = floorf( frac( _value * 256.0 ) * 256.0 );
*height_ = frac( _value * 65536.0 ) * ((float)(1<<_hPrecisionBits));
}
Usage example:
unsigned char hPrecisionBits = 11; // (1<<11) = 2048
unsigned char expectedError = 1 << (hPrecisionBits - 9); // 4
unsigned short maxHeight = (1 << hPrecisionBits) - expectedError; // 2044
// Pack as 8 | 8 | 16
float packedValue = packNormalAndHeight( 231, 137, 2043, hPrecisionBits );
// Unpack from single float
float x,y,h;
unpackNormalAndHeight( packedValue, hPrecisionBits, &x, &y, &h );
Notice that 'expectedError' brings us the amount of error values returned in height function. For example, if our precision bits are 9, expected error will be 1 (135 when we store 136 number). For 10, error will be 2, for 11, will be 4, for 12 will be 8, etc...
I hope it will be useful.