Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I currently am trying to mask some sprites. Rather than explaining it in words, I've made up some example pictures:

The area to mask The area to mask (in white)

The area to mask, with the image to mask Now, the red sprite that needs to be cropped.

The final result The final result.

Now, I'm aware that in XNA you can do two things to accomplish this:

  1. Use the Stencil Buffer.
  2. Use a Pixel Shader.

I have tried to do a pixel shader, which essentially did this:

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 tex = tex2D(BaseTexture, texCoord);
    float4 bitMask = tex2D(MaskTexture, texCoord);

    if (bitMask.a > 0)
    { 
        return float4(tex.r, tex.g, tex.b, tex.a);
    }
    else
    {
        return float4(0, 0, 0, 0);
    }
}

This seems to crop the images (albeit, not correct once the image starts to move), but my problem is that the images are constantly moving (they aren't static), so this cropping needs to be dynamic.

Is there a way I could alter the shader code to take into account it's position?


Alternatively, I've read about using the Stencil Buffer, but most of the samples seem to hinge on using a rendertarget, which I really don't want to do. (I'm already using 3 or 4 for the rest of the game, and adding another one on top of it seems overkill)

The only tutorial I've found that doesn't use Rendertargets is one from Shawn Hargreaves' blog over here. The issue with that one, though is that it's for XNA 3.1, and doesn't seem to translate well to XNA 4.0.

It seems to me that the pixel shader is the way to go, but I'm unsure of how to get the positioning correct. I believe I would have to change my onscreen coordinates (something like 500, 500) to be between 0 and 1 for the shader coordinates. My only problem is trying to work out how to correctly use the transformed coordinates.

Thanks in advance for any help!

share|improve this question
    
Stencil seems to be the way to go, though, I need to know how to properly use them too :P I'll be waiting for a good answer on the matter. –  Gustavo Maciel Oct 2 '12 at 18:00
    
Heh, most of the good Stencil tutorials are for XNA 3.1, and most of the 4.0 ones use RenderTargets (which seems wasteful, to me). I've updated my question with a link to an old 3.1 tutorial that seemed like it could work, but not in 4.0. Maybe someone knows how to translate it to 4.0 correctly? –  electroflame Oct 2 '12 at 18:05
    
If you would use pixel shader to accomplish it, I think you would have to unproject your pixel coordinate to screen/world(depends on what you want to do), and this is kinda wasteful(stencil would not have this problem) –  Gustavo Maciel Oct 2 '12 at 18:08
    
This question is an excellent question. –  ClassicThunder Oct 3 '12 at 4:45
add comment

2 Answers

up vote 6 down vote accepted

You can use your pixel shader approach but it is incomplete.

You will also need to add some parameters to it that inform the pixel shader where you want your stencil to be located.

sampler BaseTexture : register(s0);
sampler MaskTexture : register(s1) {
    addressU = Clamp;
    addressV = Clamp;
};

//All of these variables are pixel values
//Feel free to replace with float2 variables
float MaskLocationX;
float MaskLocationY;
float MaskWidth;
float MaskHeight;
float BaseTextureLocationX;  //This is where your texture is to be drawn
float BaseTextureLocationY;  //texCoord is different, it is the current pixel
float BaseTextureWidth;
float BaseTextureHeight;

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
    //We need to calculate where in terms of percentage to sample from the MaskTexture
    float maskPixelX = texCoord.x * BaseTextureWidth + BaseTextureLocationX;
    float maskPixelY = texCoord.y * BaseTextureHeight + BaseTextureLocationY;
    float2 maskCoord = float2((maskPixelX - MaskLocationX) / MaskWidth, (maskPixelY - MaskLocationY) / MaskHeight);
    float4 bitMask = tex2D(MaskTexture, maskCoord);

    float4 tex = tex2D(BaseTexture, texCoord);

    //It is a good idea to avoid conditional statements in a pixel shader if you can use math instead.
    return tex * (bitMask.a);
    //Alternate calculation to invert the mask, you could make this a parameter too if you wanted
    //return tex * (1.0 - bitMask.a);
}

In your C# code you pass in variables to the pixel shader prior to the SpriteBatch.Draw call like this:

maskEffect.Parameters["MaskWidth"].SetValue(800);
maskEffect.Parameters["MaskHeight"].SetValue(600);
share|improve this answer
    
Thank you for the answer! This seems like it will work, but currently I'm having an issue with it. Currently, it's being cropped on the left of the image (with a straight edge, as well). Am I supposed to be using screen coordinates for the positions? Or am I already supposed to have converted them to 0 - 1? –  electroflame Oct 3 '12 at 1:24
    
I see the problem. I should have compiled the pixel shader before sharing it. :P In the maskCoord calculation one of the X's should have been a Y. I've edited my initial answer with the fix and included the UV clamp settings you will need for your MaskTexture sampler. –  Jim Oct 3 '12 at 4:40
1  
Now it works perfectly, thank you! The only thing that I should mention is that if you have your sprites centered (using Width/2 and Height/2 for the origin) you will need to subtract half of your Width or Height for your X and Y locations (that you pass into the shader). After that it works perfectly, and is extremely fast! Thanks a ton! –  electroflame Oct 3 '12 at 16:54
add comment

Using the stencil buffer.

enter image description here

GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.Stencil, Color.Transparent, 0, 0);

var m = Matrix.CreateOrthographicOffCenter(0,
    graphics.GraphicsDevice.PresentationParameters.BackBufferWidth,
    graphics.GraphicsDevice.PresentationParameters.BackBufferHeight,
    0, 0, 1
);

var a = new AlphaTestEffect(graphics.GraphicsDevice) {
    Projection = m
};

var s1 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.Always,
    StencilPass = StencilOperation.Replace,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

var s2 = new DepthStencilState {
    StencilEnable = true,
    StencilFunction = CompareFunction.LessEqual,
    StencilPass = StencilOperation.Keep,
    ReferenceStencil = 1,
    DepthBufferEnable = false,
};

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s1, null, a);
spriteBatch.Draw(huh, Vector2.Zero, Color.White); //The mask                                   
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, s2, null, a);            
spriteBatch.Draw(color, Vector2.Zero, Color.White); //The background
spriteBatch.End();    

Opps forgot to mention you need to set the PreferredDepthStencilFormat to DepthFormat.Depth24Stencil8. For example.

graphics = new GraphicsDeviceManager(this) {
    PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
share|improve this answer
    
+1 This is one of the most complete StencilBuffer examples I've seen for XNA 4.0. Thanks for this! The only reason I chose the pixel shader answer is because it was easier to set up (and actually faster to boot), but this is a perfectly valid answer as well and may be easier if you already have a lot of shaders in place. Thanks! Now we've got a complete answer for both routes! –  electroflame Oct 3 '12 at 16:52
    
This is the best real solution for me , and more than the first –  Mehdi Bugnard Oct 17 '13 at 14:06
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.