The basic idea is as follows, we want this effect:
Here, the purple part is at 50% opacity. The green circle texture is repeatable. The "grass" on the purple tiles still shows through in the final result. Here's how it's done.
First and foremost, set the graphics format like so:
graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;
We are going to utilize XNA's stencil masking capabilities. The following are the two stencil masks we need to use.
//This is a stencil. It keeps track of pixels. But only the ones we tell it to. //In this case, every pixel we pass to this stencil will be written to the buffer because we are using CompareFunction.Always public static DepthStencilState AlwaysStencilState = new DepthStencilState() { StencilEnable = true, StencilFunction = CompareFunction.Always, StencilPass = StencilOperation.Replace, ReferenceStencil = 1, DepthBufferEnable = false, }; //This one will check what stuff was written to the stencil buffer and will help us to know what pixels to mask. public static DepthStencilState EqualStencilState = new DepthStencilState() { StencilEnable = true, StencilFunction = CompareFunction.Equal, StencilPass = StencilOperation.Keep, ReferenceStencil = 1, DepthBufferEnable = false, };
These masks will help us "slice out" the portions of a texture that we wish to mask. The AlwaysStencilState will pass every pixel of the texture-to-be-masked to the buffer we are using. The EqualStencilState will then check the pixels that were written and see if their alpha channel passes the alpha test; in this case, if they have an alpha of 127 out of 255 (50%). Great, we have some stencils set up. But how do we use them?
First, we make a buildMask method:
private void buildMask() { _bounds = _camera.Viewport.Bounds; _projection = Matrix.CreateOrthographicOffCenter( 0, _spriteBatch.GraphicsDevice.PresentationParameters.BackBufferWidth, _spriteBatch.GraphicsDevice.PresentationParameters.BackBufferHeight, 0, 0, 1); }
This defines a couple of member variables for us. _bounds is for the size of the buffer we will be writing to. Not used in this particular example. _projection sets up the correct view matrix for our masking needs.
Now set up a couple of alpha tests. One to check for 50% opacity and the other to check for more solid values.
//Create Alpha Test Effect _alphaEffect = new AlphaTestEffect(_spriteBatch.GraphicsDevice); _alphaEffect.AlphaFunction = CompareFunction.Equal; //This value can be 127, 128 or 129 depending on the program used to create the tile. We're shooting for 50% opacity //Apparently GIMP gives us 127 _alphaEffect.ReferenceAlpha = 127; //Create next Alpha Test Effect for grass _alphaEffect2 = new AlphaTestEffect(_spriteBatch.GraphicsDevice); _alphaEffect2.AlphaFunction = CompareFunction.Greater; //Use the "greater than" comparison, the alpha value of the pixel must be greater than the ReferenceAlpha _alphaEffect2.ReferenceAlpha = 130; //The value to test against is 130 out of 255 //Give the alpha test the correct projection matrix _alphaEffect.Projection = _projection; _alphaEffect2.Projection = _projection;
Once all that is set up, we can simply draw. Draw in this order 50% opacity parts, opaque parts, mask.
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Transparent); //Clear the stencil's buffer GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 0); //Paint the layer to the stencil buffer _spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, AlwaysStencilState, null, _alphaEffect); //Draw the tile _spriteBatch.Draw(_tile, new Vector2(200,200), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0); //End _spriteBatch.End(); //Now we draw the "grass" part of each tile. It is the only part that gets drawn to the rendertarget because of alphaEffect2 //which says, if you have over 130/255 opacity, get drawn son! We don't use a stencil here because we don't need to keep track //of these pixels because we aren't masking them later. _spriteBatch.Begin(SpriteSortMode.Immediate, null, SamplerState.PointClamp, null, null, _alphaEffect2); //Draw me some grass! _spriteBatch.Draw(_tile, new Vector2(200, 200), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0); //Done with grass! _spriteBatch.End(); //Now we paint the mask texture _spriteBatch.Begin(SpriteSortMode.Immediate, null, SamplerState.PointClamp, EqualStencilState, null, null); //Draw the mask _spriteBatch.Draw(_maskTex, new Vector2(200, 200), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0); //End _spriteBatch.End(); base.Draw(gameTime); }
Take a look at the linked project to see a fully working example. Feel free to use the code however you please but please give me credit somewhere. Thanks!
Click here to download sample project.
No comments:
Post a Comment