想象你经营一家玩具工厂,老板(CPU)下达命令:批量生产十万只“发光恐龙”。工厂里有一条长长的传送带,叫作渲染管线。传送带的不同工位上站着四类师傅:摆位师傅守在入口,把每块恐龙零件按图纸放到三维空间的正确位置;装配师傅站在中段,能把一块大零件敲成好几块,也能直接把废料扔下传送带;上色师傅守在末端,拿着喷枪给恐龙表面的每一个小点涂上颜色——涂多少、涂什么色,全由他说了算;厂房二楼还有一间独立实验室,里面的计算员不碰传送带,专门用几千人同时开工,做物理模拟、粒子运动这类通用计算。 这四类师傅正是 GPU 上四种可编程的着色器:顶点着色器、几何着色器、片元着色器与计算着色器。他们使用的“工作指令语言”叫 HLSL,工厂的管理规章叫 ShaderLab。这篇文章带你走进这座玩具工厂,把 GPU 流水线的四道工序、两种语言,以及它们在程序化特效和 GPU 粒子系统中的真实用法,一次讲明白。

岗位关联

对 AI 从业者而言,Shader 是理解 GPU 并行计算的钥匙:图形渲染与神经网络训练共享同一套大规模并行架构,读懂着色器,你就读懂了 GPU 的底层思维。

目录
  1. 可视化演示
  2. 渲染管线总览
  3. 顶点着色器
  4. 几何着色器
  5. 片元着色器
  6. 计算着色器
  7. HLSL语法基础
  8. Shader Lab实战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示GPU渲染管线VS-GS-RS-FS-FB阶段衔接、顶点着色器模型到裁剪空间变换矩阵链、片元着色器纹理采样光照逐像素计算及计算着色器线程组并行GPU粒子,含数据流背景动画。

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

渲染管线总览

工厂效率的秘诀不是某一台机器有多快,而是整条传送带能同时开工:第一个零件还在摆位师傅手里时,第二个零件已上了传送带,上色师傅也没闲着。GPU 的渲染管线正是这样一条流水线,每一帧画面上百万个三角形鱼贯而过,各个阶段同时忙碌。早期 GPU 是“固定管线”——工位上的师傅是被焊死的机器人,只能做固定动作。后来 GPU 开放了“可编程管线”,把关键工位换成可以写代码的师傅,于是有了四类可编程着色器。

为什么 GPU 能扛住百万级三角形?因为它的“人手”极多。一块 GPU 有上千个计算核心,每个顶点分给一个核心同时处理。理想情况下:

回到玩具工厂:传送带在转,每个工位都有几十个师傅并排站岗,这就是 GPU 并行之美。接下来,我们去见四位师傅本人。

T并行=n×tm\displaystyle T_{\text{并行}} = \frac{n \times t}{m}
公式说人话:总共 n 个顶点、每个花 t 时间、GPU 有 m 个核心同时算,总耗时大约是串行时间(n × t)除以核心数 m。GPU 并行加速的本质,就是“用人多换时间短”。
  1. 顶点总数 n一帧画面中所有三角形的顶点数量,常见量级是几十万到几百万。
    n=顶点数量(百万级)n = \text{顶点数量(百万级)}
  2. 单点耗时 t处理一个顶点所需的时间,比如完成坐标变换、光照计算。
    t=单个顶点处理耗时t = \text{单个顶点处理耗时}
  3. 并行核心数 mGPU 上能同时干活的计算核心数量,消费级显卡就有几千个。
    m=GPU 并行核心数m = \text{GPU 并行核心数}
  4. 总耗时 T 并行任务被平均分给所有核心后,消耗的总时间由“单批工作量”决定。
    T并行=n×tmT_{\text{并行}} = \frac{n \times t}{m}

顶点着色器

摆位师傅的日常工作:图纸(模型文件)上写着每块零件的三维坐标,但图纸坐标系和车间坐标系不一样。师傅得先把零件搬到车间的大坐标系,再对齐到摄像机“眼睛”看到的方位,最后换算成屏幕上的位置。这一套“坐标搬家”,就是顶点着色器的核心。

顶点着色器是渲染管线的第一个可编程阶段,逐个处理每个顶点:输入一个顶点的属性(位置、法线、UV 坐标),输出变换后的顶点位置和其他数据。关键特性是一个顶点进,一个顶点出——它不能凭空增加或删除顶点。顶点变换的标准操作是“矩阵三连乘”:

摆位师傅只会“摆位置”,不负责涂颜色——涂色是后面的事。就算他想改颜色,也只能把颜色数据“托付”给下一个工位,这就是图中从顶点着色器流向片元阶段的插值数据。

vclip=Mproj×Mview×Mmodel×vlocal\displaystyle \mathbf{v}_{\text{clip}} = M_{\text{proj}} \times M_{\text{view}} \times M_{\text{model}} \times \mathbf{v}_{\text{local}}
公式说人话:把顶点从“模型自己的小坐标”(v_local),经过模型矩阵 M_model 搬到世界,再经视图矩阵 M_view 搬到相机眼前,最后经投影矩阵 M_proj 压成屏幕坐标。顺序从右往左,一步不能乱。
  1. 本地坐标 v_local顶点在模型文件里的原始位置,四维向量 (x, y, z, w),w 通常为 1。
    vlocal=(x,y,z,1)⊤\mathbf{v}_{\text{local}} = (x, y, z, 1)^\top
  2. 模型矩阵 M_model负责旋转、缩放、平移,把模型从自身坐标系搬到世界坐标系。
    Mmodel=M平移×M旋转×M缩放M_{\text{model}} = M_{\text{平移}} \times M_{\text{旋转}} \times M_{\text{缩放}}
  3. 视图矩阵 M_view以摄像机为原点重新描述所有顶点,相当于把世界“重新对焦”进镜头。
    Mview=世界坐标到相机坐标M_{\text{view}} = \text{世界坐标到相机坐标}
  4. 投影矩阵 M_proj把相机坐标压成裁剪坐标,产生近大远小的透视效果。
    Mproj=透视投影矩阵M_{\text{proj}} = \text{透视投影矩阵}
  5. 矩阵乘法顺序矩阵乘法不满足交换律,最右边的矩阵最先作用于顶点。
    vclip=Mproj×(Mview×(Mmodel×vlocal))\mathbf{v}_{\text{clip}} = M_{\text{proj}} \times (M_{\text{view}} \times (M_{\text{model}} \times \mathbf{v}_{\text{local}}))

几何着色器

装配师傅比较特别:他不一定按图纸来,可以发挥主观能动性。图纸说一块大板子,他觉得拼成三块小三角才好看,就“啪”地敲成三块;觉得是废料,直接扔下传送带。这种“改图元”的能力,就是几何着色器。

几何着色器位于顶点着色器之后、片元着色器之前。它处理的不是单个顶点,而是完整的图元(primitive)——点、线段、三角形。它可以把一个三角形变成多个三角形,可以把一个点变成一簇点,也可以完全不输出(删除图元)。顶点着色器是严格的 1:1,几何着色器是 1:N。设输入图元总数为 N_in,第 i 个图元发射出 k_i 个新图元,则:

几何着色器最著名的应用,是把一个三角形“爆炸”成三块,或把一根线放大成一片草地。装配师傅的创造力常常让上色师傅加班,所以开发时要小心别让它太“高产”。

Nout=∑i=1Ninki\displaystyle N_{\text{out}} = \sum_{i=1}^{N_{\text{in}}} k_i
公式说人话:最终输出的图元总数,等于每个输入图元“生”出的新图元数量之和。k_i 可以是 0(丢弃),也可以是几百(疯狂增殖)。
  1. 输入图元数量 N_in进入几何着色器的点、线、三角形数量。
    Nin=输入图元总数N_{\text{in}} = \text{输入图元总数}
  2. 发射数量 k_i第 i 个输入图元通过发射操作输出的新图元个数,可以是 0 或任意正整数。顶点着色器永远做不到这一点。
    ki=第 i 个图元发射的图元数k_i = \text{第 } i \text{ 个图元发射的图元数}
  3. 求和符号 Σ把每个输入图元的产出全部累加起来。
    ∑i=1Ninki=k1+k2+⋯+kNin\sum_{i=1}^{N_{\text{in}}} k_i = k_1 + k_2 + \cdots + k_{N_{\text{in}}}
  4. 输出总数 N_out送到下一阶段的图元总量,决定了后续片元着色器的工作量。

片元着色器

上色师傅是整条流水线上最忙的人:最终画面上每一个可见小点(像素)都需要他喷一次漆。一块 1080p 的屏幕有 1920×1080≈207 万个像素,意味着每帧他要喷 200 多万次漆。而且颜色不能乱喷:要看这个点是正面还是侧面,光线照不照得到,纹理贴图画了什么。

GPU 把三角形“敲碎”成一个个像素片元(fragment),这个敲碎过程叫光栅化,属于固定功能阶段;之后每个片元调用一次片元着色器(也叫像素着色器)。它输入的是插值后的顶点数据(法线、UV 等),输出的是颜色值。最简单的光照模型——兰伯特漫反射,描述了“表面法线与光线的夹角决定亮度”:

上色师傅不仅会按法线打光,还会“贴墙纸”:从纹理图(UV 映射的贴图)上取色,这就是纹理采样。在玩具工厂里,他手里永远攥着一本色卡,色卡对应恐龙身上的花纹图纸。

Iout=Ilight×kd×max⁡(0,N⃗⋅L⃗)\displaystyle I_{\text{out}} = I_{\text{light}} \times k_d \times \max(0, \vec{N} \cdot \vec{L})
公式说人话:像素的最终亮度等于光源强度乘漫反射系数,再乘“光照角度因子”——表面正对光源时最亮(点积约等于 1),侧面变暗,背光面直接取 0。
  1. 表面法线 N垂直于该像素所在表面的单位向量,代表“这个点朝哪个方向”。
    N⃗=法线单位向量\vec{N} = \text{法线单位向量}
  2. 光源方向 L从表面指向光源的单位向量。
    L⃗=光照方向单位向量\vec{L} = \text{光照方向单位向量}
  3. 点积 N·L两个单位向量的点积等于夹角余弦,衡量表面朝向与光线的对齐程度。
    N⃗⋅L⃗=cos⁡θ\vec{N} \cdot \vec{L} = \cos \theta
  4. max(0, ·)把负值(背光面)钳制为 0——背光面不该有漫反射。
    max⁡(0,N⃗⋅L⃗)\max(0, \vec{N} \cdot \vec{L})
  5. 光源强度与漫反射系数I_light 是入射光强,k_d 是材质反射光的比例。
    Iout=Ilight×kdI_{\text{out}} = I_{\text{light}} \times k_d

计算着色器

二楼独立实验室里的计算员,不碰玩具传送带。他们的任务五花八门:模拟十万个气球在恐龙乐园里飘动,计算物理碰撞,甚至帮老板训练“AI 质检员”识别次品。唯一要求是:计算要到 GPU 上跑,而且要快。

计算着色器是通用 GPU 计算(GPGPU)的入口。它不在图形渲染管线的主路径上,而是通过 Dispatch 命令直接调度一大群“线程”并行执行同一段代码。它没有顶点、图元、像素的概念,只有“线程”和“数据”。它的调度模型是“分组”的:所有线程被划分为多个线程组(thread group),每个组里又有许多线程。为了在代码里定位“我是谁、该处理哪块数据”,需要计算全局线程编号:

GPU 粒子系统就是计算着色器的代表作:每颗粒子是一份数据,十万个线程并行更新它们的位置,再用顶点、片元流水线渲染出来。玩具工厂的“十万个气球”,就是这么被算出来的。

globalID=groupID×groupSize+localID\displaystyle \text{globalID} = \text{groupID} \times \text{groupSize} + \text{localID}
公式说人话:全校两千人做操站成 40 个方阵,每阵 50 人。想知道你在全校的第几个位置,用“方阵编号 × 每阵人数 + 阵内序号”即可。HLSL 里分别对应 SV_GroupID、SV_GroupThreadID 与 SV_DispatchThreadID。
  1. localID线程在自己组内的编号,类似“班内学号”,范围是 [0, groupSize)。
    localID∈[0,groupSize)\text{localID} \in [0, \text{groupSize})
  2. groupSize每个线程组包含的线程数,通常是 64、128 等 2 的幂。
    groupSize=64 或 128\text{groupSize} = 64 \text{ 或 } 128
  3. groupID线程组在整个调度网格中的编号,类似“班级号”。
    groupID=0,1,2,…\text{groupID} = 0, 1, 2, \ldots
  4. globalID全局唯一编号,用来直接索引要处理的数组或缓冲区。
    globalID=groupID×groupSize+localID\text{globalID} = \text{groupID} \times \text{groupSize} + \text{localID}

HLSL语法基础

四位师傅要干活,必须有“工作指令书”——这就是 HLSL(High Level Shading Language),微软为 Direct3D 设计的高级着色语言,语法类似 C 语言。每份指令书写着“输入什么数据、怎么算、输出什么”。一个典型 HLSL 着色器由三部分组成:数据类型声明、输入输出结构体(通过语义绑定)、入口函数。

HLSL 中颜色通常用 float4 四维向量表示(r, g, b, a),四个分量取值范围是 0 到 1:

入口函数通常叫 main,每个阶段写一份;编译时用着色模型(如 Shader Model 5.0)指定能力等级。指令书写得好,师傅们才能高效配合——快递单写清楚,GPU 才知道把法线送给哪道工序。

c⃗=(r,g,b,a),r,g,b,a∈[0,1]\displaystyle \vec{c} = (r, g, b, a), \quad r, g, b, a \in [0, 1]
公式说人话:0 表示“没有这个分量”,1 表示“满值”。比如 (1, 0, 0, 1) 是纯红不透明,(0, 0, 1, 0.5) 是半透明蓝色。0~1 的浮点区间,在屏幕上等价于 0~255 的整数颜色区间。
  1. float4 类型HLSL 最常用的向量类型,装 4 个 float,常用 .xyzw 或 .rgba 访问分量。
    c⃗=float4(r,g,b,a)\vec{c} = \text{float4}(r, g, b, a)
  2. 分量范围 [0,1]颜色分量被归一化到 0~1,避免大数运算,GPU 上更快更稳。
    0≤r,g,b,a≤10 \le r, g, b, a \le 1
  3. 与整数的换算显示到屏幕时,把 0~1 映射到 0~255 的 RGB 整数。
    RGB=(r×255,g×255,b×255)\text{RGB} = (r \times 255, g \times 255, b \times 255)
  4. 语义绑定结构体成员用 SV_Position、NORMAL、UV 等语义标记,让 GPU 知道数据流该往哪儿去。语义就是 HLSL 的“数据快递单”。

Shader Lab实战

HLSL 是一道道工序的“操作指令书”,可是一个完整车间还需要管理规章:生产线怎么配置、用哪几套操作手册、车间降级时怎么办。Unity 的管理规章就叫 ShaderLab——它不是一门新语言,而是一个“外壳”,内部包的还是 HLSL。

ShaderLab 用类似 YAML 的声明式语法组织一个 Shader 文件,核心结构是:Properties(对外暴露的属性面板)、SubShader(针对不同硬件档位的实现)、Pass(具体的渲染通道)、Fallback(降级方案)。程序化特效常配合 C# 脚本动态修改材质属性,一个经典操作是“颜色渐变”——在片元着色器里对两种颜色做线性插值:

把时间变量塞进 p,恐龙身上的花纹就会流动起来;把 UV 坐标塞进去,就能画出一个渐变天空。程序化特效的千变万化,底层就是 ShaderLab 搭好车间、HLSL 写指令、数学公式当配方。

c⃗out=(1−p) c⃗start+p c⃗end,0≤p≤1\displaystyle \vec{c}_{\text{out}} = (1-p)\,\vec{c}_{\text{start}} + p\,\vec{c}_{\text{end}}, \quad 0 \le p \le 1
公式说人话:p 像“渐变进度条”,0 时输出起始色,1 时输出结束色,0.5 时正好是中间色。HLSL 里的内置函数 lerp(a, b, p) 干的就是这件事。
  1. 起始色 c_start渐变的起点颜色向量。
    c⃗start=(r1,g1,b1)\vec{c}_{\text{start}} = (r_1, g_1, b_1)
  2. 结束色 c_end渐变的终点颜色向量。
    c⃗end=(r2,g2,b2)\vec{c}_{\text{end}} = (r_2, g_2, b_2)
  3. 混合权重 p控制过渡进度,通常来自 UV 坐标、时间变量或法线方向。
    p=uv.x 或 时间p = \text{uv.x 或 时间}
  4. 线性插值权重系数 (1-p) 与 p 之和恒为 1,保证颜色始终落在两端点之间。
    (1−p)+p=1(1-p) + p = 1

AI 应用场景

计算着色器加速 AI 推理

阶段:模型部署与推理加速

解决的问题:神经网络推理包含大量矩阵乘法,在 CPU 上串行执行很慢;计算着色器可以把权重矩阵拆分给成千上万个线程并行乘加,就像二楼实验室全员同时开工。在游戏引擎里做实时 AI(机器人导航、NPC 决策)时,延迟必须低,GPU 并行计算是最佳解。

具体案例:一个无人车模拟器用 Unity 渲染场景,同时用 Compute Shader 跑轻量级卷积神经网络做障碍物识别,把 CPU 上 30ms 的推理压到 3ms,让虚拟车实时避障。

AI 生成纹理与片元着色器渲染

阶段:AIGC 内容生产与渲染

解决的问题:用 AI 生成程序化纹理(如扩散模型生成恐龙皮肤贴图)后,需要用片元着色器做纹理采样、颜色映射和光照叠加,才能把贴图“穿”到 3D 模型上。上色师傅手里的“色卡”就是 AI 生成的贴图。

具体案例:某游戏团队用 AI 生成风格化草地纹理,再写自定义片元着色器,把“草尖偏黄、草根偏绿”的规则编进采样逻辑,配合 UV 实现大规模程序化着色,省下大量美术手工调色时间。

强化学习可视化的 GPU 热力图

阶段:AI 训练过程可视化与调参

解决的问题:训练强化学习智能体时,难以直观看到它在环境中的“价值分布”。用顶点、片元着色器把状态价值画成热力图:网格顶点坐标交给顶点着色器变换,片元着色器按价值数值映射颜色,实现 60 帧实时刷新。

具体案例:机器人抓取训练中,用 Shader 热力图实时显示机械臂周围每个位置的成功率预估,工程师据此调整奖励函数,训练效率明显提升。

Python 演示

python47 行
import numpy as np def vertex_shader(angle):    # 模拟顶点着色器: 本地坐标 -> 模型矩阵旋转 -> 屏幕归一化坐标    v_local = np.array([        [-0.5, -0.3, 0.0, 1.0],        [0.5, -0.3, 0.0, 1.0],        [0.0, 0.5, 0.0, 1.0],    ], dtype=float)    c, s = np.cos(angle), np.sin(angle)    model = np.array([        [c, -s, 0, 0],        [s, c, 0, 0],        [0, 0, 1, 0],        [0, 0, 0, 1],    ])    clip = (model @ v_local.T).T    ndc = clip[:, :2] / 2.0 + 0.5    return ndc def fragment_shader(uv, width, height):    # 模拟片元着色器: 对三角形包围盒内每个像素做重心坐标插值上色    img = np.zeros((height, width, 3), dtype=np.uint8)    x, y = uv[:, 0], uv[:, 1]    v0 = np.array([x[1] - x[0], y[1] - y[0]])    v1 = np.array([x[2] - x[0], y[2] - y[0]])    denom = v0[0] * v1[1] - v1[0] * v0[1]    for py in range(height):        for px in range(width):            p = np.array([px / (width - 1) - x[0], py / (height - 1) - y[0]])            u = (p[0] * v1[1] - v1[0] * p[1]) / denom            v = (v0[0] * p[1] - p[0] * v0[1]) / denom            w = 1.0 - u - v            if u > -0.01 and v > -0.01 and w > -0.01:                img[py, px] = (int(255 * w), int(200 * u), int(180 * v))    return img def main():    uv = vertex_shader(0.3)    img = fragment_shader(uv, 64, 64)    cnt = int((img.sum(axis=2) > 0).sum())    print("模拟 GPU 流水线完成: 64x64 渲染输出")    print("三角形内部像素数量:", cnt)    print("三个顶点屏幕坐标:", uv.round(3).tolist()) if __name__ == "__main__":    main()
目的:用 NumPy 在 CPU 上模拟 GPU 渲染管线的两个核心阶段——顶点着色器(矩阵变换)与片元着色器(重心坐标插值上色),帮助直观理解渲染流程。
  1. v_local = np.array([...])定义三角形的三个顶点坐标,相当于模型文件里的顶点数据输入。
  2. model = np.array([...])构造绕 Z 轴旋转的模型矩阵,对应 HLSL 里的 M_model。
  3. clip = (model @ v_local.T).T矩阵乘顶点,完成“模型矩阵变换”这一步,这正是顶点着色器的核心动作。
  4. ndc = clip[:, :2] / 2.0 + 0.5把 [-1,1] 坐标归一化到 [0,1],模拟从裁剪空间映射到屏幕。
  5. denom = v0[0] * v1[1] - v1[0] * v0[1]计算重心坐标的分母,用于判断像素是否落在三角形内。
  6. u, v, w重心坐标三个权重,三者之和为 1,像素在三角形内当且仅当它们都大于等于 0。
  7. img[py, px] = (int(255 * w), ...)用重心坐标给像素上色,等价于片元着色器输出颜色。

常见误区

误区我以为在顶点着色器里写个颜色值,整个模型的颜色就定了,毕竟“顶点”代表模型,改顶点就是改模型。

正解顶点着色器的本职是变换位置——计算顶点该出现在屏幕哪里。它虽然能输出颜色数据,但那只是“托付给下一个工位”的中间数据,真正决定像素最终颜色的是片元着色器。就像摆位师傅摆好零件后会传一张“要涂蓝”的小纸条给上色师傅,但喷漆的活还是上色师傅干。

为何常见:初学者看到“顶点着色器输出颜色”的示例,误以为输出即最终结果,忽略了顶点数据还要经历光栅化插值才会变成像素颜色。

误区既然顶点着色器和几何着色器都在处理顶点,应该都能增加或删除顶点吧?

正解顶点着色器是严格的 1:1——一个顶点进、一个顶点出,不能增删;几何着色器是 1:N——一个图元进,可输出 0 个、1 个或多个图元。两者定位完全不同:顶点着色器是“摆位”,几何着色器是“装配、拆改”。

为何常见:两个着色器都能操作顶点数据,名字又相近,初学者很容易混淆它们输入输出的粒度。

误区计算着色器那么强且通用,不如直接用它在屏幕上画像素,取代片元着色器。

正解计算着色器默认没有绑定渲染目标,也不经历光栅化,不能像片元着色器那样直接输出到帧缓冲。想显示计算结果,必须把结果写到纹理或缓冲区,再经由图形流水线(如全屏四边形加片元着色器)显示出来。它更像二楼实验室——算得再好,成品也得送回主流水线包装。

为何常见:“通用计算等于万能”的直觉,加上看到 Compute Shader 也能写纹理,误以为它接管了渲染管线的一切。

误区Unity 的 Shader 文件里写 Properties、Pass,这应该是一门叫 ShaderLab 的新编程语言,我得先学它才能写 Unity 的 Shader。

正解ShaderLab 是管理外壳(声明式组织文件),真正的计算代码用的还是 HLSL(或 GLSL/Cg)。你可以理解为:ShaderLab 是“车间管理规章”(怎么排产、贴标、降级),HLSL 才是“师傅手上的操作指令书”,两者是组织关系,不是并列的两种语言。

为何常见:.shader 文件语法风格迥异,容易让人误以为是独立语言,其实它的代码块内部还是 HLSL。

面试题

Q1. 图形渲染管线中,主要负责把三维顶点坐标变换到屏幕位置的是哪个阶段?

单选题 · easy

  • A.顶点着色器← 正确答案
  • B.片元着色器
  • C.几何着色器
  • D.计算着色器

答案:A。顶点着色器负责逐顶点变换位置(如 MVP 矩阵相乘),就像摆位师傅摆放零件。片元着色器算颜色,几何着色器改图元数量,计算着色器做通用并行计算。

Q2. 片元着色器的核心职责是什么?

单选题 · easy

  • A.矩阵乘法变换顶点坐标
  • B.计算每个像素的最终颜色← 正确答案
  • C.发射新的三角形图元
  • D.调度 GPU 线程组并行

答案:B。片元着色器在光栅化后的每个像素上运行并输出颜色,像上色师傅给每个像素喷漆。A 是顶点着色器的活,C 是几何着色器的活,D 是计算着色器的活。

Q3. 一个顶点进入顶点着色器,最多能输出多少个新顶点?

单选题 · easy

  • A.零到无限多个
  • B.输出两个顶点
  • C.输出一个顶点← 正确答案
  • D.由硬件配置决定

答案:C。顶点着色器是严格的 1:1 变换——一个顶点进、一个顶点出,不能增删。能增删图元的是几何着色器(1:N),两者定位不同。

Q4. HLSL 中语义 SV_Position 的主要作用是什么?

单选题 · mid

  • A.声明纹理采样器类型
  • B.标记顶点位置输出← 正确答案
  • C.设置线程组的大小
  • D.定义材质的混合模式

答案:B。语义是 HLSL 的“数据快递单”,SV_Position 告诉 GPU 这个输出是裁剪空间位置,必须供光栅化使用。纹理采样用 sampler,线程组大小用 numthreads,混合模式在 Blend 状态中设置。

Q5. 几何着色器与顶点着色器最本质的区别是什么?

单选题 · mid

  • A.几何着色器只能在移动端运行
  • B.几何着色器输出的是最终像素颜色
  • C.几何着色器可发射多个图元← 正确答案
  • D.顶点着色器能删除不想要的顶点

答案:C。几何着色器是 1:N——一个图元可发射 0 个或多个新图元(增删改);顶点着色器是 1:1,只能变换不能增删。B 是片元着色器的职责,A 和 D 与事实不符。

Q6. 计算着色器中,如何计算线程的全局唯一编号 globalID?

单选题 · mid

  • A.localID × groupSize + groupID
  • B.groupID × groupSize + localID← 正确答案
  • C.groupID + localID × groupSize
  • D.groupSize + groupID × localID

答案:B。全局编号 = 组编号 × 组大小 + 组内编号,对应“方阵编号 × 每阵人数 + 阵内序号”。A、C、D 把乘数和顺序搞反,会让不同线程组算出重叠编号,导致数据读写冲突。

Q7. 在“玩具工厂”比喻中,几何着色器对应哪类师傅?

单选题 · mid

  • A.摆位师傅只摆零件位置
  • B.装配师傅可拆改零件数量← 正确答案
  • C.上色师傅逐像素喷漆上色
  • D.实验室计算员通用并行计算

答案:B。几何着色器的核心能力是改图元数量——把一个三角形拆成多个或丢弃废料,对应装配师傅。摆位是顶点着色器,上色是片元着色器,通用计算是计算着色器。

Q8. 以下四个 GPU 阶段中,哪些属于可编程着色器阶段?

多选题 · hard

  • A.顶点着色器← 正确答案
  • B.光栅化插值
  • C.片元着色器← 正确答案
  • D.深度测试

答案:AC。可编程阶段是开发者能用 HLSL 写代码的阶段:顶点、几何、片元、计算着色器。光栅化(三角形变像素)和深度测试(判断谁遮挡谁)是固定功能阶段,由硬件完成,不可编程。

Q9. 关于 Unity 的 ShaderLab,下列说法正确的有哪些?

多选题 · hard

  • A.Properties 块暴露材质属性给编辑器← 正确答案
  • B.SubShader 可针对不同平台提供多种实现← 正确答案
  • C.ShaderLab 是独立于 HLSL 的编程语言
  • D.Fallback 指定硬件不兼容时的降级方案← 正确答案

答案:ABD。ShaderLab 是管理外壳:Properties 是属性面板,SubShader 分平台实现,Fallback 写降级 Shader。C 错——ShaderLab 内部的计算代码仍是 HLSL,它是组织外壳而非并列语言。

Q10. 关于计算着色器,以下说法正确的有哪些?

多选题 · extreme

  • A.用 numthreads 声明线程组大小← 正确答案
  • B.常用于 GPU 粒子系统的位置更新← 正确答案
  • C.必须绑定渲染目标才能执行
  • D.通过 Dispatch 命令启动调度← 正确答案

答案:ABD。计算着色器通过 Dispatch 调度,用 numthreads 定义每个线程组的线程数,典型应用是 GPU 粒子系统(每线程更新一颗粒子)。C 错:计算着色器与图形管线解耦,默认不绑定渲染目标,结果需先写回缓冲区或纹理再显示。