Search code examples
c#xna

ColorMatrix equivalent in XNA


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.


Solution

  • 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.