Compact World Space normal storage in g-buffer

Started by
6 comments, last by kauna 11 years, 3 months ago

Until now I used to store the normals in my g-buffer in view space and encoded with the spheremap technique that is described is here (two 16 bit unorm channels). That works good.

I find myself using world space normals increasingly often for world space light probes and stuff like that, so I thought it might be beneficial to just store world space normals in my g-buffer.

The spheremap technique is not suitable any longer because I get completely wrong normals close to the "z-pole" when viewing the normals of a sphere for instance.

I then tried storing the normals as mentioned in the paper about the Unreal Engine 4 demo. They simply use a R10G10B10A2 format. It has the same space requirements, but even with a specular power of 128 I can clearly see artifacts in the hightlights on a sphere, which is disappointing. (Of course the problem is much less apparent with more complex geometry and normal maps.)

I also tried a R16G16B16A16 format which looks fine. But I am curious I you know of some more compact storage method that works well with world space normals?

Advertisement
You could try storing just x and y to 16-bit channels and reconstruct z. But you need steal one bit of information from other channels to get good results. If you have this extra bit that you could use this would be easy and fast encode/decode scheme with ok quality

Spherical coordinates will give you pretty good precision in world space, especially in 16-bit. However you'll eat up some ALU cycles on trig instructions. Another option is to simply transform from world space to view space, compress using spheremap, and decompress/transform back to world space when sampling your G-Buffer. This isn't always as bad as you think, especially on GPU's with oodles of ALU to spare. There's also best-fit normals, but I'm not a big fan of having to use a lookup texture.

Spherical coordinates will give you pretty good precision in world space, especially in 16-bit.

Edit: ahhh... missread sperical with spheremap, my fault, the following statement is only valid for spheremap transformation. spherical should work like sugguested.

I see one issue with blind spots. From my experiences with all the compression algorithms from aras page, you will encounter a blind spot, a single vector, when approxiamated, will suffer in compression quality and therefore results in ugly artifacts. As example take a look at method 4, first algorithm. You will see that the normal (0,0,1) and (0,0,-1) have the same encoding, therefore the decoding of one of these vectors will be wrong. This seems to be just one case, but the artifacts I have experienced were really obviously.

This is no issue in view space, because you can adjust the algorithm to hide the blind vector (pointing into the screen), so that it is practically never compressed. But when you use world space, you will have spots where the blind vector is visible (e.g. lighting artifacts), and it will be clearly visible.

You could try storing just x and y to 16-bit channels and reconstruct z. But you need steal one bit of information from other channels to get good results. If you have this extra bit that you could use this would be easy and fast encode/decode scheme with ok quality

I thought about this as well. I prefer to have the normals in a single render target though. This means I have to cram the x,y and the z sign into a single channel. I have not tried it yet, but it should work. Wonder how it will perform.

Spherical coordinates will give you pretty good precision in world space, especially in 16-bit. However you'll eat up some ALU cycles on trig instructions. Another option is to simply transform from world space to view space, compress using spheremap, and decompress/transform back to world space when sampling your G-Buffer. This isn't always as bad as you think, especially on GPU's with oodles of ALU to spare. There's also best-fit normals, but I'm not a big fan of having to use a lookup texture.

I overlooked the spherical coordinates approach. It seems like a good option to me.

Until now I transformed my normals to world space when I needed it and of course I could let it that way. But I feel a bit uncomfortable about it, because then it seems it would be preferable to do the typical lighting calculations in view space and only transform to world space when there are no other options. Maybe I could mix both, because I reconstruct both world space and view space positions from exactly the same source. But generally this is exactly what I wanted to avoid in the first space, assuming that it would be a bit clearer to do everything in the same space. Depends a bit on how expensive the spherical coordinates really are.

Just looking at world space normals, what do you think is faster: decoding WS normal stored in spherical coordinates or decoding VS normal stored in spheremap and transforming to view space?

I also wonder how Epic gets away with it in their demo. Maybe it's really not that apparent with proper geometry/materials. Or do you think the remark "Gaussian Specular for less aliasing" has anything to do with it?

Thanks for the replies, that gives me some options to try!

[quote name='B_old' timestamp='1357719213' post='5019396']
I thought about this as well. I prefer to have the normals in a single render target though. This means I have to cram the x,y and the z sign into a single channel. I have not tried it yet, but it should work. Wonder how it will perform.
[/quote]

This depends on how you use this channel. If you encode the sign with other data, you could get in trouble when you need to interpolate these data (e.g. down scale, blending etc.).

I for one use 16 bit floating point targets compressing the normal into 2 channels using the methods mentioned on aras page. This is done in view space, therefore I have no troulbe hiding the z-pole (blind spot) vector into the screen, getting rid of the lighting artifacts. I need worldspace normals very seldomly, therefor doing all calculations in view space is the best solution for me.

Benefits of doing all in view space:

1. The position reconstruction is very fast (depth + screen pos, no transformation necessary).

2. Many screenspace methods works very well in view space (SSAO,normal mapping).

3. Lights can be transformed to view space before uploading them to the GPU, therefor no additional overhead for using view space here.

4. View space is tighly packed around the (0,0,0) center, this has the benefit of high precision of floating point numbers.

Benefits of doing all in view space:

1. The position reconstruction is very fast (depth + screen pos, no transformation necessary).

2. Many screenspace methods works very well in view space (SSAO,normal mapping).

3. Lights can be transformed to view space before uploading them to the GPU, therefor no additional overhead for using view space here.

4. View space is tighly packed around the (0,0,0) center, this has the benefit of high precision of floating point numbers.

1. It is almost as fast for world space

2.,3. No issues here in world space, I assume

4. Very interesting point I never thought about

[quote name='Ashaman73' timestamp='1357736141' post='5019432']
I for one use 16 bit floating point targets compressing the normal into 2 channels using the methods mentioned on aras page.
[/quote]

I used also 16-bit floating point with stereographic projection, but I encountered some bad geometry which produced nan/inf in the render target, which messed up all the lighting calculations and then luminance calculations. I wasn't able to determine the problem causing geometry so I switched back to 16-bit unorm format which doesn't have the problem of nans/infs.

Cheers!

This topic is closed to new replies.

Advertisement