How can I add a border to my sprites?

I’m using 2D Toolkit to create a 2D game for iOS and Windows with Unity Free. I would like to be able to add a border to my sprites under certain conditions, like so:

Ideally, I don’t want to have to create two versions of every sprite; one with a border, and one without. I’d like to simply create a single version of each sprite.

The way I currently have this working is by creating the border directly in the texture source image in Photoshop, and setting the opacity of the border pixels to 99%. Then, in Unity, I have two shaders:

  1. The normal 2D Toolkit shader, which I use for displaying the sprite with border:
  2. A custom shader I made to hide any pixels with alpha less than 1.0, which I use to display the sprite without border:

Unfortunately, I was just reading elsewhere on Unity Answers that using AlphaTest in a shader is a “big no-no” for iOS, so it seems like this is ultimately not going to be a very good solution. Furthermore, it locks me into using a single border color (whatever color I choose when I make the sprite in Photoshop), instead of being able to dynamically add a border of any color at runtime.

Is there a better, higher performance way to add a border to a sprite with a shader that will work well on iOS? Or am I stuck creating two versions of each of my sprites?

Just thinking out loud here: I’ve done some simple pixel shader work in the past with HLSL, and I was able to test the color of nearby pixels and selectively set a new color for any given pixel based on that test (which seems like it might be helpful for creating a border), but I haven’t been able to figure out how to do that with Unity. Admittedly, I don’t understand Unity shaders very well (or any shaders very well, for that matter), so maybe I’m completely barking up the wrong tree here, but I just thought I’d throw that idea out there.

To answer that question, you can do something quite cheeky to get the effect you require, but will require a custom shader and the texture set up in a special way. What I’m describing here will only work with point sampling, and will fail when bilinear / trilinear is turned on.

First of all, lets say you color key your source image in such a way that r = 0 => transparent. You can make your shader produce the same result you see above, by making it consider the alpha channel.

half4 color = tex2D( ... );   // you can reduce this to a max/multiply
color.a = (color.r == 0)?1:0; // if this evaluates into a branch

Now you have a basic sprite drawing, but you now have the alpha channel free to store anything you like. There is only one caveat here, you will not be able to draw any color where r = 0, but r = 1 works, and that might be a fair compromise.

Now for the border

In your alpha channel, make sure you always draw a one pixel border around all your sprites. When you need to draw something with a border, simply switch to a shader which uses this

color.rgb = (color.r == 0)?borderColor:color;

So now you have a border, which you can control the color of. You can try to be creative with this and use the vertex color attribute to do various things instead of just tinting the sprite.

Just a random example that springs to mind, and assuming you can get the instruction count low enough, you can merge the 2 shaders to produce something where you can fade the border in using the alpha channel of vertex color.

half colorKey = color.r;
color.rgb = (colorKey == 0)?borderColor:color;
color.a = (colorKey == 0)?IN.vcolor.a:0;

Hope this helps.

p.s. always look at shader output and/or profile to see what the compiler has produced. There will always be ways to coax it to do one thing over the other to gain performance.