Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    375
  • comments
    1136
  • views
    298210

Vertex Format Compression

Sign in to follow this  
superpig

263 views

OK, I'll attempt to be useful today. I was thinking of writing this as an article but a journal post is probably better.

The average 3D game has a lot of geometry data. Quake 3 levels have thousands of polygons; more recent systems like HL2 have orders of magnitude more. Thousands of polygons bring with them thousands of vertices. We've got limited video memory, so we can only load so many vertices onto the card at once; however, we want to avoid splitting the vertices up into seperate buffers as much as possible, because switching buffers is slow. How can we improve the memory footprint of our vertices?

One of the things that many people seem to overlook is the flexibility we have in our vertex formats. In the days of the programmable pipeline, position data doesn't necessarily mean an (X, Y, Z) float tuple.

So firstly, you can drop bits you don't need. I was writing a water renderer recently, in which all vertices in the source mesh were at the same height. If they're all at the same height, I don't need to store that height in each vertex - I can store it in a vertex shader constant instead, and the shader can 'reassemble' the position before using it. So that's what I did. You can use a similar approach for, say, a mesh that is a constant colour but has varying alpha. And if you get really desperate for space, you can drop components that could be deduced in the vertex shader - if your normals are normalised, you can calculate the third component using only the first two. Same for blend weights.

Secondly, you've got a choice of formats. We know that X bits can store (1 << X) different values. Consider your source data - do you really have that many unique values to store? Even if you do, is it important that the values be so diverse? Through quantization - mapping the full set of values onto a restricted set, like rounding everything to the nearest integer - you can reduce the number of bits you need.

Consider texture coordinates. You can create meshes that only have texture coordinates in the 0..1 range, by adjusting and subdividing anything outside that. 0..1 might be a bit too restrictive.. we'll say -8 to +8, just to give us some room to wrap a little. So, our texcoords are in the -8 to +8 range, and most people would store texcoords as 32bit floats, so we've got (1 << 32) unique values between -8 and +8 that our texcoords could be. (1 << 32) is around 4.3 billion, so the smallest difference between two values we can store is in the region of 0.000000003. If we were using a 256x256 texture, that resolution lets us store texture coordinates to the accuracy of around 0.000001 of a texel.

If you ask me, that's overkill.

So let's drop it down a size. Let's implement texture coordinates as 'short's instead - 16-bit values. (1 << 16) is 65535, and 65535 values across our [-8..8] range gives us about 0.00024 between values. For a 256x256 texture, it results in texture coordinates accurate to 0.0625 of a texel - or 1/16th. Much better.

'short' is a 16-bit integer type ranging from 0 to 65535. Our texture coordinates are floating point values ranging from -8 to +8. How do we consolidate the two? Simple - linear mapping. -8 will map to 0, and +8 will map to 65535, with values in between being evenly distributed. Encoded value = ((value + 8) / 16) * 65535. In the vertex shader, just reverse the process - takes one multiply-add instruction - and you're sorted.

And in the process you've halved the space that texture coordinate was taking up. Nice job.

Check out the docs for a full list of available formats. I'm eyeing DEC3N for normals, amongst other things. It's slightly irritating that the set of available types is fairly limited - there is no SHORT1 or SHORT3 type, for example - but you can try and take advantage of spare slots by packing data together. Need to store UV and a bone index? Pack them together into a SHORT4, and hey, if you've got an extra 1D texcoord knocking around there's a free slot for it.
Sign in to follow this  


6 Comments


Recommended Comments

Nice post [smile]

Just a quick clarification... from my general reading about the subject, very few games (note: not tech demos!) are transform limited. Thus, I presume, it's fairly safe to say that the overhead of reconstructing/decompressing vertex data is of little-to-no importance performance wise?

A regular vertex:

Position : 3 floats
Normal : 3 floats
TexCoord : 2 floats
---
32 bytes

Position: 2 floats (1 is a constant height -> only works for your water example though)
Normal : 2 floats (normalized => 3rd can be known)
TexCoord: 2 shorts (as per your example)
---
24 bytes

So, for the "optimal" 4mb static vertex buffer approximately 43 thousand more verticies for your money [grin]

Jack

Share this comment


Link to comment
Yes, it's fair to say that most games aren't transform limited. In truth decompression of things like texcoords has usually come free for me - it's the difference between "mov oT0.xy, v2.xy" and "mad oT0.xy, v2.xy, c95.xx, c95.yy".

And that comes to 20 bytes, by my count, not 24 (4 * float + 2 * short = 4 * 4 + 2 * 2 = 20).

Share this comment


Link to comment
Quote:
And that comes to 20 bytes, by my count, not 24 (4 * float + 2 * short = 4 * 4 + 2 * 2 = 20).

I can count. Honest. [embarrass]

Jack

Share this comment


Link to comment
Quote:

Check out the docs for a full list of available formats. I'm eyeing DEC3N for normals, amongst other things. It's slightly irritating that the set of available types is fairly limited - there is no SHORT1 or SHORT3 type, for example - but you can try and take advantage of spare slots by packing data together. Need to store UV and a bone index? Pack them together into a SHORT4, and hey, if you've got an extra 1D texcoord knocking around there's a free slot for it.


Which docs would this be?

Share this comment


Link to comment
The docs for whichever API you're using - in my case, the DXSDK docs.

Share this comment


Link to comment
If you're using OpenGL then you are only restricted by the types you can set for each attribute pointer as there are no fixed vertex stream formats.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!