Compressing vertex attribute normals

Started by
12 comments, last by tanzanite7 11 years, 9 months ago
Hello. I am making a game which generates a huge amount of geometry which is using up too much VRAM. I am using almost 250MBs of data for just a simple test level, and I expect to have up to 4 times as large levels, so I'm in a pretty bad situation. Only a small part of the screen is visible at any moment, and it seems like the driver is really good at swapping out unused data to RAM so even if I go over the amount available, it won't grind to a halt. It still produces pretty bad stuttering if I quickly turn around for the first time in a while and a big amount of data has to be swapped back. All in all, reducing the amount of data seems like a good start in reducing the problem, but I might need other measures too.

The geometry is static and stored in VBOs. Each vertex contains the following information:

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


Total: 36 bytes per vertex

I definitely need 4 bytes per dimension for position and 16-bit texture coordinates. Animation data can't be compressed either. Therefore I was hoping to at least be able to compress the normals as much as possible. They do not need to be very accurate. 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.


TL;DR: Is it possible to compress a 3-float world space vertex normal to only 4 bytes and quickly unpack it to in a vertex shader?
Advertisement
I am using almost 250MBs of data for just a simple test level,[/quote]
Yea I would post where your memory is going to because that sounds terrible when you say the word test.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

Yea I would post where your memory is going to because that sounds terrible when you say the word test.[/quote]
Like I said, to vertex data for terrain. A majority of all levels will be of the size of the "test" level, but larger levels are also possible. Running at the maximum supported world size results in 1080.4 MBs of data; 997.3MBs of vertex data and 83.1MBs of index data. With compressed normals the vertex data would drop by 22% to around 774.9MBs, putting the total at 858MBs, which barely fits my 896MB VRAM graphics card.

Like I said, most of the level will be far away from the player, so I'll probably add some code to unload data for far away terrain. Try to focus on the question please. =S
Well your question leads to basically: you are doing something wrong. 858MB/32bytes = 24 million verts.You need to LOD your terrain. I asked where the memory is going because, you have no memory set aside for textures or shadow maps.


Is it possible to compress a 3-float world space vertex normal to only 4 bytes and quickly unpack it to in a vertex shader?[/quote]
Put 2 components in and cross product to get the 3rd. (2 bytes per component)

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

Are you using indexed drawing? That way, up to 6 triangles can share the same vertex. Normals can sometimes be shared for terrain. It may even be desired, to get a smooth view.

texture coordinates can be more tricky.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

Put 2 components in and cross product to get the 3rd. (2 bytes per component)

A cross-product is between 2 vectors. To get the 3rd component of a vector you have to determine what value normalizes the vector.
sqrt( 1.0 - ((X * X) + (Y * Y)) )
Assuming Z is the forward vector and thus always positive.


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

I think what he meant is store two components (x,z) and then get the third one from y = sqrt(1-(x^2+z^2)). that doesn't give you the sign of y but for example in a heightfield you can usually assume that the y component has to point "up".
Well your question leads to basically: you are doing something wrong. 858MB/32bytes = 24 million verts.You need to LOD your terrain. I asked where the memory is going because, you have no memory set aside for textures or shadow maps.[/quote]
I am well aware of this. I will take steps to reduce the amount of data too. Are you saying I should focus on that instead?

Are you using indexed drawing? That way, up to 6 triangles can share the same vertex. Normals can sometimes be shared for terrain. It may even be desired, to get a smooth view.
texture coordinates can be more tricky.[/quote]
Yes, vertices are reused whenever possible.

Assuming Z is the forward vector and thus always positive.[/quote]
This is not the case. The normals are in world space.

I think what he meant is store two components (x,z) and then get the third one from y = sqrt(1-(x^2+z^2)). that doesn't give you the sign of y but for example in a heightfield you can usually assume that the y component has to point "up".[/quote]
The normals can face any direction, so this is probably not going to work well.


I was recommended this article in another forum: http://aras-p.info/texts/CompactNormalStorage.html I may use method 4 in that article, but for now I'll focus on reducing the amount of data.

Assuming Z is the forward vector and thus always positive.

This is not the case. The normals are in world space.
[/quote]
Not after they have been transformed into view space and culled.
My assumption is correct and the technique works. You aren’t doing lighting in world space (if you are, shame on you) and you don’t need the Z component until you are in view-space.



I think what he meant is store two components (x,z) and then get the third one from y = sqrt(1-(x^2+z^2)). that doesn't give you the sign of y but for example in a heightfield you can usually assume that the y component has to point "up".


The normals can face any direction, so this is probably not going to work well.
[/quote]
Read above. They can’t face any direction, they can only face towards the viewer. We are not talking about world space here.
So again, I restate, store the X and Y only and calculate the Z in the vertex shader.
The Z will always be positive, but a false positive will never reach the screen—any faces that point away from the screen will be culled, thus all false positives will be culled.


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

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.

You can drop one component and reconstruct it with this trick, but you do need to store a sign-bit somewhere to correctly reconstruct the missing component.
[hr]
32-bits per component for normals is definitely overkill, 16-bits per component will definitely suffice. You can either use a fractional integer format (where -32768 is unpacked to -1, and 32767 is unpacked to +1, automatically for free, by the input assembler before being fed into the vertex shader), or half-floats (which are also automatically unpacked for free).

Even 8-bits per component may be enough if you actually make use of all 24-bits of data. When storing normalized values, then most of the possible values in that 24-bit space are unusable (because they don't represent normalized vectors), but there is a crytek presentation somewhere (search for "best fit normals") that makes the observation that these unusable/non-normalized values can be normalized in your vertex shader to re-create the original input value fairly accurately. The theory is that at data-build-time you take your high-precision normal, and then search the full non-normalized 3*8-bit space for the value that when normalized will best recreate the input value. At runtime, the decoding cost is 1 normalize in the vertex shader (again, the input-assembler should be able to do the scale/bias from int8 to float for free). This increases the precision of 8-bit world-space normals by about 50x.

Also, you may be able to get away with using half-floats for your positions if you break the mesh up into several local chunks -- each chunk with it's own local origin in it's center -- and use an appropriate transformation matrix for each chunk to move it back to the correct world location.
Half-floats have amazing precision between 0-1, decent precision between 1-2, but start getting pretty inaccurate for large values, so you'd have to experiment with scaling factors too. For vertices that are a large distance from the origin, the visible artefact will be a quantisation of positions, which will make originally-straight lines look wobbly and smooth curves look faceted.

This topic is closed to new replies.

Advertisement