Deferred Rendering in XNA

Started by
7 comments, last by smasherprog 13 years, 6 months ago
I implemented a deferred renderer in XNA based on the 2 articles in GPU Gems 2 and 3 (http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter09.html, http://http.developer.nvidia.com/GPUGems3/gpugems3_ch19.html).

I'm satisfied with the final result, but I have a few problems.

Using SurfaceFormat.Color (RGBA32) is good enough for Color, but absolute garbage for normals. To solve this, I can either use 64-bit render targets, or mix and match 16 and 8 bits channels (this is tricky with XNA because it uses D3D which forced identical render target format).

My current setup is SurfaceFormat.Color for color (alpha for specular power), SurfaceFormat.Color for Normals alpha for specular light intensity) and SurfaceFormat.Single for depth and stencil. I used the 4th render target for edge detect and a few other optional things.

One solution, is to stick to SurfaceFormat.Color for color, but split normals between 2 render targets (SurfaceFormat.Rg32 each). Normals Red and Green channels go into the first Rg32 target. Blue goes into the Red channel of the second and the remain 16 bits are for specular light (which is a waste). This fragmentation of normals only works for the Red and Green, the blue channel looks wrong and subsequantly yields wrong normals. On top of that, is there any way to split the 16-bits in the second channel so they can be used for both specular light and edge detect. Is there an EFFICIENT way to do that?

Second option is to use 64-bit render targets. The problem here is that I am pretty much limited to only one format: RGBA64 (http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.surfaceformat.aspx).

This means that I have to fragment pretty much EVERYTHING (including the storage for the 32-bits of the depth buffer). Is there a realistic way to do this?

Would appreciate any help I can get.
Advertisement
Are you encoding your normals? There are many ways to do so. With encoding you can probably get away with only two 8-bit values for your normals.
Quote:Original post by MJP
Are you encoding your normals? There are many ways to do so. With encoding you can probably get away with only two 8-bit values for your normals.


Thank you for the reply.

Spheremap Transform appears to be the best choice on that list. Do you have any recommendations? Also, any reasons not to stick to the Rg32 format (since I only need components of the normal now)?
Honestly I would avoid the hastle and either stick with rg32 or bump your g-buffer size up to 64bit if your GPU can handle it. The latest batch of GPUs (I have a gtx470) show hardly any performance drop even from upping the g-buffer to 128bit!

A 64bit g-buffer would also let you store a lot of extra information given some encoding methods to compact two 32bit outputs into one 64bit one. For example the diffuse could be put into the first half of the first texture and then you have specular and other information in the second 32bit half. Tack on a 32bit depth into the third RT and you've got yourself another 32bit extra half to play with. There's a lot of things you can do with those extra slots and you still are only using 3 RTs with perfect Vector2 normals.
Portfolio & Blog:http://scgamedev.tumblr.com/
Quote:Original post by CombatHammie
Thank you for the reply.

Spheremap Transform appears to be the best choice on that list. Do you have any recommendations? Also, any reasons not to stick to the Rg32 format (since I only need components of the normal now)?


Yeah spheremap is good, although it works best for view space normals as opposed to world space. And yes Rg32 is a surface format for that encoding, except you'll need to check that the device supports it. Any DX10-class hardware should support it as a render target, but DX9 hardware probably won't. Also I don't think the 360 supports it, if you're planning on targeting that. HalfVector2 should be a good alternative in those cases.

I am working on a C2D 960/GTX 480 workstation. However, I am not interested in anything like 128-bit render targets right now.

What I would like to do is have both a mid-range and a high-end modes. That is, design the rendering engine to use both 32-bit and 64-bit render targets.

The only question that remains, is how to fragment actual components? For example, in an actual 64 bit buffer (R16G16B16A16), how do I efficiently split the 16-bit channels? And vice versa (combine two 16-bit channels into one 32-bit channel).
Quote:Original post by MJP
Quote:Original post by CombatHammie
Thank you for the reply.

Spheremap Transform appears to be the best choice on that list. Do you have any recommendations? Also, any reasons not to stick to the Rg32 format (since I only need components of the normal now)?


although it works best for view space normals as opposed to world space.


Would you be able to elaborate on that?
Packing multiple values into a single component is pretty ugly in SM3.0 and below, because you don't have integer ops. So essentially what you need to do is use floating point math to determine the final integer value of a component, and then divide that value by the max integer value to normalize back to the [0,1] range. So for instance lets say you wanted to pack two [0,1] values into an 8-bit integer. This would mean you need two 4-bit integer values. The a 4-bit value has a range of [0, 15], so you would simply multiply by 15 to get the integer value. For the upper 4 bits, if you had integer ops you would take your 4-bit value and shift to the left 4 bits. With floating point ops we emulate this by multiplying by 16. Then you would add (equivalent of bitwise OR) the two values to get your 0-255 integer value. After this you would divide that sum by 255 to get the final [0,1] value to be written out to the render target.
float Pack8Bits(float val0, float val1){    float lower = val0 * 15.0f;    float upper = val1 * 15.0f * 16.0f;    float sum = lower + upper;    return sum / 255.0f;}


To adjust for 16-bit you would just expand the range to 0 - 65535 instead.
float Pack16Bits(float val0, float val1){    float lower = val0 * 255.0f;    float upper = val1 * 255.0f * 256.0f;    float sum = lower + upper;    return sum / 65535.0f;}


EDIT: Fixed the code after making a really dumb mistake the first time

[Edited by - MJP on October 2, 2010 7:35:36 PM]
You could use the DXGI_FORMAT_R10G10B10A2_UNORM format for your render target for your normals. That should give you more than enough resolution, just ignore the alpha. Ether that, or use the DXGI_FORMAT_R16G16_FLOAT and compute the z as needed. I am not sure which one would be faster.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.

This topic is closed to new replies.

Advertisement