In XNA and its successors, like SharpDX Toolkit or Monogame, the easiest way to draw sprites is by using the SpriteBatch class. As the name implies, SpriteBatch draws the sprites in batches rather than as separate draw calls. Handy and efficient, right?
Enter pixel shaders. You want to do something like draw a single sprite white, and suddenly SpriteBatch doesn’t seem all that convenient anymore. The only way to accomplish this would be to ditch SpriteBatch altogether or draw your sprites in batches of one – what would kinda defeat the purpose of the whole class.
Giga girl emulates ye olde palettes with a simple pixel shader. In a nutshell, the shader replaces a specific colors with something else. For example, our hero’s suit gets colorized and the pink background color in my sprite sheets is never actually rendered. This is how the sprite sheet contents look like:
And this is how they are rendered on screen:
If I now changed the shader to draw everything except transparent as white, it would literally do just that (white on white; sorry):
This is because if we set the HSL parameter for the whole batch, it obviously gets applied to everything that will be drawn within that batch. This simply won’t do.
SpriteBatch doesn’t expose much of its internals, but it does have a built-in tint option within its draw method. Because of the retro nature of the game, Giga girl doesn’t really utilize the alpha-channel to much degree. Could we try transferring information to our HSL shader using that? Hell yeah! Here’s the actual draw method call in my Sprite class:
this.Whiteness ? new Color(0, 0, 0, 0) : Color.White,
Look at the line marked in red. Setting the tint to transparent black (0, 0, 0, 0) is a sneaky way to get around the limitation caused by batching. In the HSL code I first check the transparent color from the texture and then look for alpha value 0:
// Texture color
float4 c0 = SAMPLE_TEXTURE(Texture, texCoord);
// Pink equals transparent
if (distance(c0, pink) < 0.01)
return float4(0, 0, 0, 0);
// White only
if (color.a == 0)
return float4(0.9, 0.9, 0.9, 1);
The only way a totally transparent color value would exist at that point would be A) an invalid spritesheet or B) our super secret backdoor. You might notice that the returned value is not actually pure white, but that is only because it was too bright. Running the game shows the fancy new effect in action:
We can call this a great success! We got what we wanted, and most importantly, without resorting to custom quad renderer or misusing SpriteBatch 🙂