Shader – Bitmasking is Easier than you Think
I recently created a Unity asset, Fast Line Renderer. I wanted different line caps so that the lines look nice. One option was to do four draw calls and four materials. I didn’t like this idea as it seemed like a performance problem. I needed a way for the shader itself to know for each vertice what kind of line cap it was using (none, join, start cap or end cap).
In order to make this happen in one draw call, I am storing the line cap type in the texture coordinate of the vertice.
float textureCoordinateX = (int)QuadUV1.x | lineType;
In the shader, I initially tried to use bitmasking to pull out the line type, only to realize that older OpenGL and DirectX versions do not support this. Since the texture coordinate is always stored only in the first bit, I realized I could use modulus and division to pull out the texture coordinate.
uint lineType = abs(round(v.texcoord.x));
float2 texcoord = float2(fmod(lineType, 2), v.texcoord.y);
From there it was simply a matter of dividing by two and then doing another fmod by 2. Doing this four times allowed me to get a multiplier for each line cap type which I then multiplied against the tex2D call for the four textures: line texture, join texture, start cap texture and end cap texture. Adding these all up gives me the final pixel 🙂
It’s amazing that simple modulus and division operations can be a substitute for bit masking. They also help avoid if / else statements which should generally be avoided in a shader. GPU are great at doing simple math operations like divide and modulus so it’s no problem to have lots of them, especially in a vertex shader.
Fast Line Renderer does almost all it’s work in the vertex shader. The pixel shader simply does the four tex2D calls and multiplies each by the four line cap multipliers, then sums it up to get the final pixel.