Compressing vertex attribute normals

Started by
12 comments, last by tanzanite7 11 years, 9 months ago

[quote name='L. Spiro' timestamp='1343734169' post='4964811']We are not talking about world space here.
Then "we" aren't helping -- theagentd posted the thread and is reiterating that the original (unanswered) question of the thread was about compressing world-space normals in a vertex buffer...
Also, view-space normals can have both + and - z values with a perspective projection! Making the assumption that z is always positive will cause strange lighting artefacts on surfaces at glancing angles.
[/quote]
My first reply is in regards to compressing world-space normals.
Yes they are world-space within the vertex buffer, but you must recognize that they are not actually used until later. The values when they are used are what matter, so when approximating them, or in any other way modifying them before that point, really has no meaning. They could be XYZARB, up until the moment when they are used within a lighting equation as long as the equation is using the standard terms.

Basically, we are helping because the original topic poster assumed the normals need to be used in world coordinates. You can define 2 components in world space and then derive the third in view space later, inside the shaders, at whatever point lighting needs to be done.

If you think about it you can see how the sign for the Z component is always positive and then how that leads to my previous suggestion.
But if you need more than just my word, I can tell you that we use this type of compression at work, and I can promise with actual hands-on experience that it works.
The only way in which it fails is when you don’t reject back-facing polygons, but that is a rare condition and we have special cases for that.

That being said, I also recommend 16-bit normals. Normals are usually confined to a small range and you gemerally don’t need to exceed this range.
Using 2 16-bit floats instead of 3 32-bit floats will save you a lot of memory.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Advertisement
If you think about it you can see how the sign for the Z component is always positive
Think some more. The rasterized triangles are not in view-space; they have gone through a perspective transformation, which allows them to be facing slightly away from the camera and still be visible, and still pass the 'backfacing' test (i.e. they are not back-facing in post-projection space, but are back-facing in view-space, so they aren't culled, but do have negative view-space z).
To visualise, imagine standing in a room where the floor is slightly sloping downwards away from you, and the roof is slightly sloping upwards away from you -- both the floor and roof normals are pointing away from the camera, but because of perspective, they are both visible!
But if you need more than just my word, I can tell you that we use this type of compression at work, and I can promise with actual hands-on experience that it works.
Yes, it mostly works, but has artefacts on glancing angles (and gets worse the larger the FOV).
Tactfully raise this issue with your senior graphics programmer, or show the artefacts to a senior artist, and make yourself look good tongue.png
Basically, we are helping because the original topic poster assumed the normals need to be used in world coordinates. You can define 2 components in world space and then derive the third in view space later, inside the shaders, at whatever point lighting needs to be done.
Would you care to explain to the OP how to compress his 3 world-space values down to just two values, how to transform these 2 values into view-space, and then how to reconstruct the missing 3rd view-space component correctly?
e.g. say I've got two test world-space normals, for the floor and roof of a room [font=courier new,courier,monospace]A=(0,0,1) B=(0,0,-1)[/font] and my view-matrix is [font=courier new,courier,monospace]identity[/font], to simplify things.
Step 1) Drop the world-space z component: A=(0,0) B=(0,0)
Step 2) Transform to view-space: A=(0,0) B=(0,0)
Step 3) Reconstruct view-space z component: A=(0,0,1) B=(0,0,1) ... now the floor and the roof have the same normal.

Again, if you want to use this technique and correctly reconstruct the missing component, you need to store a sign-bit somewhere so that you can flip the reconstructed component in some cases. e.g. you could store [font=courier new,courier,monospace]x[/font] in 16-bits, [font=courier new,courier,monospace]y[/font] in 15 bits, and the sign of [font=courier new,courier,monospace]z[/font] in y's spare bit.
Now that I am sober I can easily see that you are correct.
I also now remember more accurately that at work we are not sending only 2 components to the GPU. I was completely mixed up regarding the point in the pipeline where that happens and made an idiotic “promise” regarding hands-on experience.

My previous advice in this thread can be disregarded.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid


1) format
Position, 3 x 4 bytes.
Normal. 3 x 4 bytes.
Texture coordinates. 2 x 2 bytes
Animation data, 4 x 2 bytes.

2) I definitely need 4 bytes per dimension for position

3) I was hoping to compress each normal to only 4 bytes, meaning I'll save 8 bytes from each vertex (~22% less data per vertex). Is this possible? Packing would be done at load time, but unpacking will be done in the vertex shader, meaning that it'll have to be pretty fast to be a worthwhile trade-off.

I had a similar problem with terrain data not fitting into my target memory spec and had to shrink my 32byte vertex format to 16byte. So, based on that experience:

1) My format ended up being (2 variants):
* 3x2B - position
* 2x1B - misc material params
* 4x1B - more material data OR 2x2B texcoord
* 4x1B - normal + 1 byte extra data OR quaternion - normal & tangent vectors

2) ... so, I have to challenge this. I use a dangling attribute to move a whole "chunk" of vertexes to where they should be (hence why a full float is not needed to get the exact same precision and range). Understanding how floating point numbers work is a must tho or cracks can appear - when done correctly it is guaranteed to give the exact same end result than when one would have used 4B-floats for positions. Might be worth considering.

3) using bytes has been sufficient for me, might be to you too. Unpacking is just one MAD instruction which is so ridiculously cheap that it wont have any effect on performance at all.

So, i would recommend:
Position, 3 x 4 bytes. => 3x2B (+dangling attribute)
. => 2B padding data for sane alignment
Normal. 3 x 4 bytes. => 4x1B (normal + something else to not completely screw up the alignment)
Texture coordinates. 2 x 2 bytes => 2x2B (if you really-really need it, i generate them in vertex shader based on position)
Animation data, 4 x 2 bytes. => 4x2B (as you said you can not shrink it, sure?)

=> 6+2+4+4+8 = 24B

edit: or use 3x2 normal instead of the 2B padding + 4x1B.

This topic is closed to new replies.

Advertisement