中EN
概念页

可编程着色器与 GPU 管线

Programmable Shaders & GPU Pipeline

更新于 2026-10-15 约 14 分钟GPU着色器API

GPU 把第 2 章的概念管线实现为一组硬件阶段,分为完全可编程、可配置但不可编程、完全固定三类。现代 GPU 采用统一着色器架构:各类着色器共用同一套编程模型和核心,由 GPU 自行分配负载。着色器用 HLSL、GLSL 等类 C 语言编写,输入分为 uniform 和 varying 两类。

GPU 管线的各阶段

下图是 GPU 的逻辑管线,虚线框是可选阶段。

flowchart LR
    VS["顶点着色器"] --> TS["曲面细分"]:::opt --> GS["几何着色器"]:::opt --> CL["裁剪"] --> SM["屏幕映射"] --> TR["三角形设置与遍历"] --> PS["像素着色器"] --> MG["合并"]
    classDef opt stroke-dasharray: 5 5
阶段可控程度
顶点着色器、曲面细分着色器、几何着色器、像素着色器完全可编程
屏幕映射(由窗口和视口设置决定)、合并阶段可配置,不可编程
裁剪、三角形设置、三角形遍历固定功能

曲面细分和几何着色器都是可选的,不是所有 GPU 都支持,移动设备尤其如此。

逻辑模型 vs 物理模型

这里讲的是 API 暴露给程序员的逻辑模型,硬件厂商怎么实现是另一回事:逻辑上固定的阶段可能被并入相邻的可编程阶段执行,一个着色器也可能被拆到不同单元甚至单独一个 pass 执行。逻辑模型能帮你推理哪些因素影响性能,但不要把它当成 GPU 的真实实现。

统一着色器架构 (Unified Shader Architecture)

  • 顶点、像素、几何、曲面细分着色器共用同一套编程模型和指令集 (ISA)。DirectX 把这种处理器叫通用着色器核心 (common-shader core)。
  • 好处是负载均衡:大量细小三角形需要更多顶点处理,大三角形需要更多像素处理。如果顶点核心和像素核心是分开的两池,分配比例就被固定死了;统一核心可以由 GPU 按需分配。

着色语言与编译

  • 常用 HLSL(DirectX)和 GLSL(OpenGL),都是类 C 语言。
  • HLSL 可以编译成与硬件无关的中间语言(IL 或 DXIL),也能离线编译保存;驱动再把它转换成具体 GPU 的 ISA。
  • 主机开发通常跳过中间语言,因为只有一种 ISA。
  • 基本数据类型是 32 位浮点标量和向量(向量只存在于着色器代码层面,硬件不一定直接支持),现代 GPU 也原生支持 32 位整数和 64 位浮点。

输入类型

一次 draw call 调用图形 API 绘制一组图元,从而驱动管线运行着色器。

输入特点例子
uniform一次 draw call 内保持不变,draw call 之间可改光源颜色
varying来自三角形顶点或光栅化,每个顶点或像素不同表面位置
纹理一种特殊的 uniform,如今可以看作任意大型数据数组贴图、查找表

uniform 只存一份、所有顶点或像素共用,所以常量寄存器的数量远多于 varying 的输入输出寄存器。

流程控制

类型依据特点
静态流程控制uniform 的值整个 draw call 走同一路径,没有线程分歧;适合让同一个着色器适应不同情况,比如不同数量的光源
动态流程控制varying 的值每个片元可以走不同路径,更强大,但路径变化剧烈时代价很高(见 GPU 并行架构 的线程分歧)

可编程着色和 API 的演进

时间事件
1984Cook 提出 shade tree,可编程着色的雏形,后来发展出 RenderMan 着色语言
19963dfx Voodoo,固定功能管线
1999NVIDIA GeForce 256,第一个叫"GPU"的芯片,可配置但不可编程
2001GeForce 3,第一个支持可编程顶点着色器的 GPU(DirectX 8.0),类汇编语言
2002DirectX 9.0 / Shader Model 2.0,真正可编程的顶点和像素着色器,推出 HLSL;OpenGL 推出 GLSL
2004Shader Model 3.0,动态流程控制
2006DirectX 10 / Shader Model 4.0,几何着色器、流输出、统一编程模型、整数类型;Wii 是最后一台主要的固定功能机器
2009DirectX 11 / Shader Model 5.0,曲面细分着色器、计算着色器
2013AMD Mantle,低开销 API 的开端
2014Apple Metal
2015DirectX 12
2016Vulkan,使用中间语言 SPIR-V

低开销 API

Mantle、DirectX 12、Vulkan、Metal 这一代 API 的重点不是新的 GPU 功能,而是大幅减少 CPU 花在驱动上的时间,并更好地支持 CPU 多线程。适合 CPU 驱动开销成为瓶颈的场景,在移动设备上还能省电。但从旧 API 移植很难,直接照搬的实现反而可能更慢。

移动端和 Web

  • OpenGL ES:为嵌入式和移动设备设计。ES 2.0(2007)开始可编程,ES 3.0(2012)加入多渲染目标、纹理压缩、实例化等,ES 3.1 加计算着色器,ES 3.2 加几何和曲面细分着色器。
  • WebGL:通过 JavaScript 调用,WebGL 1 相当于 OpenGL ES 2.0,WebGL 2 相当于 ES 3.0。跨平台、只需文本编辑器、浏览器自带调试器,非常适合做实验和教学;three.js 这类库提供了阴影、后处理、PBR、延迟渲染等现成实现。

在游戏开发中的意义

  • 用 uniform 驱动的静态分支做"着色器变体",比在逐像素数据上分支安全得多。
  • 选 API 时的取舍:DX12 和 Vulkan 能降低 CPU 开销、更好地多线程,但开发和移植成本高;如果瓶颈不在 CPU 驱动,收益有限。

GEA 的视角:着色器模型与 HLSL 入门(卷 II 11.4.5、11.4.10)

可编程性的演进

  • GEA 的时间线:3Dfx Voodoo 只做光栅化、着色和合并,前段交给 CPU;NVIDIA GeForce 256 是第一块"真正的" GPU,把变换和投影也做进硬件,但整条管线可配置不可编程;后来部分阶段变为可编程,又加入曲面细分和几何放大阶段;最近厂商转向以网格着色器和放大着色器为中心的新管线(见 网格着色器管线)(卷 II 11.4.5)。
  • 统一着色器:早期顶点着色器指令集比像素着色器复杂,两者是不同的核心,像素核心更简单、更省面积。ATI 为 Xbox 360 引入统一着色器架构,NVIDIA 和 AMD 随后跟进。

与 RTR4 的分歧:GeForce 3 支持哪种可编程着色器

来源说法
RTR4(本页「可编程着色和 API 的演进」表)2001 年 GeForce 3 是第一个支持可编程顶点着色器的 GPU(DirectX 8.0)
GEA 卷 II 11.4.5最早的可编程 GPU GeForce 3 (NV20) 只提供可编程像素着色器,用表达力很有限的汇编编写,可编程顶点着色器"不久之后"才加入

两书说法相反,这里并列记录。

原书问题(11.4.5):GEA 的说法不对。GeForce 3(NV20,2001)同时支持 DirectX 8.0 的顶点着色器 1.1 和像素着色器 1.1;它的顶点着色器("nfiniteFX" 引擎中的可编程顶点处理器)反而比像素部分更接近真正的可编程处理器——像素着色器 1.x 只有很少的算术指令,更像可配置的寄存器组合器(Wikipedia "GeForce 3 series";Wikipedia "High-Level Shader Language" 的着色器模型对照表)。RTR4 的表述正确。 另:"ATI 为 Xbox 360 引入统一着色器,NVIDIA 和 AMD 随后跟进"一句中,AMD 在 2006 年收购了 ATI,跟进的 PC 产品是 NVIDIA G80(GeForce 8800,2006 年 11 月)和 ATI/AMD 的 R600(Radeon HD 2900,2007 年),并非 ATI 之外另有一家 AMD(Wikipedia "Unified shader model")。

着色器模型、着色器配置与 caps 位

  • 着色器模型 (Shader Model) 是描述着色器能力的版本号;每种着色器还有着色器配置 (shader profile),前缀表示类型,后跟主次版本,如 ps_2_0(像素)、vs_(顶点)、cs_(计算)、gs_(几何)。SM1 到 SM3 能力逐步增强,但指令数、常量和寄存器都很受限,顶点和像素着色器用完全不同的 ISA;从 SM4 开始统一架构,几乎所有着色器共用一套 ISA。
  • 早期 PC 游戏靠查询 GPU 的 caps 位(每位对应一个硬件特性)来适配不同硬件,组合繁多,容易出兼容性 bug。DirectX 11 的特性级别把它简化成线性序列,见 图形 API 设备、管线状态与资源。

HLSL 的数据类型

GEA 用 HLSL 讲着色器编程,说明 GLSL、PSSL 等语言大同小异(卷 II 11.4.10):

  • 标量:bool、int、uint、half(16 位浮点)、float、double,以及 min10float 这类只保证最低位宽的类型。
  • 向量 float4(或 vector<float,4>)、矩阵 float3x4(或 matrix<float,3,4>)、纹理与缓冲、纹理采样器、结构体(按值传递,没有指针和引用之分)、typedef。
  • 分量可按 xyzw 或 rgba 访问,可重排 (swizzle),如 bar.xyz = foo.zxy;。
  • 重要区别:着色器里的四元向量不是 SIMD 的四个通道。GPU 着色器跑在 16、32 或 64 通道的 SIMD 核心上,每个通道各运行一次内核;向量运算会被编译成逐分量的串行指令。这与在 CPU 上写 SIMD 代码完全不同(对照 SIMD 向量化)。

动态数据、uniform 数据与语义

  • 动态数据:每次调用处理的顶点、图元或片元,类型一致但值各不相同;uniform 数据:一次 draw call 内所有调用看到的相同值,如常量缓冲中的模型-观察矩阵和投影矩阵。这与本页「输入类型」表中的 varying 与 uniform 对应。
  • 语义 (semantics) 是给变量和结构体成员加的元信息。早期语义按阶段定义,如 POSITION[n] 只能用于顶点着色器、COLOR[n] 只能用于像素着色器;DirectX 10 起有以 SV_ 开头的系统值语义,所有阶段都可用。像素着色器只能输出 SV_Depth 和 SV_Target(GEA 原话;不完整:像素着色器还可输出 SV_Coverage、SV_StencilRef,以及 SV_DepthGreaterEqual/SV_DepthLessEqual 等保守深度变体,见 MS Learn "Semantics" 系统值表);SV_VertexID、SV_InstanceID、SV_IsFrontFace 只能作为第一个能解释它的阶段的输入,之后各阶段只能原样传递。

一对顶点/像素着色器的结构

书中示例的要点(卷 II 11.4.10.5):

  • 两个常量缓冲:每相机常量(c_viewProj、相机位置)和每物体常量(c_modelToWorld),外加一个采样器和一张漫反射纹理。
  • 顶点着色器把模型矩阵与观察投影矩阵相乘,输出裁剪空间位置(SV_POSITION)和世界空间位置,法线用 w=0 乘模型矩阵后归一化,颜色和 UV 原样传递。HLSL 中写作 mul(float4(p,1), M),即 GEA 一贯的行向量约定(见 齐次坐标与矩阵约定)。
  • 像素着色器拿到的是三次顶点着色器输出插值后的结果,所以要重新归一化法线(线性插值不保持长度),然后算 Blinn-Phong 光照,乘以纹理颜色和顶点颜色输出到 SV_TARGET。
  • 原书问题(11.4.10.5 示例代码):像素着色器调用 BlinnPhong(fragment.position, fragment.normal),但 fragment.position 带 SV_POSITION 语义,到像素着色器时已是屏幕空间像素坐标(xy 为像素中心、z 为深度、w 为 1/w1/w),不是世界空间位置;示例中顶点着色器专门输出的 worldPos 才是光照需要的位置,应写成 BlinnPhong(fragment.worldPos, fragment.normal)(MS Learn "Semantics":SV_Position 在像素着色器中描述像素位置)。另外法线用 w=0w=0 乘模型矩阵只在没有非均匀缩放时正确,否则需要逆转置矩阵(见 基本仿射变换)。

LearnOpenGL 的视角:编译、链接与 Shader 封装(Getting Started / Shaders)

来源:Shaders;摘录 Getting Started — Shaders。

GLSL 源码在运行时(或离线)经 glCreateShader → glShaderSource → glCompileShader,再 glCreateProgram → glAttachShader → glLinkProgram。编译/链接失败时用 glGetShaderInfoLog / glGetProgramInfoLog 取日志——调试着色器几乎全靠这些日志与「把中间量画成颜色」。

LearnOpenGL 的 Shader 类把路径读入、编译链接、错误打印、use 与一组 setBool/setInt/setFloat(内部 glGetUniformLocation)收进单一对象。这与 GEA/RTR 讨论的「着色器配置 / 材质绑定」同一层抽象,只是示例停在最小可用封装,没有变体排列与反射系统(变体见 着色频率与材质系统)。

  1. realtime_rendering_4ed.pdf 第 3 章 The Graphics Processing Unit
  2. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  3. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.4.5、11.4.10 节
  4. Getting Started — Shaders
  5. learnopengl.com/Getting-started/Shaders

相关页面

图形学渲染管线Rendering Pipeline渲染管线是实时图形的核心:给定虚拟相机、三维物体、光源等,把场景转换成一张二维图像。RTR4 把它粗分为四个功能阶段:应用、几何处理、光栅化、像素处理,各阶段并行执行,整体速度由最慢的阶段决定。图形学GPU 并行架构GPU Parallel ArchitectureGPU 为吞吐量而设计:芯片大部分面积是成千上万个着色器核心,而不是缓存和控制逻辑。它靠在大量线程组 (warp/wavefront) 之间快速切换来隐藏访存延迟,用 SIMD 让一组线程锁步执行同一条指令。理解占用率和线程分歧,是写出高效着色器的关键。图形学顶点着色器Vertex Shader顶点着色器是程序员直接控制的第一个管线阶段,逐个处理输入顶点,至少要输出顶点在齐次裁剪空间里的位置。它不能创建或删除顶点,顶点之间也不能传递结果,正因如此可以大规模并行。图形学曲面细分与几何着色器Tessellation & Geometry Shader曲面细分、几何着色器和流输出是顶点着色器之后的三个可选阶段。曲面细分把曲面描述按需生成合适数量的三角形;几何着色器能把图元变成别的图元,但效率不好、实际用得少;流输出把处理后的顶点写回内存以便复用。图形学像素着色器Pixel Shader像素着色器(OpenGL 里叫片元着色器)对光栅化产生的每个片元运行,用插值后的顶点数据算出颜色,也可以输出不透明度、修改深度或直接丢弃片元。多渲染目标让它一次输出多组数据,催生了延迟着色。它通常只能写自己所在的像素,但能通过 2×2 quad 拿到相邻像素的导数。图形学合并阶段与帧缓冲Merging & Framebuffer合并阶段把像素着色算出的片元颜色和缓冲区里已有的颜色结合起来,同时负责深度测试、模板测试和混合。它通常不可编程但高度可配置,也叫 ROP(光栅操作或渲染输出单元)。帧缓冲是系统中所有缓冲区的总称。图形学计算着色器Compute Shader计算着色器是 DirectX 11 引入的一种 GPU 计算方式:它不固定在图形管线的某个位置,但由图形 API 调用,和其他着色器共用统一着色器核心。它暴露了线程组和组内共享内存,最大的优势是能直接处理 GPU 上生成的数据,不用来回传给 CPU。图形学着色频率与材质系统Shading Frequency & Material Systems实现着色时要按"计算频率"划分工作:整个 draw call 不变的放到应用端作为 uniform,其余放在相应的可编程阶段。实践中大多数着色在像素级完成,顶点着色器负责变换和变形。材质系统把面向美术的材质与底层着色器组织起来,处理海量着色器变体的组合问题。游戏开发SIMD 向量化SIMD VectorizationSIMD(单指令多数据)用一条机器指令并行处理多个数据。x86 上从 MMX 发展到 SSE(128 位,4 个 float)、AVX(256 位,8 个)和 AVX-512(512 位,16 个)。GEA 用 SSE 内建函数演示:数组相加、点积、向量×矩阵、矩阵×矩阵,强调两点——避免寄存器内的水平相加(先转置数据),以及把 SIMD 寄存器看成 N 条并行的「通道 (lane)」而不只是一个 4D 向量;条件分支则用向量谓词化 (vector predication / select) 代替。图形学图形 API 设备、管线状态与资源Graphics API Devices, Pipeline State & Resources所有图形 API(OpenGL、DirectX、Vulkan、Metal)都围绕同一组概念:代表 GPU 的设备、描述各阶段配置的管线状态、承载数据的资源及其视图。GEA 把 API 分成高层(OpenGL、D3D11 及更早)和底层(D3D12、Vulkan、Metal、PlayStation 的 Gnm),并介绍 D3D11 的特性级别、设备上下文与延迟上下文、D3D12 的命令列表、PSO 和根签名。本页按 GEA 的整理讲这些跨 API 的共同概念,不是任何一个 API 的教程。图形学网格着色器管线Mesh Shader Pipeline网格着色器管线(也叫网格几何管线)是 GPU 厂商为取代传统光栅管线前段而推出的新架构:光栅化器和像素着色器不变,网格着色器和可选的放大着色器(任务着色器)接管顶点、外壳、域和几何着色器的全部工作。它以类似计算着色器的线程组处理叫 meshlet 的小批图元,像启动计算着色器一样启动绘制,还能把部分应用层工作搬到 GPU 上。2019 年进入 DirectX 12,2022 年以 VKEXTmeshshader 扩展进入 Vulkan。游戏数学齐次坐标与矩阵约定Homogeneous Coordinates & Matrix Conventions实时渲染用 4×4 矩阵和四元素齐次坐标统一表示点和方向:方向向量 w=0,点 w=1。这样线性变换与平移可以合成为仿射变换并连乘。RTR4 采用列向量记法、右手观察坐标系;DirectX 采用行向量记法,书中强调这只是记法差异。游戏数学基本仿射变换Basic Affine Transforms平移、旋转、缩放、切变是摆放和变形物体的基础矩阵;它们的连乘顺序有关、但满足结合律。刚体变换的逆可以直接写出,法线要用伴随矩阵的转置(传统说法是逆转置)变换。理解这些矩阵的性质能写出更快、更正确的变换代码。图形学着色方程与经典光照模型Shading Equation & Classic Lighting ModelsGEA 把渲染方程在两条简化假设下化成的求和式称为着色方程:只有理想化的点光源和方向光,间接光只用一个环境光项粗略近似。配上 Lambert、Phong、Blinn-Phong 这类简单 BRDF,它给不出照片级真实感,但能产生可信的画面,是早期游戏的全部光照,也是理解 BRDF 用法的最好入门(GEA4 卷 II 12.4、12.5.1)。图形学片元着色器与 GLSL 基础Fragment Shaders & GLSL Basics片元着色器(fragment shader)是在每个像素(片元)上并行执行的小程序:输入位置等信息,输出颜色。GLSL 是 OpenGL / OpenGL ES / WebGL 共用的着色语言;本书用全屏 billboard 上的像素着色练习,建立程序化图形的基础心智模型。游戏开发 · UE5Custom 材质表达式Custom Material ExpressionCustom 节点允许在材质图里直接写 HLSL:任意数量输入、指定输出类型,可选额外输出、define 与外部 .ush/.usf 包含路径。它是「内置节点做不到」时的逃生口;代价是无法常量折叠、输入仅函数局部可见、且必须是合法 HLSL。优先用内置节点与材质函数,只在必要时上 Custom。游戏开发 · UE5材质图到 HLSL 的翻译Material Graph to HLSL Translation材质编辑器点 Apply 之后,引擎先把节点图翻译成 HLSL,再把 HLSL 编译成 GPU 字节码。翻译以 MaterialTemplate.ush 为模板、由 FHLSLMaterialTranslator 做文本替换与常量折叠;内置节点能被折叠,Custom 材质表达式 则不能。每个材质还会按特性/平台/用途生成多份着色器排列。游戏开发 · UE5材质系统Material SystemUE 材质 (Material) 是用节点图描述「表面如何与光交互」的着色器生成器。它有三个正交维度:着色模型 (Shading Model) 决定光照模型(Default Lit、次表面、清漆、毛发等),混合模式 (Blend Mode) 决定与背景的合成公式(Opaque/Masked/Translucent…),参数 + 材质实例 (Material Instance) 让美术在不重编译着色器的前提下做变体。PBR 输入(Base Color/Roughness/Metallic/Specular)直接对应 RTR4 的金属度-粗糙度工作流;最大的工程坑是静态参数导致的着色器排列爆炸。

被这些页面引用2 篇

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原