XNA 4.0 Gamma Corrected Pipeline


This post will show how to implement a Gamma Corrected pipeline  in XNA 4.0 (PC and Xbox).

Normally we dont pay too much attention to gamma correction, the reason in that we can achieve good results without it. But when we want professional quality, gamma correction becomes a must have feature.

For those that dont know what gamma correction is, i suggest this and this classic wiki page =P

In XNA 3.1 (PC version only) we could use some DirectX 9c instructions to configure the texture sampler to automatically convert the texture from SRGB to Linear space on hardware. We also could set the render surface to be SRGB (convert the output to SRGB), so the gamma correction pipeline was pretty simple and fully done in hardware. More informations here.

In XNA 4.0 we cant use these “configurations” anymore (cause it is not Compatible with Xbox) >.<. The obvious aproach is to make all the convertions on the shaders but it is slow and involves changing some shaders.

My idea is to shift some of the work to the pre process phase and minimize changes to the existing code. The idea is:

  • In a pre process phase, we convert the textures (2D and cubemaps) to the linear space and create the mipmaps in this space
  • Use those converted textures in shaders calculations
  • After all processing, we apply a post processing to convert the colors from the Linear space to the SRGB

The problems with this aproach are:

  • We use the format 10R10B10G2A (constant alpha) or 8R8G8B8A (variable alpha) to store the linear space. Sometimes we can have precision problems. I tryied using float point texture BUT the XNA 4.0 does not have filtering in these textures. We would have to use Pointer filter in all textures and this is not acceptable.
  • We still applying Blending and Multisample in SRGB space, this is theoricaly wrong

The results i got is far better than not using Gamma Correction, so i suggest you to use it =P.

Pipeline Processor

I implemented two pipeline processors (one for the texture2D and other for textureCube). You can choose the output format (8R8G8B8A or 10R10G10B2A).

The code is not optimized, i just wanted it to work =P

The first one is the Texture Processor for the Texture Cube and the next is for the 2D Textures

TextureCube

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using System.ComponentModel;
namespace ContentLibrary
{

    [ContentProcessor(DisplayName = "Gamma Decode TextureCube Processor")]
    class ContentProcessor2 : ContentProcessor
    {

        [DisplayName("Encode to SRGB after mipmap")]
        [DefaultValue(false)]
        public bool EncodeAfter
        {
            get;
            set;
        }

        public override TextureCubeContent Process(TextureCubeContent input, ContentProcessorContext context)
        {
//            System.Diagnostics.Debugger.Launch();
            TextureCubeContent tc = new TextureCubeContent();
            tc.Name = input.Name;
            tc.Identity = input.Identity;
            int i = 0;
            foreach (var item in input.Faces)
            {
                PixelBitmapContent bmpInput = (PixelBitmapContent)item[0];

                // Create Intermediate Content
                Texture2DContent texMipMap = new Texture2DContent();

                // Add decoded Vector4
                texMipMap.Mipmaps.Add(Decode2(bmpInput));

                // Generate Mip Maps
                texMipMap.GenerateMipmaps(true);
                MipmapChain mc = new MipmapChain();
                // Convert each bitmap to Gamma Encoded SurfaceFormat.Color
                for (int mi = 0; mi < texMipMap.Mipmaps.Count; mi++)
                {
                    // Get Mip Map
                    PixelBitmapContent bmpMipMap = (PixelBitmapContent)texMipMap.Mipmaps[mi];
                    if (EncodeAfter)
                    {
                        PixelBitmapContent bmpColor = Encode2(bmpMipMap);
                        mc.Add(bmpColor);
                    }
                    else
                    {
                        mc.Add(bmpMipMap);
                    }
                }                

                tc.Faces[i++] = mc;
            }

         return tc;
        }

        PixelBitmapContent Encode2(PixelBitmapContent bmpMipMap)
        {
            // Create Color Bitmap of Equal Size
            PixelBitmapContent bmpColor = new PixelBitmapContent(bmpMipMap.Width, bmpMipMap.Height);

            // Convert each pixel to gamma encoded color
            for (int y = 0; y < bmpMipMap.Height; y++)
            {

                for (int x = 0; x < bmpMipMap.Width; x++)
                {

                    // Get Input Pixel
                    Rgba1010102 CmipMap = bmpMipMap.GetPixel(x, y);

                    // Set Output Pixel
                    bmpColor.SetPixel(x, y, GammaEncodeColor2(CmipMap.ToVector4()));

                }//for (x)

            }//for (y)

            return bmpColor;

        }//method

        public PixelBitmapContent Decode2(PixelBitmapContent bmpInput)
        {
            // Decoded Bitmap
            PixelBitmapContent bmpDecoded = new PixelBitmapContent(bmpInput.Width, bmpInput.Height);

            // Convert each pixel to gamma decoded float
            for (int y = 0; y < bmpInput.Height; y++)
            {

                for (int x = 0; x < bmpInput.Width; x++)
                {

                    // Get Input Pixel
                    Color Cinput = bmpInput.GetPixel(x, y);

                    // Set Output Pixel
                    bmpDecoded.SetPixel(x, y, GammaDecodeColor2(Cinput.ToVector4()));

                }//for (x)

            }//for (y)

            return bmpDecoded;

        }

        Rgba1010102 GammaEncodeColor2(Vector4 vec)
        {
            Vector4 resp = new Vector4(1);
            resp.X = (float)Math.Pow(vec.X, 1.0 / 2.2);
            resp.Y = (float)Math.Pow(vec.Y, 1.0 / 2.2);
            resp.Z = (float)Math.Pow(vec.Z, 1.0 / 2.2);
            return new Rgba1010102(resp); ;
        }

        static Rgba1010102 GammaDecodeColor2(Vector4 vec)
        {
            Vector4 resp = new Vector4(1);
            resp.X = (float)Math.Pow(vec.X, 2.2);
            resp.Y = (float)Math.Pow(vec.Y, 2.2);
            resp.Z = (float)Math.Pow(vec.Z, 2.2);
            return new Rgba1010102(resp);
        }

    }//class
    // - - - - - - - - - - - - - - - - - - - -
}//namespace
// - - - - - - - - - - - - - - - - - - - -

Texture2D

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using System.ComponentModel;
namespace ContentLibrary
{

    [ContentProcessor(DisplayName = "Gamma Decode Texture Processor")]
    class GameTextureProcessor : ContentProcessor
    {

        [DisplayName("Encode to SRGB after mipmap")]
        [DefaultValue(false)]
        public bool EncodeAfter
        {
            get;
            set;
        }

        [DisplayName("Use RGBA10")]
        [DefaultValue(false)]
        public bool UseRGBA10
        {
            get;
            set;
        }

        // - - - - - - - - - - - - - - - - - - - -
        public override Texture2DContent Process(Texture2DContent texInput, ContentProcessorContext context)
        {

            //System.Diagnostics.Debugger.Launch();

            // Input Bitmap
            PixelBitmapContent bmpInput = (PixelBitmapContent)texInput.Mipmaps[0];
            if (!UseRGBA10)
            {
                // Gamma Decode
                PixelBitmapContent bmpDecoded = Decode(bmpInput);

                // Create Intermediate Content
                Texture2DContent texMipMap = new Texture2DContent();

                // Add decoded Vector4
                texMipMap.Mipmaps.Add(bmpDecoded);

                // Generate Mip Maps
                texMipMap.GenerateMipmaps(true);

                // Create Output Content
                Texture2DContent texOutput = new Texture2DContent();

                // Passthrough Properties
                texOutput.Name = texInput.Name;
                texOutput.Identity = texInput.Identity;

                // Convert each bitmap to Gamma Encoded SurfaceFormat.Color
                for (int mi = 0; mi < texMipMap.Mipmaps.Count; mi++)
                {

                    // Get Mip Map
                    PixelBitmapContent bmpMipMap = (PixelBitmapContent)texMipMap.Mipmaps[mi];

                    // Create Color Bitmap of Equal Size
                    if (EncodeAfter)
                    {
                        PixelBitmapContent bmpColor = Encode(bmpMipMap);
                        texOutput.Mipmaps.Add(bmpColor);
                    }
                    else
                    {
                        texOutput.Mipmaps.Add(bmpMipMap);
                    }

                }//for

                // Return Gamma Encoded Texture with Linear Filtered Mip Maps
                return texOutput;
            }
            else
            {
                // Gamma Decode
                PixelBitmapContent bmpDecoded = Decode2(bmpInput);

                // Create Intermediate Content
                Texture2DContent texMipMap = new Texture2DContent();

                // Add decoded Vector4
                texMipMap.Mipmaps.Add(bmpDecoded);

                // Generate Mip Maps
                texMipMap.GenerateMipmaps(true);

                // Create Output Content
                Texture2DContent texOutput = new Texture2DContent();

                // Passthrough Properties
                texOutput.Name = texInput.Name;
                texOutput.Identity = texInput.Identity;

                // Convert each bitmap to Gamma Encoded SurfaceFormat.Color
                for (int mi = 0; mi < texMipMap.Mipmaps.Count; mi++)
                {

                    // Get Mip Map
                    PixelBitmapContent bmpMipMap = (PixelBitmapContent)texMipMap.Mipmaps[mi];

                    // Create Color Bitmap of Equal Size
                    if (EncodeAfter)
                    {
                        PixelBitmapContent bmpColor = Encode2(bmpMipMap);
                        texOutput.Mipmaps.Add(bmpColor);
                    }
                    else
                    {
                        texOutput.Mipmaps.Add(bmpMipMap);
                    }

                }//for

                // Return Gamma Encoded Texture with Linear Filtered Mip Maps
                return texOutput;
            }

        }//method
        // - - - - - - - - - - - - - - - - - - - -

        public PixelBitmapContent Decode2(PixelBitmapContent bmpInput)
        {
            // Decoded Bitmap
            PixelBitmapContent bmpDecoded = new PixelBitmapContent(bmpInput.Width, bmpInput.Height);

            // Convert each pixel to gamma decoded float
            for (int y = 0; y < bmpInput.Height; y++)
            {

                for (int x = 0; x < bmpInput.Width; x++)
                {

                    // Get Input Pixel
                    Color Cinput = bmpInput.GetPixel(x, y);

                    // Set Output Pixel
                    bmpDecoded.SetPixel(x, y, GammaDecodeColor2(Cinput.ToVector4()));

                }//for (x)

            }//for (y)

            return bmpDecoded;

        }
        PixelBitmapContent Decode(PixelBitmapContent bmpInput)
        {

            // Decoded Bitmap
            PixelBitmapContent bmpDecoded = new PixelBitmapContent(bmpInput.Width, bmpInput.Height);            

            // Convert each pixel to gamma decoded float
            for (int y = 0; y < bmpInput.Height; y++)
            {

                for (int x = 0; x < bmpInput.Width; x++)
                {

                    // Get Input Pixel
                    Color Cinput = bmpInput.GetPixel(x, y);

                    // Set Output Pixel
                    bmpDecoded.SetPixel(x, y, GammaDecodeColor(Cinput.ToVector4()));

                }//for (x)

            }//for (y)

            return bmpDecoded;

        }//method
        // - - - - - - - - - - - - - - - - - - - -
        PixelBitmapContent Encode(PixelBitmapContent bmpMipMap)
        {
            // Create Color Bitmap of Equal Size
            PixelBitmapContent bmpColor = new PixelBitmapContent(bmpMipMap.Width, bmpMipMap.Height);

            // Convert each pixel to gamma encoded color
            for (int y = 0; y < bmpMipMap.Height; y++)
            {

                for (int x = 0; x < bmpMipMap.Width; x++)
                {

                    // Get Input Pixel
                    Color CmipMap = bmpMipMap.GetPixel(x, y);                    

                    // Set Output Pixel
                    bmpColor.SetPixel(x, y, GammaEncodeColor(CmipMap.ToVector4()));

                }//for (x)

            }//for (y)

            return bmpColor;

        }//method

        PixelBitmapContent Encode2(PixelBitmapContent bmpMipMap)
        {
            // Create Color Bitmap of Equal Size
            PixelBitmapContent bmpColor = new PixelBitmapContent(bmpMipMap.Width, bmpMipMap.Height);

            // Convert each pixel to gamma encoded color
            for (int y = 0; y < bmpMipMap.Height; y++)
            {

                for (int x = 0; x < bmpMipMap.Width; x++)
                {

                    // Get Input Pixel
                    Rgba1010102 CmipMap = bmpMipMap.GetPixel(x, y);

                    // Set Output Pixel
                    bmpColor.SetPixel(x, y, GammaEncodeColor2(CmipMap.ToVector4()));

                }//for (x)

            }//for (y)

            return bmpColor;

        }//method        

        Color GammaEncodeColor(Vector4 vec)
        {
            Vector4 resp = new Vector4(1);
            resp.X = (float)Math.Pow(vec.X, 1.0 / 2.2);
            resp.Y = (float)Math.Pow(vec.Y, 1.0 / 2.2);
            resp.Z = (float)Math.Pow(vec.Z, 1.0 / 2.2);
            return Color.FromNonPremultiplied(resp); ;
        }

        Rgba1010102 GammaEncodeColor2(Vector4 vec)
        {
            Vector4 resp = new Vector4(1);
            resp.X = (float)Math.Pow(vec.X, 1.0 / 2.2);
            resp.Y = (float)Math.Pow(vec.Y, 1.0 / 2.2);
            resp.Z = (float)Math.Pow(vec.Z, 1.0 / 2.2);
            return new Rgba1010102(resp); ;
        }

        Color GammaDecodeColor(Vector4 vec)
        {
            Vector4 resp = new Vector4(1);
            resp.X = (float)Math.Pow(vec.X, 2.2);
            resp.Y = (float)Math.Pow(vec.Y, 2.2);
            resp.Z = (float)Math.Pow(vec.Z, 2.2);
            return Color.FromNonPremultiplied(resp); ;
        }

        Rgba1010102 GammaDecodeColor2(Vector4 vec)
        {
            Vector4 resp = new Vector4(1);
            resp.X = (float)Math.Pow(vec.X, 2.2);
            resp.Y = (float)Math.Pow(vec.Y, 2.2);
            resp.Z = (float)Math.Pow(vec.Z, 2.2);
            return new Rgba1010102(resp);
        }

        // - - - - - - - - - - - - - - - - - - - -
    }//class
    // - - - - - - - - - - - - - - - - - - - -
}//namespace
// - - - - - - - - - - - - - - - - - - - -

The code is very simple and easy to follow =P
The next stage is the post process that converts the Linear space to the SRGB. It is pretty simple and is listed below:

float4 PixelShader1( float2 Tex : TEXCOORD ) : COLOR0
{
float3 Color =  tex2D(baseSampler, Tex);
Color = pow(Color, 1.0/2.2));
return float4(Color,1);
}

Just applying the formula =P
Below you can see a simple screenshot with the diference of using and not using Gamma correction

This scene uses simple illumination, the first picture in this post shows the huge difference between using and not using gamma correction (the(a) picture has gamma correction).

I used the aproach i explained before in the PloobsEngine, and we good good results =P

A good source of information about this matter is this article from GPU Gems.

Special thanks to skytiger and his post about this same trouble.

Its all for today. =P

, , , , , , , ,

  1. #1 by stallholder insurance on 27 de maio de 2017 - 12:48 pm

    Nothing I could say would give you undue credit for this story.

  2. #2 by http://fertilitetsradgivningen.se/formular/75.html on 27 de maio de 2017 - 1:06 pm

    I got our headphonesconcerning the mom of mothers evening, additionally she completely liked that! It really is really attractive headphonesand the suggesting on the card that will goes in box is really sentimental!! And the excellent of beads is ideal!

  3. #3 by http://www.docguide.com/ext_link/?url=http://negociosonlinelucrativos.com.br/ganhar-dinheiro-online-e-verdade-sim-descubra-como/ on 27 de maio de 2017 - 1:15 pm

    Se você não reunir certeza de que você tem material suficiente para redigir,
    tente grafar artigos com diversos títulos anterior de produzir blog.

  4. #4 by 168friend.com on 27 de maio de 2017 - 1:41 pm

    This is really interesting, You’re a very skilled blogger.

    I’ve joined your rss feed and look forward to seeking more of your great post.
    Also, I have shared your website in my social networks!

  5. #5 by cheap louboutin shoes replica on 27 de maio de 2017 - 1:57 pm

    it comes down within a striking box seems ideal still the slightly little additionally for the parents wrist however it appearances great only wish that it had been much longer

  6. #6 by http://rogerspallet.com/soapstone/87.html on 27 de maio de 2017 - 4:17 pm

    Yet not because fancy when real life in case mothers wrist is actually never 2small dont go for that it cause that get tight .. their attractive still things yur 5year aged make . No that the not so its one ok gift in order to provide yet not the wow gifts…cant go wrong in case do not need a great deal to spend on a gift therefore take that

  7. #7 by http://www.dhaninvestment.com/power/65.html on 27 de maio de 2017 - 4:20 pm

    Ideal headphonespurchase, nicely listed furthermore what is actually displayed. Perfect gifts package as well as poem additionally enclosed. Ideal concerning mother’s time!

  8. #8 by http://orestadsbygg.se/bilder_ref/509.html on 27 de maio de 2017 - 4:21 pm

    The son provided myself it to parents time. He understands im not up to price however exactly what originates from ones heart. I can’t believe this has my favourite colors as well as even matches our wrist. I do not could see bracelets to suit headphonesour tiny wrist. Most happy to own recieved our as being a gift.

  9. #9 by prime media car insurance contact details on 27 de maio de 2017 - 7:43 pm

    1. Definitivamente habrá un suceso estelar, los mayas eran astrónomos muy precisos, así que esperemos a ver qué nos deparan los astros!2. La colección fusion green me encanta, el uso de los tonos y texturas, chi-le-ro!

  10. #10 by Divorce Attorney on 27 de maio de 2017 - 9:35 pm

    we prefer to honor numerous other internet web-sites on the web, even though they arent linked to us, by linking to them. Beneath are some webpages worth checking out

  11. #11 by http://www.hexpilot.com/help_hermes.aspx on 27 de maio de 2017 - 10:26 pm

    I have this gifts to my mom for Christmas time due she is your precious jewelry freak. Their one thing she cannot wear a lot of is actually, bracelets. I purchased the lady this particular allure bracelet and also once she opened that it yesterday she absolutley liked that! Nowadays the problem is, this girl acquiring they on top of and off with herself. Haha… in general awesome goods, that shipped and appeared completely early and also my personal mom are enjoying information technology. Thank we.

  12. #12 by gsa ser tier on 27 de maio de 2017 - 10:26 pm

    I’m not sure where you’re getting your info, but good topic.
    I needs to spend some time learning more or understanding more.
    Thanks for magnificent info I was looking for this information for my mission.

  13. #13 by http://www.bharatdefencekavach.com/flash/article/66.html on 27 de maio de 2017 - 11:26 pm

    Purchased this one as being a gift concerning my mother and also she loved it. nice high quality awesome pricing plus the mother loved that it. Additionally delivered super quick. When you require a present fast and you such as this than this is single you ought to go for!

  14. #14 by http://kristengemenskap.com/templates/16309/469.html on 27 de maio de 2017 - 11:27 pm

    Yet not since fancy at real life when mothers wrist is actually not really 2small do not go for they result they feel tight .. its attractive although one thing yur 5year old make . Non some sort of less its a great okay gifts to award yet not the best wow present…cant get wrong if you think dont have a great deal to spend for a gifts well have that

  15. #15 by cl10c470jb8nnnc on 28 de maio de 2017 - 4:13 am

    Hi there, just became aware of your blog through Google, and found that it’s truly informative. I am going to watch out for brussels. I will be grateful if you continue this in future. A lot of people will be benefited from your writing. Cheers!

  16. #16 by http://citysecurity.nu/flash/old/79.html on 28 de maio de 2017 - 4:24 am

    I had gotten our headphonesto the mother for the mothers day, as well as she completely loved this! The completely cute headphonesand stating in the card it comes inside package is really emotional!! And the good of the beads was ideal!

  17. #17 by http://news.mensandals.xyz/mensandalsxyz/136.asp on 28 de maio de 2017 - 4:25 am

    I have your headphonesconcerning my personal mom for the moms day, to she completely adored this! It is quite pretty headphonesand the stating on the card your will come in the box is very emotional!! And good of beads is great!

  18. #18 by minimum car insurance in pa on 28 de maio de 2017 - 4:27 am

    That’s an inventive answer to an interesting question

  19. #19 by http://news.vancleefreplica.win/vancleefreplicawin/598.asp on 28 de maio de 2017 - 6:49 am

    Yet not as fancy in real life whether mothers wrist is actually not really 2small do not purchase information technology cause things try to be tight .. it is cute but anything yur 5year older make . No some sort of lower its one ok present towards provide but not per wow present…cant go wrong should do not get a lot to blow on a gifts therefore attain that it

  20. #20 by http://news.christianlouboutinreplica.pw/christianlouboutinreplicapw/591.asp on 28 de maio de 2017 - 6:50 am

    Bought that as being a present of the mother and also she liked it. sweet good kind cost and/or our mom liked that it. Always sent ultra fast. So if you require a present fast while like this than this is the someone it is best to choose!

  21. #21 by frequently asked questions about insurance on 28 de maio de 2017 - 6:58 am

    What a pleasure to meet someone who thinks so clearly

  22. #22 by http://news.portside.xyz/portsidexyz/504.asp on 28 de maio de 2017 - 7:17 am

    My boy offered me personally it towards parents evening. He realizes im not really about price although what else comes from all center. I cannot know this has our favourite colors and/or really matches the wrist. I not could get a hold of bracelets to suit headphonesmy small wrist. Quite happy to come with recieved this particular as being a gift.

  23. #23 by http://news.cartierlovebraceletreplica.xyz/cartierlovebraceletreplicaxyz/357.asp on 28 de maio de 2017 - 8:51 am

    But not like fancy inside real life should moms wrist was not 2small do not pick information technology result things feel tight .. it is adorable but some thing yur 5year old makes . Non all reduced its one okay gift towards present yet not your wow gift…cant get wrong when don’t have a lot to spend on a gifts subsequently bring information technology

  24. #24 by http://news.cartierreplica.pw/cartierreplicapw/604.asp on 28 de maio de 2017 - 8:52 am

    Yet not just as fancy inside real world provided moms wrist is never 2small do not get information technology result information technology stay tight .. it really is cute however whatever yur 5year old can make . No that the much less it really is a ok gifts towards offer yet not one wow gift…cant go wrong when don’t posses a lot to blow on a gift well buy that it

  25. #25 by http://www.goodheartresource.com/blackpumps.asp on 28 de maio de 2017 - 10:03 am

    I got this gifts for the my mom concerning Holiday because she is actually per jewelry freak. The actual one thing she cannot don much regarding was, bracelets. I purchased this girl this appeal bracelet then after she exposed it yesterday she absolutley liked that it! This time the issue is, this girl getting that it on furthermore off simply by by herself. Haha… on the whole awesome goods, information technology transported and/or appeared massively early and also our mom was enjoying it. Thank your.

  26. #26 by http://www.ljusihus.se/cartierlove.aspx on 28 de maio de 2017 - 10:04 am

    it comes in a beautiful package styles ideal however the slightly tiny including towards my personal mothers wrist however it appearances great only wish things ended up being extended

  27. #27 by affordable insurance for college students on 28 de maio de 2017 - 10:08 am

    Thanks for contributing. It’s helped me understand the issues.

  28. #28 by auto insurance endorsements on 28 de maio de 2017 - 10:38 am

    p. 130Two typos (one if you’re British 😉 ):”When the nasty nerd and the glamour guy ran against each other in 1960, they would up in a near dead heat.”

  29. #29 by www.tender247.com on 28 de maio de 2017 - 10:40 am

    You should take part in a contest for one of the best sites on the internet. I most certainly will highly recommend this web site!|

1 443 444 445
(não será publicado)