tags:

views:

84

answers:

3

In XNA, how can I achieve the same effect of applying a System.Drawing.Imaging.ColorMatrix? The following article shows how I would like to render my sprites, but it uses GDI+:

http://www.c-sharpcorner.com/UploadFile/mahesh/Transformations0512192005050129AM/Transformations05.aspx

How can this be done in XNA? Is there a general purpose shader I can use? Any help would be aprreciated.

+1  A: 

You can use a custom pixel shader to do this kind of color manipulation very easily. For example, take a pixel shader like this:

sampler2D sampler;

float4 PixShader( float2 tex : TEXCOORD0 ) : COLOR0
{
    float4 color;
    color = tex2D( sampler, tex);

    //do anything you want to the colors
    color.r = color.r * 2; // intensify the red component
    color.g = color.g * 0.5; // cut all green values in half
    // etc.

    return color;
}

And using custom shaders with the SpriteBatch class is oh so much easier in XNA 4.0:
SpriteBatch and custom shaders in XNA Game Studio 4.0 by Shawn Hargreaves

Joel Martinez
+1  A: 

Here's a solution that works with Silverlight and WPF:

sampler2D input : register(s0);

/// <defaultValue>1.0</defaultValue>
float RMul : register(C0);
/// <defaultValue>1.0</defaultValue>
float GMul : register(C1);
/// <defaultValue>1.0</defaultValue>
float BMul : register(C2);
/// <defaultValue>1.0</defaultValue>
float AMul : register(C3);

/// <defaultValue>0</defaultValue>
float RAdd : register(C4);
/// <defaultValue>0</defaultValue>
float GAdd : register(C5);
/// <defaultValue>0</defaultValue>
float BAdd : register(C6);
/// <defaultValue>0</defaultValue>
float AAdd : register(C7);

float4 main(float2 uv : TEXCOORD) : COLOR 
{
   float4 color = tex2D(input, uv);
   color = color.rgba * float4(RMul, GMul, BMul, AMul) + float4(RAdd, GAdd, BAdd, AAdd);
   return color;
}

I'll give the answer to the first person that tweaks it to work on XNA.

Charles
+2  A: 

OK, based on your code (here), which is not quite the same as ColorMatrix (but this should give you a good idea of how to add the full matrix functionality should you desire), here is a version that will work on XNA:

First of all, download the Sprite Effects sample. This will save me going through the setup stuff. I'm just going to replace the Desaturate effect.

Here's a pixel shader that is almost the same as your own. Replace the contents of "desaturate.fx" with this:

sampler TextureSampler : register(s0);

float4 colorMultiply = float4(1, 1, 1, 1);
float4 colorAdd = float4(0, 0, 0, 0);

float4 PixelShader(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    // Look up the texture color.
    float4 tex = tex2D(TextureSampler, texCoord);

    // Perform scaling and addition and return
    return color * tex * colorMultiply + colorAdd;
}

technique ColorMatrix { pass Pass1 { PixelShader = compile ps_2_0 PixelShader(); } }

And now replace the DrawDesaturate function with this:

(As Joel Martinez mentions, in XNA 4 this code becomes much neater.)

void DrawDesaturate(GameTime gameTime)
{
    Effect colorMulAddEffect = desaturateEffect; // reusing desaturateEffect to keep this short!
    float pulsate = Pulsate(gameTime, 4, 0, 1);

    spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);

    colorMulAddEffect.Parameters["colorMultiply"].SetValue(new Vector4(1.5f, 1f, pulsate, 1f));
    colorMulAddEffect.Parameters["colorAdd"].SetValue(new Vector4(-0.5f, 1-pulsate, 0f, 0f));
    colorMulAddEffect.Begin();
    colorMulAddEffect.CurrentTechnique.Passes[0].Begin();

    spriteBatch.Draw(glacierTexture, FullscreenRectangle(), Color.White);

    spriteBatch.End();
    colorMulAddEffect.CurrentTechnique.Passes[0].End();
    colorMulAddEffect.End();
}

Now there is one major proviso with this code - you cannot batch sprites with different colorMultiply and colorAdd values! (Actually colorMultiply is a bit redundant, as you can just use the sprite color as colorMultiply, which you can change per-sprite.)

So if each sprite has a different add/multiply value, you will have to do the whole SpriteBatch.Begin, Effect.Begin, draw stuff, SpriteBatch.End, Effect.End thing for each sprite.

The reason for this is that the pixel shader and its parameters can only be set once per-batch as explained in this blog post.

Doing one sprite per batch will be slow if you have many sprites!

The solution to this (if you need that kind of performance) would be to make a custom sprite batcher, with a custom vertex shader that can pass more than one colour through to the pixel shader. This is possible, although non-trivial. A good starting point would be the source code for the shaders used by SpriteBatch.

Andrew Russell
Very nice - thanks!
Charles