shader 是什么:从 Draw Call 到像素的渲染管线

Unity 知识 · Shader 区域 · 原理 · 第 1 篇 更新于 2026-10-15

写 shader 时你写的其实是 GPU 流水线上某个 stage 的程序。不知道自己在哪个 stage,就解释不了大量现象:为什么透明物体会互相穿插、为什么顶点动画会连阴影一起动、为什么片元里的采样比计算还贵。这篇把整条管线画出来,标清 shader 的工作位置。

Draw Call 之后,GPU 做了什么

Draw Call 是 Unity 发给 GPU 的一条指令:「用这套材质和参数,把这个网格画出来」。GPU 收到后走一条固定流水线:

阶段输入 → 输出可编程性
输入装配(IA)顶点缓冲 + 索引 → 三角形列表不可编程
顶点着色器每个顶点 → 一个裁剪空间顶点可编程:vertex shader
曲面细分 / 几何着色器细分或增删几何可选,Unity 常规用法基本不碰
裁剪 + 屏幕映射 + 光栅化三角形 → 它覆盖的像素集合不可编程
片元着色器每个被覆盖的像素 → 颜色和深度值可编程:fragment shader
输出合并(OM)颜色 + 深度 → 写入帧缓冲不写代码,用渲染状态控制

你写的代码只出现在两个 stage:顶点着色器和片元着色器。输出合并阶段虽然不写代码,但可以用 Blend、ZWrite、ZTest 这些状态去配置它——见 /notes/unity/structure-3/。

顶点着色器:每个顶点跑一次

顶点着色器的输入是一个顶点的全部属性(位置、法线、UV……语义怎么绑定见 /notes/unity/structure-2/),输出至少包含一个裁剪空间位置。它的职责概括起来是三件事:

  1. 空间变换:把顶点从模型空间搬到裁剪空间(MVP 链,见 /notes/unity/principles-2/)。
  2. 准备插值数据:把法线、UV、颜色等塞进输出结构,交给光栅化器插值。
  3. 可选的几何修改:顶点动画、GPU 蒙皮、位移——改的是几何本身,所以阴影、深度都会跟着变(这正是「顶点动画要同步改 ShadowCaster」的原因,见 /notes/unity/effects-5/)。

关键性质:每个顶点恰好执行一次,与屏幕上占多少像素无关。

片元着色器:每个被覆盖的像素跑一次

三角形被光栅化成一堆像素覆盖后,片元着色器对每个像素跑一次,输出颜色和深度。它决定像素「长什么样」:采样贴图、算光照(见 /notes/unity/lighting-1/)、做溶解和扭曲(见 /notes/unity/effects-4/)。

关键性质:每个被覆盖的像素执行一次——一个铺满 1080p 屏幕的大三角形,片元程序要跑约两百万次。这就是性能账要算在片元里的原因(成本模型见 /notes/unity/performance-1/)。

一个三角形的完整旅程

把两者串起来,一个三角形从数据到屏幕:

  1. IA 按索引组装出 3 个顶点,各跑一次顶点着色器。
  2. 裁剪:完全在视野外的三角形整块丢弃。
  3. 光栅化:算出三角形覆盖了哪些像素(按像素中心判定,见 /notes/unity/principles-3/)。
  4. 每个覆盖像素跑一次片元着色器。
  5. 输出合并:深度测试决定要不要覆盖旧像素,混合决定新颜色怎么和旧颜色叠加。

一个具体数量级:一个铺满屏幕的四边形(2 个三角形、4 个顶点)在 1920×1080 下 ≈ 4 次顶点执行 + 约 207 万次片元执行。顶点程序里稍贵的计算往往无所谓,片元程序里每一次采样都要心疼——这是后面所有 shader 性能建议的出发点。

为什么透明必须排序:管线埋的坑

输出合并的混合是「新颜色和帧缓冲里已有的颜色」叠加。已有颜色取决于之前画过什么——所以透明物体必须从远到近画,否则叠加顺序错,结果就错。深度测试对透明也不够用(透明像素不该写深度挡住后面的透明),这些细节在 /notes/unity/principles-3/ 展开。

怎么亲眼看到这条管线

Unity 菜单 Window → Analysis → Frame Debugger 可以把一帧拆成逐 Draw Call 回放:每个 Draw Call 用了哪个 shader 的哪个 Pass、传了什么参数、状态是什么,全部可见。第一次学管线时开一次,比任何图都直观——工具用法见 /notes/unity/performance-2/。

自检三问

  • 一个铺满屏幕的 UI 大图,顶点程序和片元程序各执行多少次?
  • 溶解效果用 discard 丢弃像素,发生在哪个 stage?(提示:片元之后、输出合并之前)
  • 为什么改顶点动画会连影子一起变?

三问都能答上,就可以进入下一篇的空间变换。