When the same word is used to mean slightly different things, there’s always a chance of creating confusion—and the word “shader” is a bit overloaded in computer graphics. Between engineers, artists, and DCC tools’ terminology, there are at least four different meanings of “shader” out there.
To begin with, there’s the low-level engineering meaning of a “shader” as an individual GPU program,
e.g. a vertex shader, pixel shader, or compute shader. They begin life as source code written in
a shading language such as HLSL, and they get compiled (often through multiple stages) down to machine
code that actually executes on the GPU’s shader cores. Graphics APIs represent the resulting binaries
as primitive objects—for example,
instances, or OpenGL shader objects.
A vertex shader or pixel shader isn’t of much use by itself. Most of the time, one of each—and perhaps tessellation or geometry shaders also—are designed to work together to accomplish a task. So engineers like to hop up an abstraction level and refer to the complete graphics pipeline’s worth of shaders as “a shader”, as well.
Most game engines contain an object expressing this concept, such as Unreal’s
class. Certain APIs express it too, as in OpenGL’s longtime “program objects”, and more recently
D3D12/Vulkan/Metal’s concept of “pipeline states”. (Although pipeline states include more than just
shaders.) Moreover, a pipeline-state of shaders frequently acts as a unit in game engines’ asset
systems, e.g. for dependency tracking, asset pack building, and so on.
From this point of view, an individual shader binary is more like a single function out of a module or library, rather than a program in its own right.
But the ladder of abstraction doesn’t stop there. Given the complexity of renderers these days, a single pipeline-state “shader” is still too fine-grained an object for many purposes.
First, a game object usually needs to get rendered in several passes, such as: depth prepass, shadow maps, deferred G-buffer pass, forward lighting pass, etc. Each of these has its own set of shader binaries and pipeline state, but they all work together to make the object show up in the game world, properly lit and shadowed. Another case is a postprocessing effect with multiple passes that cooperate—for instance, the standard bloom implementation involves several downsample, blur, and upsample passes.
Second, we often generate many variants of a shader, offering different
combinations of features: with a normal map or not, with skinned animation or not, with
subsurface scattering or not, and so on. Conceptually these behave like one giant shader with a
if-statements in it, but in practice we actually compile variants that strip out the code
for unused features, to get better runtime performance. This can lead to
vast combinatorial explosion.
For both of these reasons, it’s common to group together a set of related pipeline-state shaders, or a schema for lazily generating shaders on demand, and call that whole thing a “shader”, too.
This is usually the smallest unit that artists and other non-engineers are concerned with—it’ll appear in the interface of your editor as “a shader”, and act as a single noun, with all the underlying structure hidden. We might say that this object represents the sum total of the game engine’s capabilities of rendering some general type of thing, efficiently and with all the appropriate lighting/shading phenomena in place.
In days of yore, D3D9 HLSL had the concept of an effect, a collection of shaders compiled from a single source file and organized into “techniques” and “passes”. That original system is long obsolete, but many game engines still organize their shaders along similar lines.
The final meaning of “shader” that I’ll discuss here isn’t a higher level of abstraction, but something more specific: a shader (in the sense of the previous section) together with specific settings for its user-editable texture inputs and other parameters. This is also called a “material”. For example, you might have a generic diffuse/normal/specular shader, and when you bind it to a certain set of textures and specular parameters, it becomes a “brick shader”, or a “metal shader”, or a “grass shader”.
This terminology comes from certain DCC tools—in particular, Maya, where materials take the form of shader nodes in Maya’s dependency graph. Users might talk about “editing a shader”, “putting a shader on a mesh”, and so on, referring to the material settings as opposed to, say, the shader source code.
That’s why when you hear someone talking about shaders and it’s not clear from the context, you might have to ask them to clarify just what kind of “shader” they mean!