XNA Shader Programming – Tutorial 11, Post process Grayscale

XNA Shader Programming
Tutorial 11 – Post process, Grayscale
This tutorial builds on Tutorial 9, post process wiggling. If you haven’t read that yet, you should in order to fully understand what’s going on here.
 
The source-code and binaries can be found in the end of the chapter.
 
Grayscale Image
In order to make a black and white image/scene, we will need to convert our colored SceneTexture to a nagative one. This is done in a post process pixel shader.
A black and white image consist of only pixels with a shade of gray, so our shader must somehow take a color, and make it to a shade of gray. There is a few ways of doing this, and Im going to show you two of them.
 
1. Average color
One way of doing this is to take the three color channels, add them together and devide the result on 3. This gives us the average color, and we set all components of our color to this average color:

Color.rgb = (Color.r + Color.g + Color.b)/3;

This will result in a pixel with all components set to the same value. It gives a pretty good result, but if you look closely, it’s incorrect! Let’s take a look on an other method of doing this.

2. Grayscale intensity
The eye is more sensitive to one color component, green, than towards the others and so on. One of the most common ways of converting an image to grayscale is to use a set of values that describe the grayscales intensity/weight for each of the three color components:
g = 0.3 R + 0.59 G + 0.11 B
 
This will convert our grayscale image correctly, and can be implemented in the shader like this:

Color.rgb = dot(Color.rgb, float3(0.3, 0.59, 0.11));

I have added this figure as a comparison on the two different grayscale methods. It’s a little hard to see in this example but it can be seen in a more colorful scene, or one that contains more green.

Implementing the shader

We have seen how to convert a color to grayscale. I’v also added the shaders for both methods, so you can see the whole picture without having to download the example.

 

1. Average color

sampler ColorMapSampler : register(s0);

float4 PixelShader(float2 Tex: TEXCOORD0) : COLOR
{
 float4 Color = tex2D(ColorMapSampler, Tex); 
 
 Color.rgb = (Color.r + Color.g + Color.b)/3;

 // Keep our alphachannel at 1.
 Color.a = 1.0f;
  
    return Color;
}

technique PostProcess
{
 pass P0
 {
  PixelShader = compile ps_2_0 PixelShader();
 }
}

 

2. Grayscale intensity

sampler ColorMapSampler : register(s0);

float4 PixelShader(float2 Tex: TEXCOORD0) : COLOR
{
 float4 Color = tex2D(ColorMapSampler, Tex); 
 
 Color.rgb = dot(Color.rgb, float3(0.3, 0.59, 0.11));

 // Keep our alphachannel at 1.
 Color.a = 1.0f;
  
    return Color;
}

technique PostProcess
{
 pass P0
 {
  PixelShader = compile ps_2_0 PixelShader();
 }
}

NOTE:
You might have noticed that I have not used effect.commitChanges(); in this code. If you are rendering many objects using this shader, you should add this code in the pass.Begin() part so the changed will get affected in the current pass, and not in the next pass. This should be done if you set any shader paramteres inside the pass.

Thats about it, if you have any inputs on this one, please leave a comment, or send me an e-mail on petriw(at)gmail(dot)com

YouTube – XNA Shader programming, Tutorial 11 – Grayscale postprocess
  

This entry was posted in XNA Shader Tutorial. Bookmark the permalink.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.