XNA Content Pipeline Gamma Correct Mip Map Texture Processor

By default the XNA Content Pipeline generates mip maps without any gamma decode / encode.

This creates a small error in the final textures.

I wrote this Texture Processor that performs the gamma decode and encode with 32 bit float precision:

// - - - - - - - - - - - - - - - - - - - -
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;
// - - - - - - - - - - - - - - - - - - - -
namespace ContentLibrary {
	// - - - - - - - - - - - - - - - - - - - -
	[ContentProcessor(DisplayName = "Game Texture Processor")]
	class GameTextureProcessor : ContentProcessor<Texture2DContent, Texture2DContent> {
		// - - - - - - - - - - - - - - - - - - - -
		public override Texture2DContent Process(Texture2DContent texInput, ContentProcessorContext context) {

			//System.Diagnostics.Debugger.Launch();
			
			// Input Bitmap
			PixelBitmapContent<Color> bmpInput = (PixelBitmapContent<Color>)texInput.Mipmaps[0];

			// Gamma Decode
			PixelBitmapContent<Vector4> 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<Vector4> bmpMipMap = (PixelBitmapContent<Vector4>)texMipMap.Mipmaps[mi];

				// Create Color Bitmap of Equal Size
				PixelBitmapContent<Color> bmpColor = Encode(bmpMipMap);

				// Add Mip Map to Final Output
				texOutput.Mipmaps.Add(bmpColor);

			}//for

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

		}//method
		// - - - - - - - - - - - - - - - - - - - -
		PixelBitmapContent<Vector4> Decode(PixelBitmapContent<Color> bmpInput) {

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

			Vector4 Crgb;

			// 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);

					// Gamma Decode RGB
					Crgb.X = GammaDecodeByte(Cinput.R);
					Crgb.Y = GammaDecodeByte(Cinput.G);
					Crgb.Z = GammaDecodeByte(Cinput.B);

					// Normalize Alpha // No Gamma Decoding
					Crgb.W = NormalizeByte(Cinput.A);

					// Set Output Pixel
					bmpDecoded.SetPixel(x, y, Crgb);

				}//for (x)

			}//for (y)

			return bmpDecoded;

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

			Color Coutput = new Color();

			// 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
					Vector4 CmipMap = bmpMipMap.GetPixel(x, y);

					// Gamma Encode RGB
					Coutput.R = GammaEncodeFloat(CmipMap.X);
					Coutput.G = GammaEncodeFloat(CmipMap.Y);
					Coutput.B = GammaEncodeFloat(CmipMap.Z);

					// Normalize Alpha // No Gamma Encoding
					Coutput.A = ConvertFloat(CmipMap.W);

					// Set Output Pixel
					bmpColor.SetPixel(x, y, Coutput);

				}//for (x)

			}//for (y)

			return bmpColor;

		}//method
		// - - - - - - - - - - - - - - - - - - - -
		byte ConvertFloat(float val) {
			return (byte)(val * 255.0f);
		}//method
		// - - - - - - - - - - - - - - - - - - - -
		byte GammaEncodeFloat(float val) {
			double encoded = Math.Pow(val, 1.0 / 2.2);
			double expanded = encoded * 255.0;
			return (byte)expanded;
		}//method
		// - - - - - - - - - - - - - - - - - - - -
		float NormalizeByte(byte val) {
			float unit = (float)val / 255.0f;
			return unit;
		}//method
		// - - - - - - - - - - - - - - - - - - - -
		float GammaDecodeByte(byte val) {
			double unit = (double)val / 255.0;
			double decode = Math.Pow(unit, 2.2);
			return (float)decode;
		}//method
		// - - - - - - - - - - - - - - - - - - - -
	}//class
	// - - - - - - - - - - - - - - - - - - - -
}//namespace
// - - - - - - - - - - - - - - - - - - - -

As there is no way of performing hardware gamma decode on the X360 – this problem will be repeated when the texture is filtered.

I also tried converting gamma encoded RGB8 to linear RGB10A2.
This would allow linear filtering of linear colour on the X360.
But the precision is still not good, and I feel the quality is even lower.

On the X360 you have to choose the lesser of two evils:

a) Linear filtering of Gamma Encoded color
b) Precision issues with Linear color stored in 10 bits

Advertisements

4 Responses to “XNA Content Pipeline Gamma Correct Mip Map Texture Processor”

  1. Thiago Dias Pastor Says:

    i tryed only using the deconding part and sending the texturr in the linear space to xna (use it in the shaders and only converto to srgb in the last post proccess)
    worked but i lost the harware linear filtering …. and this is a luxury that i cant loose….
    cant see a good solution for gamma corrected pipeline in XNA …
    the only way is using encode/decode in shader (errors in blending, filtering, multisample …….)

    Do you have any succes in this part ?

  2. Thiago Dias Pastor Says:

    I extended your code and created a processor that decodes textures and cubemaps to rgba10 bits =P
    The results are not perfect but are far better than using SRGB colors directely in my shaders =P


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: