I know that you can use custom vertex and pixel shaders using SpriteBatch but I can't figure out how to apply colors to individual vertex points on a sprite generated by spritebatch. All I can do is apply a blanket color to the entire sprite rather than being able to blend between the 4 points that should be available in a quad. Can anyone give me a hand with this?
1 Answer
Edit - Changed to another solution that allows a source rectangle to be used.
The color you choose when calling SpriteBatch.Draw
is automatically stored in all four vertices of the quad. There's no way to change this behavior because it's done internally inside the SpriteBatch
.
But there's a hack that you can use that relies on looking at the vertices' texture coordinates (which bu the way are automatically generated by SpriteBatch
from your source rectangle and from the texture size) in order to identify which of the four vertices we're dealing with in the vertex shader, and then change its color before reaching the pixel shader.
I can think of many variations, but the trick I used was to pre-calculate what the texture coordinates would end up being at the center of the quad, so that in the vertex shader you can simply compare to see if the X and Y components are lower or higher than the center in order to determine which corner we're dealing with. You also need to pass it the color for each of the vertices, obviously. So here's the complete effect file code I used:
// Viewport size needed for VS
float2 ViewportSize;
// Vertex color
float4 TopLeftColor;
float4 TopRightColor;
float4 BottomLeftColor;
float4 BottomRightColor;
float2 CenterTexCoord;
void SpriteVertexShader(inout float4 color : COLOR0, inout float2 texCoord : TEXCOORD0, inout float4 position : POSITION0)
{
// Half pixel offset for correct texel centering.
position.xy -= 0.5;
// Viewport adjustment.
position.xy = position.xy / ViewportSize;
position.xy *= float2(2, -2);
position.xy -= float2(1, -1);
// Determine which corner we're dealing with and set the corresponding color
if(texCoord.x < CenterTexCoord.x)
{
if(texCoord.y < CenterTexCoord.y)
{
color = TopLeftColor;
}
else
{
color = BottomLeftColor;
}
}
else
{
if(texCoord.y < CenterTexCoord.y)
{
color = TopRightColor;
}
else
{
color = BottomRightColor;
}
}
}
technique SpriteBatch
{
pass
{
VertexShader = compile vs_2_0 SpriteVertexShader();
}
}
And now the XNA side code. When you create the Effect
you must let it know what the viewport size is, because it's needed by the vertex shader (in fact it's needed by any custom vertex created to be used with SpriteBatch
because you need to do a viewport adjustment):
Effect _effect = Content.Load<Effect>("spriteBatchColorEffect");
_effect.Parameters["ViewportSize"].SetValue(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height));
And when rendering I used the following code. Notice how I calculate the CenterTexCoord
parameter from the source rectangle and the texture size:
Vector2 position = new Vector2(100, 100);
Rectangle source = new Rectangle(20, 20, 90, 90);
_effect.Parameters["TopLeftColor"].SetValue(Color.LightGreen.ToVector4());
_effect.Parameters["TopRightColor"].SetValue(Color.Yellow.ToVector4());
_effect.Parameters["BottomLeftColor"].SetValue(Color.Blue.ToVector4());
_effect.Parameters["BottomRightColor"].SetValue(Color.Red.ToVector4());
_effect.Parameters["CenterTexCoord"].SetValue(new Vector2((source.X + source.Width/2f)/_bg.Width, (source.Y + source.Height/2f)/_bg.Height));
_spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, _effect);
_spriteBatch.Draw(_bg, position, source, Color.White);
_spriteBatch.End();
And finally here's the result:
The best part is that there's no need for any pixel shader code - colors are automatically interpolated between vertices thanks to gouraud shading. This is the only solution I could come up with, but I'd be interested to know of a more proper way to solve the problem too.
-
1\$\begingroup\$ Using your hack seems like the only way to set the colors when constrained to SpriteBatch. The proper way would be to create your own quad using VertexPositionColorTexture vertices. Then draw the quad with a custom vertex & pixel shader. \$\endgroup\$ Commented Mar 5, 2012 at 19:00
-
1\$\begingroup\$ @LuisEstrada Yes, the proper solution would be not to use SpriteBatch. \$\endgroup\$ Commented Mar 5, 2012 at 19:04
-
\$\begingroup\$ Thanks David, but I can't use it on full textures since, like you mentioned, I'd have to have individual texture files for everything I want to draw -_-;; I'll prolly have to resort to just ditching spritebatch and using textured quads if I want to do what I'm trying to do. \$\endgroup\$– SteveCommented Mar 20, 2012 at 18:07
-
\$\begingroup\$ @Steve Check my new edit. I figured out a solution to the source rectangle problem :) \$\endgroup\$ Commented Mar 20, 2012 at 18:40
-
\$\begingroup\$ @DavidGouveia That looks like a pretty legit solution but for the most part, every single sprite that I'll be drawing for the floors will have to have different colors for the four points. It's sort of inefficient to begin and end a spritebatch for every texture that will need vertex colors -_-;; This is still good information and a slick way to apply colors to a specific sprite and thanks a lot for taking time to answer =) The more I've looked into this, the more it seems like I'll just have to ditch spritebatch for the textures I need colored in the manner I need them colored. \$\endgroup\$– SteveCommented Mar 21, 2012 at 23:14