Compressed Normals into Vertex Shader

Started by
7 comments, last by Namethatnobodyelsetook 15 years, 6 months ago
Hi all In order to save memory, I am compressing my terrain normals into two seperate bytes. I lose a bit of accuracy doing it this way, but it should be okay. My question, is how do I get those two bytes into my vertex shader? I can't see any way of importing BYTEs into the vertex shader, is the lowest common denominator a FLOAT type? If so, that surely means that the lowest type you can have in a vertex buffer is a FLOAT which kind of defeats the object of my compression slightly. Is there a way to import BYTEs into a vertex shader, or if not, is there a way to import a 4 byte float and then carry out bitwise ops on it? Or, can anyone think of a better way to represent normals for lighting a 4096x4096 terrain in the shader? (without using lightmaps). Thanks in advance
Advertisement
(I'm not a D3D expert, so treat the following as tips to start with.)

Looking into the DeclarationType (of D3D9), I see
Ubyte4 -> [v0,v1,v2,v3]
which costs 4 bytes of storage, and expands its (4) values as they are. Since you need only 2 values, you may use
Ushort2 -> [v0,v1,0,1]
instead, which costs the same but gives you a higher precision.

Or you go with one of the normalized types, namely
Short2N -> [v0/32767,v1/32767,0,1]
or even the designated compressed 3D vector type
Dec3N -> [v0/511,v1/511,v2/511,1]

All of them cost 4 bytes per normal, but are presumbly better suited than a Float.
It does get a little fiddly when dealing with D3D9 vertex shaders as, from memory, the ubyte4 and possibly short/half precisions aren't universally supported by all hardware. Check the cardcaps.xls file for more details and, obviously, check in your code [smile]

Compression systems don't really become 'natural' until you're at D3D10 and above due to the lack of bitwise ops in D3D8 and D3D9 so doing thing like shortening down to a XY and Sign at half precision can allow you to reconstruct the full normal later on...


hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

Thanks for the replies.

I was hoping to avoid DX10 at the moment as my ultimate goal is for this to run on the XBox360.

I'll take a closer look at the declaration types.

Cheers
If you want extra XY precision and will just recompute Z in the shader, then a standard short2 should work The un-normalized short type was a required type since D3D8 and shader 1.1 (They're not listed in the decltypes caps bits, so they are required types).
I have to admit to finding it hard to visualise how you can "recompute the z component". How can you know what the z is without any clue as to what it was pre-compression?

Apologies for my ignorance, this is my first intro into geometry compression techniques.
The cross product of two axis will give the third - so normalize(cross(nx,ny)) = nz with one huge caveat - you don't know what sign the resultant axis is. I forget the finer mathematical details, but you have to be careful of this... It's a neat trick for tangent space normals as you can know that its +Z, but for regular normal vectors it could be + or - so sometimes storing extra information elsewhere (e.g. in an unused diffuse channel) can help...

hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

Thanks, Jack... I was just reading another site and how it works suddenly dawned on me! I raced to post my new understanding but you'd posted first :)

My compression is on tangent normals, so I don't really need to worry about the sign of the z (or in my case, y).

I've also been reading about DXT5_NM compression which looks promising.

Rob
For your normal, you know the length should be 1. Tell the shader that normal is a float3. Normal.z will be 0, as short2 is expanded to x,y,0,1. The 0 makes it a float3 which we can use for dot products (I don't think there is a 2D dot product... maybe there is in later shader models).

Since the input is XY, and you probably want it in XZ, I swizzle the input during the scaling phase.

float3 xyzscaled = IN.normal.xzy / 32767.0f;
float lensq = dot(xyzscaled, xyzscaled);
xyzscaled.y = sqrt(1.0f - lensq);

While it's true that we don't know the sign of the computed axis (Y in this case). But as you said it's for terrain, we can probably safely assume the land is facing UP.

This topic is closed to new replies.

Advertisement