-
|
|
How can I use SpriteBatch to display sprites with dynamic alpha masks?
|
Hey guys,
I'm trying to display sprites from a sprite sheet, but mix-and-match alpha masks from the same texture.
So for example, I've got several types of grass and I want to plant them down using a variety of shoreline shapes that exists at other pixel offsets within the same texture's alpha channel.
For clarity's sake I'm showing the tiles and the alpha channel side by side...
Is this possible?
I thought had this problem nailed down, and wrote a pixel shader which basically sampled the color and then alpha at an offset, but I just learned the params passed to shaders are global to the pass. :/ It looks great when I draw 1 test tile, but screws everything else up.
What strategy should I be pursuing to accomplish this?
I might be able to use a shader if it were possible the shader could identify which object it was drawing and use the right offsets, although I'd be reluctant to push a crapload of data to the shader for every object in the batch.
Can I continue to use SpriteBatch or do I need to use quads?
Any suggestions would be much appreciated.
|
|
-
-
- (20352)
-
premium membership
MVP
-
Posts
12,388
|
Re: How can I use SpriteBatch to display sprites with dynamic alpha masks?
|
You would normally do this by passing in an extra set of texture coordinates and using that for the choice of alpha mask. But I dont think you can change the vertex definition to sprite batch so you are stuck unless you write your own pixel/vertex shader and just use your own effects.
The only other thing you can pass in per sprite is the color, which does happen to have 4 floats, I'm not sure exaclty what the default vertex shader does with those but I'm guessing it passes them as vertex colors into the pixel shader. Maybe you can find a way to use those as an offset into you alpha maps.
Play Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
|
|
Re: How can I use SpriteBatch to display sprites with dynamic alpha masks?
|
Hi ZMan, thanks for the response.
Let me see if I understand your last comment correctly :
1. write and implement a vertex shader,
2. create a Vector4 float ( or whatever Color is ) and store my per-sprite alpha offsets in it,
3. pass it through SpriteBatch.Draw in the Color param,
4. catch it in the vertex shader,
5. pass it to the pixel shader,
6. sample my alpha value at the passed coords.
Sound like this will work? I need to go learn how to write a vertex shader real quick, but it sounds good. :D
|
|
-
-
- (12865)
-
Team XNA
-
Posts
8,532
|
Re: How can I use SpriteBatch to display sprites with dynamic alpha masks?
|
You don't need a vertex shader for this (replacing the SpriteBatch vertex shader is something of an advanced topic and not really recommended).
XNA Framework Developer -
blog - homepage
|
|
-
-
- (20352)
-
premium membership
MVP
-
Posts
12,388
|
Re: How can I use SpriteBatch to display sprites with dynamic alpha masks?
|
There's 2 options:
1. Dont use sprite batch - its actually just a vertex/pixel shader anyway so you can write your own. If you write your own you can create a vertex buffer with 2 sets of texture coordinates and then its trivial to draw them using a custom vertex/pixl shader.
2. Use sprite batch and replace the pixel shader like you are doing now. However you need to find a way to pass in the extra texture coordinates (one for the texture, one for the alpha map). So use the color parameter since it has 4 floats. You will have to do the calculations yourself to work them out but it might work. The same color will be passed in for each vertex which means in the pixel shader you will get a constant value. You should be able to use this as an offset to the texture coordinates and resamplethe texture map. Note that you can't rewrite the vertex shader that comes with sprite batch only the pixel shader.
Play Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
|
|
Re: How can I use SpriteBatch to display sprites with dynamic alpha masks?
|
Woohoo, I got it working finally!
I managed to continue using SpriteBatch, offset my alpha masks, and I can still do tinting.
Basically the Color param on SpriteBatch.Draw passes 4 bytes as ARGB information.
I split the 3 bytes of ARG into two 12-bit values ( that was the tough part ) to store my xoff and yoff, and I reserved the B byte for passing a color index to my pixel shader.
Here is my shader...
| //------------------------------ TEXTURE PROPERTIES ---------------------------- |
| |
| texture ScreenTexture; |
| |
| sampler TextureSampler = sampler_state |
| { |
| Texture = <ScreenTexture>; |
| }; |
| |
| //------------------------ PIXEL SHADER ---------------------------------------- |
| |
| float4 MyShaderFunction( float2 TexCoord0 : TEXCOORD0, float4 c : COLOR) : COLOR0 |
| { |
| float pixelSize = 1.0f / 2048.0f; // our texture is 2048x2048 |
| |
| float2 TexCoord1; |
| |
| // we want to reconstruct the xoff/yoff out of our color value into two signed 12-bit values |
| |
| // 1) convert the 0.0 - 1.0 values of these floats back into 0-255 byte format |
| |
| int a = c.a * 255; |
| int r = c.r * 255; |
| int g = c.g * 255; |
| int b = c.b * 255; |
| |
| // split the "alpha" member into two values. |
| // half this byte is for xoff, and half this byte is for yoff |
| |
| int bx = a / 16; |
| int by = a - (bx * 16); |
| |
| // reconstruct the xoff and yoff. |
| // this was all very tricky because we needed to pass 2 integers in 3 bytes |
| // that gave us 24-bits to work with and the necessity to split one byte. |
| // a 12-bit value can store 0 to 4095. we need negative numbers too, so we |
| // implemented a signed range -2048 to 2047. |
| |
| int xoff = (bx * 256) + r; |
| int yoff = (by * 256) + g; |
| |
| // OK, we've reconstructed our 12-bit ints |
| |
| // decrement by 2048 (which we added in our sprite class) to give us potentially signed values |
| |
| xoff -= 2048; |
| yoff -= 2048; |
| |
| // at this point xoff and yoff should contain the actual offset between our source art and a mask |
| |
| // determine a new coordinate set based on our current pixel and the offset. |
| // note that we multiply our offsets by the "pixelsize" because texture coordinates are on a |
| // 0.0 - 1.0 system as well. there may be some way to grab the texture width inside the shader |
| // but for now we've hardcoded it. |
| |
| TexCoord1.x = TexCoord0.x + (xoff * pixelSize); |
| TexCoord1.y = TexCoord0.y + (yoff * pixelSize); |
| |
| // sample both positions |
| |
| float4 color = tex2D( TextureSampler, TexCoord0); |
| float4 alpha = tex2D( TextureSampler, TexCoord1); |
| |
| // overwrite the alpha value with that of our desired mask |
| |
| color.a = alpha.a; |
| |
| // DONE! |
| |
| // now lets implement some rudimentary tinting. since we hijacked the bulk of the Color param |
| // for alpha offsets, we've got 1 byte left to work with. we can use it as a color index for |
| // some predefined types, or effects, whatever you choose to do here |
| |
| if( b == 1) // pure red |
| { |
| color.g = 0; |
| color.b = 0; |
| } |
| else |
| if( b == 2) // pure green |
| { |
| color.r = 0; |
| color.b = 0; |
| } |
| else |
| if( b == 3) // pure blue |
| { |
| color.r = 0; |
| color.g = 0; |
| } |
| else |
| if( b == 4) // bright yellow |
| { |
| color.b = 0; |
| } |
| else |
| if( b == 5) // bright cyan |
| { |
| color.r = 0; |
| } |
| else |
| if( b == 6) // purple |
| { |
| color.g = 0; |
| } |
| |
| return color; |
| } |
| |
| //-------------------------- TECHNIQUES ---------------------------------------- |
| |
| technique myTechnique |
| { |
| pass Pass0 |
| { |
| PixelShader = compile ps_2_0 MyShaderFunction(); |
| } |
| } |
Here is my sprite management class ( it has a wrapped Render method that takes our mask offset and tint color ). I load all my sprite information from a .csv file on initialization, and create an object for every sprite. They share the same loaded texture and I can draw them based on index number, which is what I store in my game map.
| namespace GameShell |
| { |
| public class _TileObject |
| { |
| public string name; |
| |
| public int topX; |
| public int topY; |
| |
| public int iWidth; |
| public int iHeight; |
| |
| public int xOff; |
| public int yOff; |
| |
| public int iRandomLow; |
| public int iRandomHigh; |
| |
| public Texture2D localTexture; |
| public Vector2 position; |
| public float rotation; |
| public Vector2 center; |
| |
| public Rectangle spriteRect; |
| |
| public int alphaX; |
| public int alphaY; |
| |
| public _TileObject( Texture2D loadedTexture) |
| { |
| name = null; |
| |
| topX = 0; |
| topY = 0; |
| iWidth = 0; |
| iHeight = 0; |
| xOff = 0; |
| yOff = 0; |
| iRandomLow = 0; |
| iRandomHigh = 0; |
| |
| rotation = 0.0f; |
| position = Vector2.Zero; |
| localTexture = loadedTexture; |
| center = new Vector2(0, 0); |
| } |
| |
| public void Render( |
| int dx, |
| int dy, |
| int ax, |
| int ay, |
| byte colorindex) |
| { |
| position.X = dx + xOff; |
| position.Y = dy + yOff; |
| |
| spriteRect.X = topX; |
| spriteRect.Y = topY; |
| spriteRect.Width = iWidth; |
| spriteRect.Height = iHeight; |
| |
| if( ax == 0 && ay == 0 ) |
| { |
| alphaX = 0; |
| alphaY = 0; |
| } |
| else |
| { |
| alphaX = ax - topX; |
| alphaY = ay - topY; |
| } |
| |
| // create a Color object |
| |
| Microsoft.Xna.Framework.Graphics.Color c = new Color(); |
| |
| // we can only support alpha masks which are within 2048 pixels +/- from the main sprite, x or y |
| |
| if( alphaX > 2047) |
| alphaX = 2047; |
| |
| if( alphaY > 2047) |
| alphaY = 2047; |
| |
| // alphaX and alphaY might be signed values. |
| // lets jack their value by 2048 ( force them positive ) and undo this inside the shader |
| // i don't want to think about negative numbers when i do the following math! |
| |
| alphaX += 2048; |
| alphaY += 2048; |
| |
| // half a byte can store a value between 0 and 15. we'll split "alpha" between xoff and yoff, |
| // with half the byte representing alphaX / 256, up to 15*256 (3840) and stick the remainders |
| // in R and G. that means we can pass a value between 0 and 4095, reduce it by 2048 in our |
| // shader and get a decent signed range. |
| |
| byte bx = (byte)(alphaX / 256); |
| byte by = (byte)(alphaY / 256); |
| |
| c.A = (byte)((bx * 16) + by); |
| |
| c.R = (byte)(alphaX % 256); |
| c.G = (byte)(alphaY % 256); |
| |
| // B will pass our color index if we want to tint the sprite |
| |
| c.B = colorindex; |
| |
| GlobalClass.spriteBatch.Draw( |
| localTexture, |
| position, |
| spriteRect, |
| c, |
| rotation, |
| center, |
| 1.0f, |
| SpriteEffects.None, |
| 0); |
| } |
| } |
| } |
In my main game class where I draw all my tiles, I start the SpriteBatch, loop through my map data and draw my tiles...
| GlobalClass.spriteBatch.Begin( SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); |
| |
| // enable our shader |
| |
| GlobalClass.alphaEffect.Begin(); |
| GlobalClass.alphaEffect.CurrentTechnique.Passes[0].Begin(); |
| |
| // some loop here that considers the map data |
| |
| myTiles [ iTileNum ].Render( px, py, 0, 0, 0); |
| |
| // after our loop is done |
| |
| GlobalClass.alphaEffect.CurrentTechnique.Passes[0].End(); |
| GlobalClass.alphaEffect.End(); |
| |
| GlobalClass.spriteBatch.End(); |
|
|
|