When generating images to display on a screen, it is really important to understand the principle of gamma correction. If you don’t carefully implement gamma correction into your 3D engine, at first textures with simple lighting calculations will look OK. But later, when doing more advanced effects like HDR, you will start seeing weird errors like color saturation.
Why do you need to implement gamma correction?
You need to implement it because on a CRT screen the colors of an image are not displayed linearly but exponentially. LCD screens also mimic this behavior.
Some common image formats (like JPG images) correct each color values by gamma correcting the pixels. They generally use a Gamma value of 2.2 which generally gives good looking results.
There are 2 types of problems that can happens when not dealing with gamma correction:
- If you sample textures that are gamma corrected, the sampled color values will not be in linear space, so all lighting calculations (which are linear) will be wrong.
- Also, if you don’t gamma correct the final generated image, the colors will look darker on your display.
You can find more information in this excellent article from NVidia.
What can I do to deal with that on PC?
On PC with Direct3D 10/11, it is very easy to implement. There are special texture formats that can tell to Direct3D to automatically convert gamma-corrected textures into linear space at no additional costs. It works well and even do that conversion before any mip-mapping.
You can also set the texture format of a 8-bits encoded render target to SRGB, to tell Direct3D to automatically convert the linear color values to gamma-corrected space. What is really nice is that if you do blending operations to your render target, Direct3D will automatically convert the colors to linear space, do the blending and the re-convert the values to gamma-corrected space.
What can I do to deal with that on Xbox 360?
On Xbox 360 with XNA, this is not as easy than on PC. The Xbox 360 also use special texture formats for gamma correction but there are not available on XNA.
So the only way to implement gamma correction on Xbox 360 is to do it manually into your shader code.
When you sample a texture, you can convert it to linear space with this code:
float4 color = tex2D(sampleTexture, textureCoordinates);
return float4(pow(color.rgba, 2.2f));
It will work well but you have to be aware that this operation takes place after mip-mapping so it is not 100% accurate.
To convert the linear color values of your render target back to gamma-corrected space, you have to do a full screen post process pass that will convert each pixels like this:
float4 LinearToSRGB(in float4 color)
return float4(pow(color.rgb, 1.0f/2.2f), color.a);
Here we are only converting the RBG color values, not the alpha channel which must stay linear.
Note that you have to do that in the last step of your post-processing pipeline. If you do the correction on all your intermediate render targets, you will have problems when blending them together.
You also need to be aware that because we do that in the last step of the render pipeline, if you use additional 8-bits intermediate render targets you will loose some precision for darker colors. But I think it is not a problem nowadays because with HDR and deferred rendering we don’t use a lot of 8-bits intermediate render targets.