Most of my Twitter feed is game development, so I often see people posting tutorials on writing shaders. Now don’t get me wrong, I absolutely love that these kind folks are taking time to help others. Learning how to write shaders can be daunting. But all too often these beginner tutorials include conditionals / branching, which is a bad idea.
This is a bit of a simplification, but the reason shaders can do so much cool stuff is because your graphics card is running the same shader simultaneously on multiple pixels. To do this, the card’s processor needs to know that every instance of the shader is at exactly the same point in its execution. If your shader contains branching, ie…
…then one instance may be at a different point than another, and the whole parallel processing thing kinda falls apart.
Fortunately it’s quite simple to avoid branching. As an example, I modified a basic Unity fragment shader to draw a square that would be red on the left and green on the right. Here’s the output:
And here’s the shader:
In the vertex function (vert) we calculate the position of the vertex in world space. In the fragment function (frag) we set the colour to _Color1 (red). Then we check if this pixel’s x position in world space is greater than zero. If it is then we change the colour to _Color2 (green). Easy. But ugh! Look at that horrible “if” block! When your graphics card is trying to run multiple instances of this shader simultaneously, they could very quickly get out of sync.
Here’s one way to avoid the if block. What we’ll do first is use the step function. This compares two values, a and b, and if b is greater than a then the function returns 1. If b isn’t greater than a then the function returns 0.
So what we’re doing there is checking if the x position is greater than zero. If it is then s will be set to 1. If it isn’t then s is 0.
Next we’ll use the lerp function. This returns a value that is part way between a and b, based on value c. If c is 0 then lerp returns a, if c is 1 then lerp returns b, if c is 0.5 then lerp returns the value half way between a and b. So for example lerp(0, 10, 0.5) would return 5, which is half way between 0 and 10.
Remember we stored the result of the step function in s? So if we’re drawing a pixel on the left of the screen, s is set to 0 and we want a red pixel. If we’re on the right of the screen then s is 1 and we want a green pixel. We can use that value s to make lerp return either _Color1 (red) or _Color2 (green) like this:
And that’s all there is to it. The fragment function can simply return the value from lerp, and this is our entire fragment function:
Once you start getting to grips with shader coding, your imagination will run wild and you’ll come up with all sorts of cool ideas. And of course, the more complex your shaders are, the more important it is that they run optimally. Avoiding conditionals is one part of that, so it’s useful to learn it early on.