可编程着色器(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 里也不再包含。


