In GLSL parts of a shader get optimized out so even if you mistakenly keep an unecessary define it'll not be part of the compiled code. I find this makes debugging REALLY hard if I randomly comment out a portion of code that uses a diffuse texture for example. I then have to comment out the C++ code that also passes in the diffuse texture sampler or else it says unknown uniform.
I found using
min(0, sampler2D(diffuseTexture)) for example, would force a zeroing out without optimizing the diffuseTexture uniform out of the shader, making me not have to recompile the C++ if I'm debugging.
Also I have a material resource where I specify things like what normal map I use, what diffuse texture I use, etc... So these bitmasks are generated automatically for me and there's no human error. If I provide a normal map, my system automatically says:
bitmask |= NORMAL_MAP
bitmask |= TEX_COORDS
bitmask |= TANGENTS
If I just provide a diffuse texture I'd say
bitmask |= DIFFUSE_MAP
bitmask |= TEX_COORDS
since tangents aren't necessary unless you're doing normal mapping or other tangent space calculations in the shader. Textures all need UVs passed in so TEX_COORDS is passed in.
Since I have a deferred shader I always pass in normals for the deferred shading stage.
If I have a full bright unlit material, that doesn't need normals and is rendered in the forward shading stage. If I want to render a lit forward shaded material I can set the Fullbright flag to false in my material, meaning this material will be affected by lights. Then I do:
bitmask |= NORMALS
Then if I need skeletal animations for the material I also have the user say this material will be used on skinned meshes and then do:
bitmask |= SKELETAL_ANIMATIONS
It's possible to generate 2 shaders instead for the material, one with skeletal animations, and one without. But I figured I'd most likely almost never try to texture a character with a brick wall texture, and I'd definitely never try to texture a wall with a character skin. If I really wanted to I can just create 2 separate materials and the textures would be shared between them with my resource management system anyway. I'd still have 2 separate shaders.
Then the renderer can also have some asserts and other debug build only checks that make sure valid things are passed in so i don't mistakenly have a mesh without tangents in its vbo being used with a material that needs tangents.