光栅化与深度测试:三角形怎么变成像素

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

顶点着色器交出的三角形,和片元着色器收到的像素之间,隔着一段「不写代码但决定大量现象」的固定流程:光栅化、深度测试、背面剔除、混合。透明排序、Z-fighting、阴影毛刺这些问题的答案都藏在这一段里。

光栅化:按像素中心判定覆盖

三角形是数学上的连续图形,屏幕是离散的像素格。光栅化器的规则简单到近乎粗暴:像素中心落在三角形内部,这个像素就归这个三角形(具体是顶边/右边的边界归属规则保证相邻三角形不重不漏)。这带来两个直接后果:

  • 片元着色器收到的每个像素,其 UV、法线等数据是从三角形三个顶点插值来的,不是采样来的——插值规则见 /notes/unity/structure-2/。
  • 斜边上「半个像素」的闪烁不是 bug,是中心判定的必然;MSAA 抗的就是这个。

深度缓冲与 Z 测试

每个像素在帧缓冲里除了颜色还有一份深度值。输出合并阶段比较:新片元的深度与已有深度,按 ZTest 配置(默认 LEqual,即更近者胜)决定保留谁。这就是不透明物体互相遮挡的全部机制——不需要任何排序,画序无关,靠深度决胜。

early-Z:现代 GPU 会在片元着色器执行之前先做深度测试,输了直接丢弃、不跑片元程序——省掉的可能就是几百万次执行。但两个东西会破坏它:

  • 片元里 discard(alpha 裁剪):GPU 还没算出颜色就无法丢弃,early-Z 失效或退化。
  • ZWrite Off 或 ZTest Always:深度信息不可靠,硬件保守处理。

移动端上这条省得极多,相关取舍见 /notes/unity/performance-1/。

背面剔除:三角形有正反

三角形顶点有绕序(Unity 默认顺时针为正面)。光栅化前可按 Cull Back(默认,剔背面)/ Cull Front / Cull Off 处理。默认剔背面直接省掉约一半的片元执行。Cull Front 不是摆设——描边的「倒模法」就是故意只画背面,见 /notes/unity/effects-3/。

混合:透明为什么必须排序

不透明物体 Blend One Zero:新颜色直接覆盖旧颜色,画序无所谓。 透明物体典型配置 Blend SrcAlpha OneMinusSrcAlpha:新颜色和帧缓冲里已有的颜色按 alpha 加权叠加。

「已有的颜色」取决于之前画了什么——所以透明队列必须从远到近画,且通常 ZWrite Off(透明像素写深度会把后面的透明物体整块裁掉)。即便如此仍有排序毛刺:Unity 按物体包围盒中心排透明 Draw Call,两个互相穿插的粒子系统怎么排都会错——这类毛刺的成因与缓解见 /notes/unity/performance-3/。

深度测试对透明也不是全没用:透明物体仍要读深度来被不透明物体正确遮挡,只是不写深度。

Z-fighting:深度精度的账

两个几乎共面的表面(贴花与地面、两个叠在一起的 UI 面)互相闪烁,是深度值差异小于深度缓冲精度。深度被压进 [0,1] 后近处精度高、远处精度指数级下降,所以:

  • 摄像机 Near 别设太小(0.3 比 0.01 给远处多出大量精度),Far 别设太大。
  • 贴花/贴面材质用 Polygon Offset 或深度偏置。

具体数值影响见 /notes/unity/performance-3/。

自检三问

  • 为什么 discard 用得多的材质在手机上明显更贵?
  • 一个 ZWrite Off, ZTest LEqual 的透明物体,会被先画的不透明物体挡住吗?会被后画的透明物体挡住吗?
  • 远处两个共面贴图闪烁,先检查摄像机哪个参数?