Monday, November 11, 2019

Apply Vertex Colors to XNA Spritebatch sprites



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?



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("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:


enter image description here


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.



No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...