想象一家繁忙的餐厅后厨:食材从进货口进入,经过洗、切、炒、装盘,最后端到顾客面前。这条固定工序就是图形渲染的“流水线”。在这条流水线上,大多数厨师都老老实实待在自己的岗位上,但有两位员工格外特别。一位是站在切菜和摆盘之间的“创意配菜师”——几何着色器,他可以在装盘前临时加料、改刀,把一块豆腐变成一簇小方块;另一位是“自由杂工队”——计算着色器,他们根本不排流水线,哪里忙就冲到哪里,能同时洗几百个杯子、揉几百个面团。几何着色器的创造力让画面更丰富,但动作太猛会把出菜口堵死;计算着色器的灵活与并行让 GPU 干更多杂活,但要调度得当才不会互相拥挤。搞懂了这两位员工的分工和脾气,你就掌握了现代 GPU 编程的一对重要搭档。

岗位关联

对于 AI 从业者来说,理解几何着色器和计算着色器能帮助你判断“哪些视觉效果该在 GPU 上生成、哪些计算该用并行加速”,是从事图形学、游戏引擎、实时渲染相关岗位的基础功。

目录
  1. 可视化演示
  2. 渲染管线全家福
  3. 几何着色器定位
  4. 图元增删魔法
  5. 性能瓶颈警示
  6. 计算着色器破局
  7. 计算着色器实战
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

渲染管线全家福

把 GPU 想象成一家餐厅后厨的流水线。食材从进货口进入,按固定工序被清洗、切块、炒制、装盘。图形渲染也有这样一条“出菜流水线”,我们叫它渲染管线。每一个步骤都像固定工位,数据必须依次通过,直到最终画面的像素端上屏幕。

渲染管线从输入装配开始,把三角形、线段等“食材”送入,依次经过顶点着色器、几何着色器、光栅化、片元着色器。顶点着色器负责逐顶点变换,把 3D 坐标映射到屏幕空间;光栅化把数学三角形变成像素碎片;片元着色器为每个像素涂上颜色。GPU 真正厉害的地方在于,成千上万个顶点可以同时通过这些工位,这就是并行。

要理解这条流水线的“食材量”,最简单的起点是顶点数量。一个三角形由三个顶点组成,所以送入顶点着色器的顶点总数等于三角形数乘 3。

后厨的切菜数目永远是锅数乘三。理解了这份“家底”,再看几何着色器时,就会明白它为何能突然打破这种稳定的节奏。

Nvertex=3×NtriangleN_{\text{vertex}} = 3 \times N_{\text{triangle}}
如果场景里有 100 个三角形,GPU 就要处理 300 个顶点。
  1. 三角形图元数$N_{\text{triangle}}$ 表示送入管线的三角形数量,是几何数据的基本单位。
    NtriangleN_{\text{triangle}}
  2. 单个三角形顶点数每个三角形由 3 个顶点组成,所以顶点数是三角形数乘以 3。
    3×Ntriangle3 \times N_{\text{triangle}}
  3. 顶点总量$N_{\text{vertex}}$ 就是最终顶点着色器需要处理的顶点个数。
    Nvertex=3×NtriangleN_{\text{vertex}} = 3 \times N_{\text{triangle}}

几何着色器定位

创意配菜师站在切菜和摆盘之间。食材已经切成块,但还没摆上盘;他可以临时改刀,把一块豆腐切成更多小块,也可以把两片肉合成一份。这就是几何着色器的角色:它在顶点着色器和光栅化之间,拥有对图元“增删改”的能力。

几何着色器是一个可编程阶段,输入是一个完整的图元——点、线段或三角形,输出可以是零个、一个或多个图元。它最特别的地方是“一对多”:输入一个点,可以输出一个四边形作为粒子;输入一个三角形,可以输出一圈新三角形形成轮廓。具体输出多少,完全由开发者编写的一段 shader 程序决定。

输入输出关系可以写成:

配菜师虽然自由,但“每份最多切成多少块”是有规定的,否则后厨的盘子储备和出菜通道都会出问题。

Mout=kmax×MinM_{\text{out}} = k_{\text{max}} \times M_{\text{in}}
每个输入图元最多能“变”出 $k_{\text{max}}$ 个图元,所以输出总数是输入数乘以上限。
  1. 输入图元数$M_{\text{in}}$ 指进入几何着色器的图元个数。
    MinM_{\text{in}}
  2. 最大生成倍数$k_{\text{max}}$ 是硬件或 API 允许的单个输入图元最大输出数量。
    kmaxk_{\text{max}}
  3. 输出图元数$M_{\text{out}}$ 是几何着色器实际生成的图元总数,不能超过 $k_{\text{max}} \times M_{\text{in}}$。
    Mout=kmax×MinM_{\text{out}} = k_{\text{max}} \times M_{\text{in}}

图元增删魔法

配菜师有两招杀手锏:一招是“一菜变多菜”,把一个点变成一片飞溅的粒子;另一招是“给菜描边”,沿着三角形轮廓再贴一圈新三角形。这两招正是几何着色器最经典的应用:粒子发射与轮廓扩展。

粒子发射在游戏里用来做烟花、爆炸、喷泉:GPU 每收到一个输入点,就生成 m 个矩形小面片作为粒子,每个粒子再经过后续渲染变成动态画面。轮廓扩展用于卡通描边:把三角形每条边向外扩展,生成新的三角形形成边带,让物体轮廓看起来更粗、更有风格。

公式化描述:

配菜师每一次加料都有代价:加得越多,后面装盘师傅越累。所以 m 要根据场景精心设定,并不是越大越好。

Nout=m×NinN_{\text{out}} = m \times N_{\text{in}}
输入 1 个点,最多变成 m 个粒子;输入 1 个三角形,最多可以扩展成 m 个新三角形。
  1. 输入图元数$N_{\text{in}}$ 是送入几何着色器的图元数量。
    NinN_{\text{in}}
  2. 生成倍数$m$ 是每个输入图元输出的新图元数,粒子发射常设为几十,轮廓扩展常用 3。
    mm
  3. 输出图元数$N_{\text{out}}$ 是生成总数,光栅化阶段将收到这么多图元。
    Nout=m×NinN_{\text{out}} = m \times N_{\text{in}}

性能瓶颈警示

创意配菜师如果“一开百”,出菜口瞬间堆满盘子,后厨乱成一锅粥。几何着色器的麻烦正是输出量不可预测。它不像传统固定流水线那样每个阶段工作量稳定,而是允许一个线程生成一百个图元,另一个线程一个都不生成,这会让 GPU 的并行负载严重不均。

更麻烦的是,很多几何效果需要访问相邻图元的信息。比如轮廓扩展要找到三角形邻边,但几何着色器默认只拿到自己负责的那一个图元,想获取邻居数据就得跨线程通信,而跨线程通信在 GPU 上的成本非常高。这也是几何着色器逐渐被实例化渲染、曲面细分等方案替代的原因之一。

我们用放大率来衡量风险:

后厨总结:创意很好,但要克制。滥用创意配菜师,流水线会从“高效”变成“堵车”。

R=NoutNinR = \frac{N_{\text{out}}}{N_{\text{in}}}
输出图元数除以输入图元数。R=3 意味着每个输入图元平均变成 3 个,几何量放大了三倍。
  1. 输入与输出图元数$N_{\text{in}}$、$N_{\text{out}}$ 分别是进入和离开几何着色器的图元数量。
    Nin,NoutN_{\text{in}}, N_{\text{out}}
  2. 比值 $R$表示几何膨胀倍数。
    R=NoutNinR = \frac{N_{\text{out}}}{N_{\text{in}}}
  3. 风险判断当 $R$ 远超 1 时,光栅化和缓存压力快速上升,容易成为瓶颈。
    R≫1R \gg 1

计算着色器破局

后厨里还有一支“自由杂工队”,不按流水线走,哪里缺人就冲到哪里。这就是计算着色器的设计哲学:不一定要排队经过顶点、光栅化,而是直接从 GPU 拿到一堆数据并行处理,干完活直接写回结果。

计算着色器不属于图形渲染固定管线的一部分。它可以读写纹理和缓冲区,执行任意数学与逻辑运算,是通用 GPU 计算的基石。它引入了两个核心概念:线程(thread)和工作组(workgroup)。一个任务被分成多个工作组,每个工作组里有若干个线程;线程之间可以通过共享内存协作。

总线程数可以用维度乘积表示:

自由杂工队不占出菜流水线,能干各种杂活,但人数规模要算清楚,不然调度器也会头大。

T=Gx×Gy×Gz×Lx×Ly×LzT = G_x \times G_y \times G_z \times L_x \times L_y \times L_z
总线程数等于三个方向的工作组数相乘,再乘以每个工作组三个方向的线程数。
  1. 工作组数量$G_x,G_y,G_z$ 表示三维网格中每个方向的工作组个数。
    Gx×Gy×GzG_x \times G_y \times G_z
  2. 局部线程数$L_x,L_y,L_z$ 表示一个工作组内每个方向的线程数。
    Lx×Ly×LzL_x \times L_y \times L_z
  3. 总线程数两者相乘得到总共并行执行的线程数。
    T=Gx×Gy×Gz×Lx×Ly×LzT = G_x \times G_y \times G_z \times L_x \times L_y \times L_z

计算着色器实战

自由杂工队最常被请去干三类活:重新布置餐厅(后处理)、搬重物(物理模拟)、清理噪点(光线追踪降噪)。

后处理时,整张图像可以看作一个大数组,一个线程负责一个像素,并行执行滤镜或特效。物理模拟时,一个线程对应一个粒子或一个网格点,更新位置和速度。光线追踪降噪时,计算着色器用时空滤波器平滑蒙特卡洛渲染产生的噪声,让画面快速变得干净。

衡量并行收益的标准是加速比:

杂工队干活要分工合理、排队有序,才能真正跑赢一个人埋头苦干。这也是 GPU 编程的魅力与挑战。

S=TserialTparallelS = \frac{T_{\text{serial}}}{T_{\text{parallel}}}
串行执行时间除以并行执行时间。S=8 说明用 GPU 比单个 CPU 快 8 倍。
  1. 串行执行时间$T_{\text{serial}}$ 是一个线程/核心顺序处理所有任务所需时间。
    TserialT_{\text{serial}}
  2. 并行执行时间$T_{\text{parallel}}$ 是 GPU 上所有线程共同处理所需时间。
    TparallelT_{\text{parallel}}
  3. 加速比$S$ 大于 1 代表并行有效,越大效果越好。
    S=TserialTparallelS = \frac{T_{\text{serial}}}{T_{\text{parallel}}}

AI 应用场景

游戏实时粒子特效

阶段:图形渲染前处理/动画生成

解决的问题:传统做法把大量粒子顶点提前存储在 CPU 中,占用显存且逐帧更新慢。几何着色器在 GPU 端实时从一个点扩展到多个粒子,减少 CPU 传输,可快速生成烟花、喷泉、爆炸碎片。

具体案例:某开放世界游戏中,玩家点燃篝火,粒子系统用几何着色器从每个火源点生成数十个火星粒子,再配合计算着色器更新粒子速度,实现上百处篝火同时燃烧而不掉帧。

物理模拟与场景交互

阶段:物理仿真/碰撞计算

解决的问题:布料、水体等物理模拟需要大量并行运算,CPU 串行处理很难实时。计算着色器让每个线程负责一个质点或网格单元,并行更新位置、速度与约束。

具体案例:虚拟展厅中,窗帘、旗帜和角色衣摆使用计算着色器驱动,每帧更新数万质点位置,交互时拖动布料也能实时变形。

光线追踪降噪

阶段:渲染后处理

解决的问题:光线追踪每像素样本数少时噪声大,样本数多则耗时大。计算着色器对噪点图像做时空滤波,把多个历史帧数据合并,快速得到干净画面。

具体案例:建筑可视化软件用计算着色器实现降噪:每像素 4 个样本的噪点画面,经过约 10ms 滤波后输出接近 64 样本的干净结果,大幅缩短渲染时间。

Python 演示

python32 行
import numpy as np  def geom_shader_expand(points, m=6, radius=0.1):    """模拟几何着色器: 每个输入点发射 m 个粒子, 返回形状 (n*m, 2)"""    n = points.shape[0]    angles = np.arange(m) * 2.0 * np.pi / m    offsets = np.column_stack([np.cos(angles), np.sin(angles)]) * radius    expanded = points[:, None, :] + offsets[None, :, :]    return expanded.reshape(n * m, 2)  def compute_shader_update(particles, velocity, dt=0.01):    """模拟计算着色器: 所有粒子的位置同步更新"""    particles[:, 0] += velocity[:, 0] * dt    particles[:, 1] += velocity[:, 1] * dt    return particles  def run():    np.random.seed(42)    input_points = np.random.rand(8, 2)          # 8 个输入点    particles = geom_shader_expand(input_points, m=6, radius=0.15)    velocity = np.random.randn(len(particles), 2) * 0.5    for _ in range(50):        compute_shader_update(particles, velocity)    print("粒子数量:", particles.shape[0])    print("前三个粒子位置:\n", particles[:3])  if __name__ == "__main__":    run()
目的:用 NumPy 模拟几何着色器的“一个输入点生成多个粒子”,以及计算着色器的“所有粒子并行同步更新”。
  1. angles = np.arange(m) * 2.0 * np.pi / m把圆周均分成 m 份,得到粒子发射方向。
  2. offsets = np.column_stack([np.cos(angles), np.sin(angles)]) * radius生成 m 个方向上的单位偏移量。
  3. expanded = points[:, None, :] + offsets[None, :, :]用广播把每个输入点复制成 m 个,实现“一对多”。
  4. particles[:, 0] += velocity[:, 0] * dt模拟每个线程并行更新自己负责的粒子 x 坐标。
  5. compute_shader_update(particles, velocity)一次调用更新全部粒子,体现 GPU 并行。

常见误区

误区很多初学者以为几何着色器像“无限打印”一样,想要多少粒子直接生成多少,而且不影响性能。

正解每个输入图元的输出数量有上限 $k_{\text{max}}$,而且生成越多,光栅化和缓存压力越大。就像配菜师虽然创意无限,但后厨的盘子和灶台有限;无节制“一开百”会让出菜口堵死。恰当做法是配合实例化渲染或计算着色器生成数据。

为何常见:几何着色器的示例常展示从 1 个点生成大量粒子的惊艳效果,容易让人忽略背后的硬件代价。

误区一听“计算”着色器,就觉得它只适合做矩阵乘法、物理公式之类的纯数值计算,跟图形渲染没什么关系。

正解它虽然不经过传统图形管线,但可以读写纹理、缓冲区,是后处理、粒子系统、光线追踪降噪等图形任务的利器。好比自由杂工队虽然不炒菜,却能重新布置餐桌、清洗餐具、搬运物料,样样关乎餐厅运营。

为何常见:“计算”这个词太有误导性,让人忽略了它操作图形资源的能力。

误区有人觉得计算着色器功能那么强,以后渲染不再需要顶点/片元着色器,管线会消失。

正解计算着色器是图形管线的补充而非替代。最终画面的生成仍然需要光栅化、深度测试等固定硬件流程。自由杂工队可以帮忙备菜,但不能取代洗切炒的整套流水线。

为何常见:看到计算着色器能做很多渲染任务,就误以为它是“万能替代品”。

误区两者都能增加几何细节,初学者容易把它们画上等号,认为相互替换没问题。

正解曲面细分按固定拓扑均匀增加顶点,由硬件控制,负载可预测;而几何着色器允许任意增删图元,灵活但低效。好比一个是用模具把面团分成统一大小的小球,另一个是手工捏各种形状,后者更自由但更慢。

为何常见:两者都在“增加几何体”,功能边界容易被混淆。

误区把任务丢给 GPU 就默认“并行=更快”,结果某些场景反而变慢,于是怀疑 GPU 不行。

正解并行性能依赖访存模式、负载均衡、同步开销。大量线程空转、全局内存频繁访问都会抵消并行收益。就像杂工队都挤在一个门口进进出出,人越多反而越堵。需要优化内存访问、合并写入,并衡量加速比。

为何常见:单纯从“人多力量大”的直觉出发,忽略了并行系统的调度成本。

面试题

Q1. 渲染管线的正确顺序中,几何着色器位于哪个阶段之间?

单选题 · easy

  • A.顶点着色器与光栅化之间← 正确答案
  • B.光栅化与片元着色器之间
  • C.片元着色器与混合器之间
  • D.输入装配与顶点着色器之间

答案:A。几何着色器在顶点着色器和光栅化之间,负责对图元增删改。A 正确;其他位置都错误。用后厨比喻,配菜师站在切菜与摆盘之间。

Q2. 下列哪项不是几何着色器的合法输入图元?

单选题 · easy

  • A.单个点图元
  • B.一条线段图元
  • C.一个三角形图元
  • D.一个像素颜色值← 正确答案

答案:D。几何着色器输入是点、线、三角形等基本图元;像素颜色值是光栅化后的产物,不能作为几何着色器的输入。就像配菜师只能处理“食材块”,不能处理已经上桌的菜。

Q3. 计算着色器的核心设计思路是什么?

单选题 · easy

  • A.完全替代固定渲染管线
  • B.绕过图形管线做并行计算← 正确答案
  • C.自动优化所有算法
  • D.无需管理线程资源

答案:B。计算着色器不经过固定图形管线,直接用 GPU 并行执行通用计算,可读写缓冲和纹理。它不能替代管线,也不会自动优化,线程资源仍需程序员管理。

Q4. 轮廓扩展中,一个输入三角形通常生成几个新三角形?

单选题 · mid

  • A.只生成 1 个新三角形
  • B.只生成 2 个新三角形
  • C.只生成 3 个新三角形← 正确答案
  • D.只生成 4 个新三角形

答案:C。典型轮廓扩展沿三角形三条边各生成一个新三角形,所以 m=3。配菜师给三角形“描边”,绕一圈正好三个。

Q5. 几何着色器最主要的性能瓶颈来自何处?

单选题 · mid

  • A.输出图元被大量放大← 正确答案
  • B.输出图元被立即缓存
  • C.输出图元被自动压缩
  • D.输出图元被延迟处理

答案:A。几何着色器可让输出图元数量暴增,导致光栅化和缓存压力剧增,还可能造成负载不均衡。后厨里“一道菜变一百道”就会堵住出菜口。

Q6. 以下哪些任务适合交给计算着色器完成?

多选题 · mid

  • A.后处理滤镜与特效← 正确答案
  • B.形状边缘轮廓扩展← 正确答案
  • C.物理碰撞与粒子模拟← 正确答案
  • D.仅限整数数学运算

答案:ABC。计算着色器适合图形后处理、粒子物理、轮廓提取等并行任务;但它不限于整数运算,浮点/位操作都能做。D 错误。自由杂工队能干各种杂活,但不等于只搬一类货。

Q7. 计算着色器中“总线程数”如何计算?

单选题 · mid

  • A.工作组数乘以每组线程数← 正确答案
  • B.工作组数除以每组线程数
  • C.工作组数加上每组线程数
  • D.工作组数减去每组线程数

答案:A。总线程数=(各方向工作组数量乘积)×(各方向局部线程数乘积)。一维可理解为“组数×每组人数”,所以选 A。

Q8. 关于几何着色器和计算着色器的说法,正确的是?

多选题 · hard

  • A.几何着色器在顶点与光栅化之间← 正确答案
  • B.计算着色器能访问共享内存← 正确答案
  • C.几何着色器适合无限制生成图元
  • D.计算着色器可做后处理与降噪← 正确答案

答案:ABD。A 和 D 是两种着色器的典型特征;B 正确,工作组的共享内存可加速协作;C 错误,几何着色器输出有上限且受性能约束。创意配菜师不能无限制加菜。

Q9. 每个输入点最多发射 m 个粒子,输入 N 个点时输出粒子数上限是多少?

单选题 · extreme

  • A.N 加上 m
  • B.N 乘以 m← 正确答案
  • C.N 除以 m
  • D.m 的 N 次方

答案:B。几何着色器输出总数 = 输入图元数 × 每个输入的最大输出数,即 N×m。不是加减除,更不是指数爆炸;后厨一盘菜最多切 m 块,N 盘就是 N×m 块。

Q10. 哪些原因会导致几何着色器成为性能瓶颈?

多选题 · hard

  • A.输出图元数量不确定← 正确答案
  • B.跨线程交换顶点数据← 正确答案
  • C.光栅化阶段负载过重
  • D.屏幕分辨率设置过高

答案:AB。输出不可控会造成负载失衡,跨线程通信成本高;C 是瓶颈造成的下游表现而非原因,D 主要影响像素阶段而非几何阶段。配菜师的“自由发挥”和“借调料”都需要付出代价。