The cost of binding shaders may not be trivial, but it's not going to be your bottleneck unless you're rendering thousands of items without batching all the objects that use the same shaders.
Though I'm not sure if this applies to mobile devices, but GPUs aren't horrendously slow with branches if the condition is between a constant and a uniform. Both are valid, both have been used in the past an will continue to be used in the future, pick whichever one you think would be cleaner in your case.
Additionally, there are a few other ways to accomplish this: "Uber-shaders" and a little trickery with the way OpenGL shader programs are linked.
"Uber-shaders" are essentially the first choice, minus the branching, but you'll have multiple shaders. Instead of using if
statements, you use the preprocessor - #define
, #ifdef
, #else
, #endif
, and compile different versions, including the proper #define
s for what you need.
vec4 color;
#ifdef PER_VERTEX_COLOR
color = in_color;
#else
color = obj_color;
#endif
You can also break the shader up into separate functions. Have one shader that defines prototypes for all the functions and calls them, link a bunch of extra shaders that include the proper implementations. I've used this trick for shadow mapping, to make it easy to swap out how filtering is done on all objects without having to modify all the shaders.
//ins, outs, uniforms
float getShadowCoefficient();
void main()
{
//shading stuff goes here
gl_FragColor = color * getShadowCoefficient();
}
Then, I could have multiple other shader files that define getShadowCoefficient()
, the necessary uniforms, and nothing else. For example, shadow_none.glsl
contains:
float getShadowCoefficient()
{
return 1;
}
And shadow_simple.glsl
contains (simplified from my shader that implements CSMs):
in vec4 eye_position;
uniform sampler2DShadow shad_tex;
uniform mat4 shad_mat;
float getShadowCoefficient()
{
vec4 shad_coord = shad_mat * eye_position;
return texture(shad_tex, shad_coord).x;
}
And you could simply choose whether you wanted shading or not by linking a different shadow_*
shader. This solution may very well have more overhead, but I'd like to think that the GLSL compiler is good enough to optimize away any extra overhead compared to other ways of doing this. I haven't ran any tests on this, but it's is the way I like to do it.