可编程着色器(programmable shader)是行为由开发者编写的程序决定的 着色器 阶段,与固定功能管线(fixed-function pipeline,算法固化在硬件里、只能通过 API 调参数)相对。

固定管线时代(OpenGL 1.x、Direct3D 7 及更早),顶点变换、光照、纹理混合的算法全部做在硬件里,开发者能做的只是"填参数":

// 固定管线:算法写在硬件里,这里只是设置参数
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);

glLightfv 里没有"漫反射怎么算",只有"这盏灯在哪、什么颜色"。硬件按内置公式算完光照,开发者既看不到公式,也改不了它。

现在,我们更多地把同一件事交给着色器来做,公式就变成了可读的代码:

vec3 N = normalize(vNormal);
vec3 L = normalize(lightPos - vWorldPos);
float diff = max(dot(N, L), 0.0);
FragColor = vec4(baseColor * diff, 1.0);

两者的差别可以这样对照:

维度 固定功能管线 可编程着色器
算法 硬件写死 开发者编写
控制方式 glEnable / glLight* / glTexEnv* 设参数 上传源码,编译链接成程序
表达力 只有预设的几种光照与混合模式 任意算法:自定义 BRDF、卡通渲染、后处理
性能 驱动与硬件能针对固定路径优化 上限接近,但会取决于代码质量
灵活性 新效果要等硬件支持 改代码即可,甚至能热重载
开发成本 简单 正确性、精度、分支与寄存器压力都要自己管

2001 年前后,Direct3D 8 引入可编程的顶点/像素着色器(shader model 1.x),OpenGL 用 ARB_vertex_program、ARB_fragment_program 扩展跟进,OpenGL 2.0 的 GLSL 1.10 才把着色器语言标准化。此后可编程范围逐步覆盖几何、细分,OpenGL 4.3 又补上计算着色器:

阶段 在管线中的位置 是否必需 典型用途
顶点着色器 最前 必需 顶点变换、法线变换、把数据往下传
细分控制/求值着色器 顶点之后、几何之前 成对可选 曲面细分、地形与曲面
几何着色器 图元装配之后 可选 按图元增删顶点,粒子、法线可视化
片段着色器 光栅化之后 有颜色输出时必需 光照、纹理采样、discard
计算着色器 管线之外 可选 后处理、粒子模拟、GPGPU

现代 渲染API 普遍已经取消了固定管线:Vulkan、Direct3D 12、Metal 的管线里只有着色器,连画一个三角形也必须提供着色器。OpenGL 的兼容 profile 还保留 glBegin、glLight* 这些旧接口,但是核心 profile 里也不再包含。