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 car insurance Williston ND on 16 de janeiro de 2017 - 10:21 am

    This is what we need – an insight to make everyone think

  2. #2 by ugg boots outlet online on 16 de janeiro de 2017 - 10:43 am

    Im trying to figure out how to subscribe to the RSS feeds, but dont seem to figure it out. Some help would be great Thank you
    ugg boots outlet online http://www.angigreene.com/boots-online/

  3. #3 by beton imprime on 16 de janeiro de 2017 - 11:25 am

    Merely wanna input that you have a very decent internet site, I love the design it really stands out.

  4. #4 by ugg boots sale on 16 de janeiro de 2017 - 11:49 am

    Well , the view of the passage is totally correct ,your details is really reasonable and you guy give us valuable informative post
    ugg boots sale http://www.tmearegion26.com/ugg/

  5. #5 by Adidas Originals NMD Runner Primeknit Sneakers Blue Orange on 16 de janeiro de 2017 - 12:02 pm

    I together with my guys have already been examining the best tips on your web blog and then unexpectedly got a terrible feeling I never expressed respect to you for those techniques. All of the people are already for that reason stimulated to learn them and have now quite simply been taking pleasure in these things. I appreciate you for being simply thoughtful as well as for considering some decent ideas most people are really desperate to be aware of. Our own sincere regret for not expressing gratitude to earlier.

  6. #6 by beton imprime on 16 de janeiro de 2017 - 12:37 pm

    Just about all of whatever you articulate happens to be supprisingly accurate and it makes me wonder the reason why I hadn’t looked at this with this light before. This particular piece truly did switch the light on for me as far as this subject goes. Nonetheless there is actually 1 point I am not really too cozy with so while I attempt to reconcile that with the actual core idea of the point, permit me see just what all the rest of the readers have to point out.Well done.

  7. #7 by jordan 12 low orange for sale on 16 de janeiro de 2017 - 12:43 pm

  8. #8 by dkny kids on 16 de janeiro de 2017 - 12:46 pm

    I like your post 3) Ill surely be peeping into it again soon! ;>
    dkny kids http://www.dknyoutlet.online

  9. #9 by patagonia outlet dillon on 16 de janeiro de 2017 - 1:49 pm

    Hey thats a very nice post you got there, Ill be checking back sometimes for more updates thx
    patagonia outlet dillon http://www.easyinboxmailer.com/patagonia/

  10. #10 by GEICO insurance san diego ca on 16 de janeiro de 2017 - 3:35 pm

    You made a number of nice points there. I did a search on the subject and found mainly persons will go along with with your blog.

  11. #11 by michael kors online promo code on 16 de janeiro de 2017 - 3:53 pm

    cheers for this post it has been very informative i hope to see more cool posts from you
    michael kors online promo code http://www.unilorites.com/michael-kors/

  12. #12 by kredite berechnung zinsen insolvenzanmeldung on 16 de janeiro de 2017 - 4:12 pm

    Learning a ton from these neat articles.

  13. #13 by alexander wang handbags on sale on 16 de janeiro de 2017 - 4:55 pm

    Its such as you read my thoughts! You appear to know so much approximately this, such as you wrote the e book in it or something. I feel that you just could do with a few percent to power the message home a little bit, but other than that, that is wonderful blog. A fantastic read. Ill certainly be back.
    alexander wang handbags on sale http://www.newyearoutlet.online

  14. #14 by GEICO insurance estimate on 16 de janeiro de 2017 - 4:58 pm

    Utterly pent content material, Really enjoyed reading.

  15. #15 by http://www.guenstigerkreditonline.net/ on 16 de janeiro de 2017 - 6:29 pm

    Es verdad, como dice mal querida, son animales y actúan como tal. Los humanos debemos estar bien concientes de ellos cuando queramos manipularlos. Saludos, araña.

  16. #16 by car insurance quotes online compare on 16 de janeiro de 2017 - 6:31 pm

    With havin so much written content do you ever run into any problems of plagorism or copyright infringement? My blog has a lot of exclusive content I’ve either written myself or outsourced but it seems a lot of it is popping it up all over the internet without my permission. Do you know any techniques to help stop content from being ripped off? I’d definitely appreciate it.

  17. #17 by http://onlinekreditetestsieger.net/kredit-krefeld-online.html on 16 de janeiro de 2017 - 7:51 pm

    You have the monopoly on useful information-aren’t monopolies illegal? 😉

  18. #18 by coach factory outlet online shopping on 16 de janeiro de 2017 - 9:00 pm

    I admire what you have done here. I like the part where you say you are doing this to give back but I would assume by all the opinions that this is working for you as well.
    coach factory outlet online shopping http://www.lticonstruction.com/coach/

  19. #19 by home and car insurance company RATINGs on 16 de janeiro de 2017 - 9:20 pm

    I’ll immediately clutch your rss feed as I can’t in finding your e-mail subscription hyperlink or e-newsletter service. Do you’ve any? Kindly permit me recognise so that I may subscribe. Thanks.

  20. #20 by car insurance quotes online compare on 16 de janeiro de 2017 - 10:36 pm

    We’ve mistakenly transformed my admin priveleges on one of my blogs to “guest”. I want the blogger people to bring back it. I am unable to find a real email or contact form anywhere, and I have googled every thing, including the Blogger Group on the search engines Groups, where they try to drive everyone exactly who needs support. I would happily pay them for better service..

  21. #21 by valentino sale shoes on 16 de janeiro de 2017 - 10:56 pm

    I added a link to this site on my blog! can you add a link of my blog to your site
    valentino sale shoes http://valentino.compucelunlock.net

  22. #23 by insurance kad kredit bank rakyat on 16 de janeiro de 2017 - 11:14 pm

    I’m really into it, thanks for this great stuff!

  23. #24 by patagonia outlet stores on 17 de janeiro de 2017 - 1:56 am

    important How much time do you spend updating this blog every day? Wow is all I can say. Thanks again.
    patagonia outlet stores http://www.joessmogtestonly.com/patagonia/

  24. #25 by android games for pc on 17 de janeiro de 2017 - 2:00 am

    Heya just wanted to give you a brief heads up and let you know a few of the pictures aren’t loading properly. I’m not sure why but I think its a linking issue. I’ve tried it in two different internet browsers and both show the same outcome.

  25. #26 by http://privaterkreditvergleich.top/kredite.html on 17 de janeiro de 2017 - 2:24 am

    I still drive by our old house every time I’m in my hometown. I’ll probably never get to be inside of it again, but I’ll never forget every little detail about it and the wonderful childhood I had there. I want that for my kids!

  26. #27 by android apps for pc download on 17 de janeiro de 2017 - 2:50 am

    I¦ll right away grab your rss as I can not to find your email subscription link or newsletter service. Do you have any? Kindly let me understand in order that I may subscribe. Thanks.

  27. #28 by vibrator on 17 de janeiro de 2017 - 3:04 am

    always a big fan of linking to bloggers that I adore but really don’t get a good deal of link like from

  28. #29 by http://privaterkreditvergleich.top/schufafrei-kredit-8000-euro-neu.html on 17 de janeiro de 2017 - 3:19 am

    Hola Neus!Nos alegramos de que te haya gustado, como a nosotros tu mesa!Desde luego que es preciosa y eres justa ganadora con todas las maravillas que había.Un saludo

  29. #30 by yeezy boost 350 uk buy on 17 de janeiro de 2017 - 4:00 am

  30. #31 by Zubat Pokemon Go on 17 de janeiro de 2017 - 4:01 am

    Hey, you used to write magnificent, but the last few posts have been kinda boring… I miss your super writings. Past several posts are just a little bit out of track! come on!

  31. #32 by haspa studentenkredit on 17 de janeiro de 2017 - 4:20 am

    Så lenge venstre kun er villig til å jobbe for en sentrum-høyre konstellasjon og ikke andre veien vil dette fortsette.Er venstre et sentrumsparti eller ikke?

  32. #33 by изгодни самолетни билети on 17 de janeiro de 2017 - 4:40 am

    You made some decent points there. I looked on the internet for the issue and found most individuals will go along with with your website.

1 294 295 296
(não será publicado)