Rebuilding normal from 2 components with cross product?

Started by
10 comments, last by Eric_Brown 14 years, 1 month ago
I stumbled over this thread where a guy describes his compressed vertex layout. He briefly explains that he stores tangent space in 4x16 bit. Normal and tangent are rebuilt by using cross products. I don't understand what he does. I thought the way to unpack an normal from 2 components is z = sqrt(1.f - x * x - y * y). Any idea what he means? He then builds the bitangent by using the normal and tanget. That part is clear, but I don't understand the previous step.
Advertisement
The author messed something up here:
Quote:- I calculate normal.z and tangent.z with cross products. And multiply them by the sign bits (stored in one bit of the x and z channel).

If you read further, you'll find this:
Quote:- For normal/binormal/tangent I have to separate the sign bits, calculate normal and tangent z-components (sqrt(1 - x^2 - y^2) * sign), and calculate binormal (cross(normal, tangent) * sign).
That seems me correct in principle, although I don't understand why there is the multiplication with sign for the bi-normal. If using a specific order in the cross-product, then a reversal possibility should not be needed IMHO.
Ah ok.
It's not the first time I hear about the multiply by sign though...

I'm curious how he stores the sign-bit. I mean the details of how to unpack in the shader code.

I read about several techniques for compressing a normal for use in a g-buffer. I don't remember that one being one of them.
Quote:Original post by B_old
It's not the first time I hear about the multiply by sign though...
For the sqrt( ... ) for sure. But for the cross-product? Please notice that the entire vector is negated
Quote:cross(normal, tangent) * sign
and because
a x b == -( b x a )
I'm still unconvinced about the need of that multiplication.
That seems me correct in principle, although I don't understand why there is the multiplication with sign for the bi-normal. If using a specific order in the cross-product, then a reversal possibility should not be needed IMHO.

If the UV coordinates are flipped, (eg, you model only half of a human that mirror it) then you must also have the sign of the binormal, otherwise normal mapping wont work.
Quote:Original post by haegarr
Quote:Original post by B_old
It's not the first time I hear about the multiply by sign though...
For the sqrt( ... ) for sure. But for the cross-product? Please notice that the entire vector is negated
Quote:cross(normal, tangent) * sign
and because
a x b == -( b x a )
I'm still unconvinced about the need of that multiplication.

You give two equalities in your example equation, but the shader still needs to know which one of those to actually do. You can't guarantee that the normals always face out (or in) when artists are involved :)

Here's some shader stuff for this. Note that I make my shader source in the code, but it should be readable enough.

AddLine("float4 TanNormal=(In.TanN/127)-1;");AddLine("float4 BiNormal=float4(cross(Normal.xyz,TanNormal.xyz),0);");AddLine("		BiNormal.xyz*=TanNormal.w;");AddLine("Out.NormW=mul(Normal.xyz,(float3x3)ModelToWorld);");AddLine("Out.BiNormW=mul(BiNormal.xyz,(float3x3)ModelToWorld);");AddLine("Out.TanNormW=mul(TanNormal.xyz,(float3x3)ModelToWorld);");


In.TanN is a UBYTE4 as are all the normals here. Normal and TanNormal are also pulled out from UBYTE4's previously.

Here's the snippet I use to put the sign into the In.TanN.w
tU32	Munk=NormalToUBYTE4(TangentNormal);if (Flipped)	Munk|=0x00000000;			// 00 equating to -1else	Munk|=0xfe000000;			// fe equating to +1Munk is obviosly what I'm about put poke into the vertex stream for the finished normal.

I'm sure you can fill in the blanks, but shout if not.

EDIT: The or with zero is a placeholder for the comment btw ;)
------------------------------Great Little War Game
Thanks for the answer, Rubicon.
I am a little confused about what you are actually sending to the shader. :)
The normal (x,y,z) and the tangent (x,y,z) and then you build the bi-tangent from that?

Currently I have 16 bit snorm for all compponents of the normal and the tangent. I actually store the sign for the bi-tangent in separate 8 bits. :)

I'm not convinced that 8 bit normals will be enough for me, but I'm intrigued by the idea from that other thread. I just don't quite understand how to extract that sign bit, although I believe can try to work something out with the help of your hints.
When I'm doing this, I have to take the input as int in the shader? Is that possible or do I have to cast? (Can't try before tomorrow, but still curious).
Quote:Original post by B_old
Thanks for the answer, Rubicon.
I am a little confused about what you are actually sending to the shader. :)
The normal (x,y,z) and the tangent (x,y,z) and then you build the bi-tangent from that?

Relating to the C snippet I posted, I'm making vertices in my mesh tool as I call that snippet. I'd have the normal and tangent as a full vector3 from earlier calculations as I make vertex normals from the faces etc.

When I come to put these into the vertex stream, I compress them both to UBYTE4 with a byte per component, signed in the range of -127 to +127.

That trickery involving "Munk" is setting the 4th component of the second normal (the tangent one) to a value I use later. When the VS gets these two UBYTE4's it unpacks them to float4s and does the cross product (using .xyz) to produce the bitangent. This is then multiplied by tangent.w to correct it's sign as this has come out at -1 or +1

The actual vertex data gets poked with my normal normal compressed using that function, then "Munk" goes straight in as my tangent.

(Wherever you read "BiNormal" in my source, it should say "BiTangent" - I only read about the correct terminology recently)

I should also have posted this, I guess:

tU32	RZVertexMgr_Base::NormalToUBYTE4 (const RZVector3& Normal){	tU32	Munge;	Munge=(  ((tU32(Normal.VZ*127.0F+127.0F)&0xFF)<<16) | ((tU32(Normal.VY*127.0F+127.0F)&0xFF)<<8) | ((tU32(Normal.VX*127.0F+127.0F)&0xFF)<<0) );	return Munge;}


Quote:Original post by B_old
I'm not convinced that 8 bit normals will be enough for me, but I'm intrigued by the idea from that other thread. I just don't quite understand how to extract that sign bit, although I believe can try to work something out with the help of your hints.
When I'm doing this, I have to take the input as int in the shader? Is that possible or do I have to cast? (Can't try before tomorrow, but still curious).
Hopefully the extra explanation above will help. I can't just send you a bunch of code as this is actually all little pieces from all over the place.

Even though I'm sending UBYTE4, you still tell your VS you're expecting to get float4s and it will fill them in appropriately. If you send through 255/255/255/255 in your UBYTE4, it will unpack into the VS input float4 as 255.0/255.0/255.0/255.0 automatically. This explains why I then divide by 127 and then subtract 1 as these 0-255 input ranges are meant to shrink down to -1 to +1 to be useful.

(There is a _NORM extension to UBYTE4 which will do this remapping automatically and thus remove the need for that 127 chicanery, but I don't trust global support for that extension and I've posted code that does it reliably without.)

Trust me, 8 bits per component is plenty, especially if it gets your whole vertex to 32 bytes or lower. I've used it for years and nobody's ever even noticed - you get a perfect spread of lighting across a ball at any magnification you like - don't forget its interpolated per pixel - all this decompression happens at the VS level, not the PS.

[Edited by - Rubicon on March 14, 2010 5:30:37 AM]
------------------------------Great Little War Game
Quote:Original post by Rubicon
You give two equalities in your example equation, but the shader still needs to know which one of those to actually do. You can't guarantee that the normals always face out (or in) when artists are involved :)
Well, I've seen it from the pure mathematical "cross-product" point of view where the multiplication would not be needed. Of course, if your intention is to be able to store either the (mathematically) correct vector or else the opposite vector, then you need to have a flag. I understand its meaning now, thanks.
Rubicon, thanks for the answer.
I guess the reason I don't trust 8 bit normals is, that while I was investigating normal storage for my g-Buffer, I found that 8 bit is not enough. I could clearly see artifacts in combination with specular highlights. But as you say, in this case the normals are interpolated first, so maybe this won't be an issue. I believe you, that you get uniform distribution in case of a sphere. What about something really complex?

I think I should be able to fix support for 8 bit normals. I'm using 16 bit right now and it should work analogous.
What I still haven't understood, is how to efficiently extract 1 bit from the 16 bit 2 component normals that are used in the other thread.

I think I'll give the 8 bit normals a try soon, especially since they should be easier to handle and with less computational overhead. On the other hand, 15 bit precision for the same amount of memory doesn't sound to bad either, right?

This topic is closed to new replies.

Advertisement