Shaders are incredibly useful, and a pain in the ar*e at the same time.
I write shaders for all sorts of things, the obvious ones like rendering and animation, but also for simulation stuff. Like cloud simulation physics, planet generation, mesh generation, etc.
There are two approaches to the rendering pipeline.
1) Uber shader
2) Shader library
The uber shader approach packs everything you want to do in a single shader and then uses flags to define the actual results.
So you could have coloured - gouraud , textured - gouraud, textured-phong, coloured-phong, bump mapped, cell shading all in the same shader.
The other approach sees you writing a seperate shader for each display mode.
It depends partly on the coder, and partly on the platform which approach is used.
There are limitations on what you can do with shaders though.
Which shader model the target platform supports is the main one, no good using an ubershader if the platform cannot load it.
Also we don't have read write textures, or random access textures, so everything has to be carefully worked out.
It's a part of the modern coding environment I really like, but can be as easy and painful as knocking down a skyscraper with your forehead.