## Packing Normal and Specular into RGB10A2

Trying to improve my g-buffer layout (so I can fit it in the X360’s 10 MiB EDRAM)
Spherical Coordinates with Phi in 8 bits and Normal.Y in 8 bits looks terrible.
So, thanks to a suggestion by doesnotcompute on gamedev forums, I tried my hand at this packing:

```Red = abs(Phi)
Green = abs(N.y)
Blue = Specular I & E combined (5 bits each)
Alpha = sign(Phi) & sign(N.y) combined```

This gives 11 bits for Phi and N.y, which is a huge improvement.
Specular has suffered a bit though …

Here is the HLSL source code:

```// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float PackIEto10bit(in float I, in float E) {
return (floor(I * 31) * 32 + E * 31) / 1023.0;
}//function
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void UnpackIEfrom10bit(in float V, out float I, out float E) {

float t1;
float t2;

t1  = V * 1023;
t2  = floor(t1 / 32.0);
I   = t2 / 31.0;
E   = (t1 - t2 * 32) / 31.0;

}//function
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float4 PackNIEtoRGB10A2(in float3 N, in float I, in float E) {

float4  color;
float   phi;

// Map E
E           = saturate(log10(E) - 1);

phi         = atan2(N.x, N.z); // Note! This gives NaN when x = z = 0

color.r     = abs(phi) / Pi;
color.g     = abs(N.y);
color.b     = PackIEto10bit(I, E);

float a1    = saturate(sign(phi)) * 0.3333;
float a2    = saturate(sign(N.y)) * 0.6666;

color.a     = a1 + a2;

return color;

}//function
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// R = abs(phi)
// G = abs(Y)
// B = IE
// A = sign(phi) + sign(Y)
float3 UnpackNIEfromRGB10A2(in float4 color, out float I, out float E) {

UnpackIEfrom10bit(color.b, I, E);

// Map E
E = pow(10, E + 1);

float signPhi   = sign(frac(color.a * 1.9) - 0.5);
float signY     = sign(color.a - 0.5);
float phi       = color.r * Pi * signPhi;
float Y         = color.g * signY;

float2 N;
sincos(phi, N.x, N.y);

const float threshold = 1021.0 / 1023.0;

if (color.g > threshold) N = 0.0; // bodge up for singularity

float L = sqrt(1.0 - Y * Y);

return float3(N * L, Y).xzy;

}//function
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
```

### Ideas for improvements

Because there is only a limited range of view space normals with negative Z that are visible … I can increase normal precision by mapping phi to the visible range.

Also some gamma encoding on the specular intensity would also improve things.

(edit) Mapping the range of Phi by 1.0 / 0.64 before packing gives an extra 56% precision. Using a 45 degree FOV with 1.777 aspect means only normals facing away by up to 36.3 degrees are visible. As I am using LH coordinates that means I also need to flip Y, atan2(x, -y).

(edit) Simple gamma encoding for Specular I. For packing: I = sqrt(I), for unpacking I *= I;