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 payless auto insurance Madison AL on 22 de julho de 2017 - 11:38 am

    I hate my life but at least this makes it bearable.

  2. #2 by average car insurance rates in Perth Amboy NJ on 22 de julho de 2017 - 12:42 pm

    Extremely helpful article, please write more.

  3. #3 by geico auto insurance quote on 22 de julho de 2017 - 2:24 pm

    Pretty section of content. I just stumbled upon your website and in accession capital to assert that I get actually enjoyed account your blog posts. Anyway I will be subscribing to your feeds and even I achievement you access consistently fast.

  4. #4 by find here on 22 de julho de 2017 - 3:47 pm

    although sites we backlink to beneath are considerably not related to ours, we feel they may be essentially really worth a go by, so have a look

  5. #5 by hermes bracelet replica on 22 de julho de 2017 - 3:50 pm

    That supplement had been with such a very good rate I did not believe that excellent is and so excellent. It’s breathtaking. Our mama will likely appreciate that upon Xmas early morning whenever she starts gift and it appears like I invested a lot more, but pricing was only great!!

  6. #6 by klicken sie jetzt on 22 de julho de 2017 - 4:49 pm

    beim täglichen link nicht widerstehen können ein bisschen sehr viel sicherer ist.

  7. #7 by click now on 22 de julho de 2017 - 7:10 pm

    usually posts some really interesting stuff like this. If you are new to this site

  8. #8 by browse around this website on 22 de julho de 2017 - 8:49 pm

    we came across a cool site that you could possibly delight in. Take a appear in the event you want

  9. #9 by basics on 22 de julho de 2017 - 10:10 pm

    we came across a cool internet site that you simply might appreciate. Take a appear if you want

  10. #10 by http://www.goodheartresource.com/blackpumps.asp on 22 de julho de 2017 - 10:22 pm

    I have purchased this particular brand regarding bracelet some instances. Every one is extremely pretty, has made well, cannot tarnish plus important depending on that a single you purchase plus whom one award information technology at.

  11. #11 by http://www.gullangrafstrom.se/online.asp on 23 de julho de 2017 - 3:29 am

    I have bought this brand to bracelet a few instances. Every single a person is super adorable, prepared well, does not tarnish furthermore important depending on which kind of an individual you buy plus just who you award information technology to.

  12. #12 by adidas stan smith shoes on 23 de julho de 2017 - 4:18 am

    I really wanted to compose a simple remark so as to thank you for these pleasant solutions you are placing at this website. My considerable internet lookup has at the end been compensated with awesome facts and techniques to exchange with my partners. I would tell you that most of us website visitors actually are truly lucky to exist in a decent network with very many wonderful individuals with great methods. I feel rather fortunate to have discovered the web site and look forward to really more entertaining times reading here. Thank you once more for everything.

  13. #13 by cheap car insurance Hinesville GA on 23 de julho de 2017 - 6:32 am

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

  14. #14 by building 4 link suspension on 23 de julho de 2017 - 7:37 am

    Sites of interest we’ve a link to

  15. #15 by Montres Cartier Replica on 23 de julho de 2017 - 8:22 am

    I’ve purchased that brand of bracelet some circumstances. Every single one is very attractive, established well, does not tarnish and/or important based on which one you buy as well as whom a person render it in order to.

  16. #16 by http://www.amici-thomae-mori.com/fr/louboutinmen.asp on 23 de julho de 2017 - 8:23 am

    Your supplement had been at such a very good pricing I never believe that premium would be and so exceptional. Its beautiful. Our mom will certainly love they at Holiday early morning whenever she starts gifts therefore looks like I devoted more, although pricing is only awesome!!

  17. #17 by polythene rolls on 23 de julho de 2017 - 8:24 am

    here are some hyperlinks to sites that we link to simply because we assume they may be really worth visiting

  18. #18 by cheap nike roshe run on 23 de julho de 2017 - 10:53 am

    Your product had been at such a very good cost I never ever thought all grade would be so that exceptional. It’s breathtaking. This mother might enjoy this on top of Holiday morning when she opens gifts and it appears as though I devoted way more, however rates is really ideal!!

  19. #19 by antislip tape on 23 de julho de 2017 - 12:33 pm

    The information mentioned within the write-up are a number of the most effective offered

  20. #20 by Serambigayo.com on 23 de julho de 2017 - 12:56 pm

    But what is important is to pick something which is comfortable to wear.
    Unfortunately, many items that happen to be produced in the past don’t work.
    Men with minimal gynecomastia are allowed to resume vigorous exercise about a
    fortnight postoperatively, while those undergoing more extensive procedures are needed to wait up to four weeks.

  21. #21 by http://www.ja-trax.com/fr/soledshoes.asp on 23 de julho de 2017 - 1:42 pm

    I have purchased this particular brand of bracelet some period. Every single one is very adorable, has made well, does not tarnish furthermore meaningful based on what one you buy plus who shoppers give information technology to.

  22. #22 by low income auto insurance dmv Greensburg PA on 23 de julho de 2017 - 1:53 pm

    I’m quite pleased with the information in this one. TY!

  23. #23 by see here now on 23 de julho de 2017 - 2:32 pm

    here are some hyperlinks to web-sites that we link to since we assume they may be really worth visiting

  24. #24 by temporary site protection on 23 de julho de 2017 - 5:16 pm

    please visit the web sites we comply with, such as this one particular, as it represents our picks through the web

  25. #25 by car insurance with no license in Petersburg VA on 23 de julho de 2017 - 6:17 pm

    Your cranium must be protecting some very valuable brains.

  26. #26 by Mississippi vacation rentals on 23 de julho de 2017 - 6:32 pm

    Here are a few of the web-sites we suggest for our visitors

  27. #27 by best auto insurance in Wheaton IL on 23 de julho de 2017 - 6:55 pm

    I’m not quite sure how to say this; you made it extremely easy for me!

  28. #28 by Pet friendly Biloxi Rentals on 23 de julho de 2017 - 7:03 pm

    although internet websites we backlink to below are considerably not related to ours, we feel they may be essentially worth a go via, so have a look

  29. #29 by Southwind Townhomes on 23 de julho de 2017 - 7:30 pm

    here are some links to websites that we link to for the reason that we feel they’re worth visiting

1 488 489 490
(não será publicado)