渲染管线架构
渲染管线架构 - 前向渲染、延迟渲染与 Cluster 渲染
引言
渲染管线(Rendering Pipeline) 是引擎把 3D 场景数据转换成屏幕上 2D 图像的整个流程。它决定了引擎如何组织光照、如何处理几何、如何在画质与性能之间权衡,是渲染架构中最顶层的设计决策。
渲染管线分为两个层次:
- GPU 硬件渲染管线 —— 一次 Draw Call 在 GPU 内部经历的固定阶段(顶点 → 光栅化 → 片元 → 输出)。
- 引擎渲染路径(Render Path) —— 引擎如何组织整帧的渲染,主要是 前向渲染 / 延迟渲染 / Forward+ 三大路线及其现代变体。
一、GPU 硬件渲染管线
无论上层用什么渲染路径,最终每个物体都要走一遍 GPU 的图形管线。理解这条管线是理解一切的基础。

主要阶段
| 阶段 | 类型 | 作用 |
|---|---|---|
| 输入装配 (Input Assembler) | 固定 | 读取顶点缓冲/索引缓冲,组装成图元(三角形) |
| 顶点着色器 (Vertex Shader) | 可编程 | 把顶点从模型空间变换到裁剪空间(MVP 变换),输出到光栅化 |
| 曲面细分 (Tessellation) | 可编程 | 可选。动态细分几何,增加细节(地形、曲面) |
| 几何着色器 (Geometry Shader) | 可编程 | 可选。生成/删除图元(粒子、草),实践中较少用 |
| 裁剪与剔除 (Clip / Cull) | 固定 | 裁剪视锥外的图元,背面剔除 |
| 光栅化 (Rasterization) | 固定 | 把三角形离散成像素(片元),插值顶点属性 |
| 片元着色器 (Fragment/Pixel Shader) | 可编程 | 计算每个像素的最终颜色(光照、贴图采样都在这里) |
| 输出合并 (Output Merger) | 固定 | 深度测试、模板测试、混合(Blending),写入帧缓冲 |
关键概念
- 可编程 vs 固定功能:着色器阶段可编程(写 Shader),其余是固定硬件功能(可配置参数但不可写代码)。
- Early-Z:现代 GPU 会在片元着色器之前做深度测试,提前丢弃被遮挡的像素,避免无效着色 —— 这也是"从前往后渲染不透明物体"能省性能的原因。
- Overdraw(过度绘制):同一像素被多次着色(多层半透明、深度排序不佳),是常见性能杀手。
Early-Z 究竟发生在哪个阶段
这是一个容易混淆的点,需要区分深度测试的逻辑位置和 Early-Z 优化两个概念。
逻辑位置(Late-Z):按图形管线的规范定义,深度测试属于输出合并(Output Merger / ROP) 阶段,即在片元着色器之后:
光栅化 → 片元着色器 → [深度测试 → 混合] 输出合并Early-Z 优化:现代 GPU 把深度测试提前到光栅化之后、片元着色器之前执行。因为深度值来自顶点插值,光栅化后就已确定,不必等片元着色器。这样被遮挡的片元在着色前就被剔除,直接省掉昂贵的贴图采样和光照计算。

一句话结论:Early-Z 发生在 光栅化(Rasterization)之后、片元着色器(Fragment Shader)之前。
Early-Z 失效的情况
当片元着色器可能改变深度或影响可见性时,GPU 无法提前测试,只能退回到 Late-Z(标准位置):
| 失效原因 | 说明 |
|---|---|
着色器写 SV_Depth / gl_FragDepth | 深度在着色时才确定,无法提前判断 |
使用 discard / clip()(Alpha Test) | 片元可能被丢弃,影响深度写入,通常抑制 Early-Z |
| 开启 Alpha Blending(半透明) | 需要读取背景色混合,不能提前剔除 |
测试(Test)vs 写入(Write):Early-Z 通过后还会再测深度吗
Early-Z 其实包含两个可以独立发生的动作,理解这点才能回答"FS 之后还测不测深度":
- Early 深度测试:FS 前判断片元是否被遮挡,被挡就丢弃(省着色)。
- Early 深度写入:FS 前就把片元深度写进深度缓冲。
只有当 FS 不影响可见性/深度时,硬件才敢把写入也提前。此时:
FS 不写 SV_Depth、不 discard
→ Early-Z 做「测试 + 写入」
→ FS 运行
→ 直接混合/写颜色(不再做第二次深度测试)结论:在这种最常见的快速路径下,深度测试只在 FS 前做一次,FS 之后不重复测试,直接进入混合。只有当 FS 会破坏早写入的安全性时,测试/写入才推迟到 FS 之后:
| FS 行为 | 深度测试 / 写入时机 |
|---|---|
| 普通(不写深度、不 discard) | FS 前测试 + 写入;FS 后直接混合 |
discard / clip() | 可提前测试剔除,但写入推迟到 FS 后(否则被丢弃片元会错误占用深度) |
写 SV_Depth / gl_FragDepth | 深度值 FS 才算出,测试与写入都推迟到 FS 后 |
注:即便走快速路径,ROP 阶段仍会做内存危险(hazard)同步以保证多片元按序更新深度 —— 这是硬件正确性保障,不是逻辑上的第二次深度测试,不产生额外着色开销。
若确定 shader 安全,可强制开启早测试+早写入:
layout(early_fragment_tests) in; // GLSL / Vulkan[earlydepthstencil] void frag() { ... } // HLSL强制开启后,即使 shader 里有
discard,深度也会照常在 FS 前写入,需自行确保语义正确。
实践关联:这正是"从前往后渲染不透明物体"和"Depth Pre-pass"能省性能的底层原因 —— 先把深度填好,后续被遮挡的片元都在 Early-Z 阶段被干掉,从而把 Overdraw 降到最低。
二、渲染路径的核心矛盾:光照怎么算
引擎渲染路径的本质分歧在于一个问题:在有很多光源、很多物体时,光照计算应该在什么时候、对什么做?
朴素前向渲染的复杂度是 O(物体数 × 光源数) —— 每个物体的每个像素都要遍历所有光源。当光源数量增多时,这个乘法会爆炸。三大渲染路径就是对这个矛盾的不同解法。

三、前向渲染(Forward Rendering)
原理
最经典、最直接的方式:逐物体渲染,在片元着色器里直接把该物体受到的所有光照算完,一次性输出最终颜色。

for 每个物体:
for 每个像素:
color = 0
for 每个影响该物体的光源:
color += 计算光照(光源, 材质, 法线...)
输出 color优点
- 实现简单,管线直观。
- 支持 MSAA:因为在几何阶段就有真实的几何信息,硬件 MSAA 天然可用(对比延迟渲染的痛点)。
- 半透明友好:透明物体本就需要前向 + 排序混合,前向路径统一处理。
- 材质自由:每个物体可以有完全不同的光照模型 / Shader。
- 带宽低:不需要庞大的 G-Buffer,对移动端(TBR 架构)非常友好。
缺点
- 多光源开销爆炸:复杂度 O(物体 × 光源),光源一多性能急剧下降。
- Overdraw 浪费:被遮挡的物体也可能完整算了光照才被覆盖。
- 光照与几何耦合:难以做大量动态光源。
现状
移动端主流(配合有限光源数)、VR、以及需要大量半透明或多样材质的场景。Unity 的 URP 默认走前向路径。
四、延迟渲染(Deferred Rendering / Deferred Shading)
原理
核心思想是把几何处理和光照计算解耦,分两个大阶段(Pass):
- 几何 Pass(Geometry Pass):渲染所有不透明物体,但不算光照,只把每个像素的材质属性(位置/深度、法线、Albedo、金属度、粗糙度等)写入一组渲染目标,称为 G-Buffer。
- 光照 Pass(Lighting Pass):以屏幕空间为单位,对每个像素读取 G-Buffer,遍历光源计算光照。

复杂度从 O(物体 × 光源) 变成 O(物体) + O(屏幕像素 × 光源) —— 光照只对最终可见的像素算一次,与场景几何复杂度解耦。
G-Buffer 布局
G-Buffer 是延迟渲染的核心,通常是多张全屏渲染目标(MRT, Multiple Render Targets):

| 渲染目标 | 存储内容(典型) |
|---|---|
| RT0 | Albedo (RGB) + 材质标识 |
| RT1 | 世界空间法线 (RGB) |
| RT2 | 金属度 / 粗糙度 / AO |
| RT3 | 自发光 / 其他 |
| Depth | 深度缓冲(重建世界坐标) |
优点
- 天然支持大量动态光源:光照复杂度与几何无关,成百上千的光源也能高效处理。
- 无重复光照计算:每个可见像素只算一次光照(Early-Z 之后的最终像素)。
- 适合光源密集的场景(城市夜景、大量点光源)。
缺点
- MSAA 困难且昂贵:G-Buffer 存的是材质属性不是颜色,硬件 MSAA 需要多倍 G-Buffer 存储,带宽爆炸 —— 这正是延迟渲染普遍改用 TAA/FXAA 的原因。
- 半透明无法处理:G-Buffer 每像素只能存一层,透明物体需要单独走一遍前向渲染再合并。
- 带宽开销大:G-Buffer 是多张全屏 RT,读写带宽巨大,对移动端 TBR 架构不友好。
- 材质变化受限:所有物体共享 G-Buffer 布局,光照模型不如前向自由。
现状
PC / 主机 3A 大作的常见选择(光源多)。Unity 的 HDRP、URP 都提供延迟路径。
五、Forward+ (Tiled/Clustered Forward)
动机
前向渲染的痛点是"每个像素遍历所有光源",延迟渲染又有 MSAA/半透明/带宽的问题。Forward+ 试图兼得两者优点:保留前向的灵活性(MSAA、半透明、材质自由),同时解决多光源问题。
原理
关键在于先做一次光源剔除,缩小每个像素要遍历的光源集合:
- 深度预 Pass(Depth Pre-pass):先渲染一遍深度,得到每个像素的深度。
- 光源剔除(Light Culling):把屏幕划分成小块(Tile,如 16×16 像素)或 3D 分块(Cluster),计算每个 Tile/Cluster 实际受哪些光源影响,生成光源列表。
- 前向着色:正式渲染时,每个像素只遍历它所在 Tile/Cluster 的光源列表,而非全部光源。

- Tiled Forward+:按 2D 屏幕分块。
- Clustered Forward+:按 3D(屏幕 XY + 深度 Z)分块,光源剔除更精确,是目前更主流的做法(如 Unity HDRP、DOOM)。
优点
- 支持大量光源(接近延迟渲染的能力)。
- 保留前向优点:MSAA、半透明、材质自由都可用。
- 比延迟渲染带宽更低(无需大 G-Buffer)。
缺点
- 实现比前向复杂(需要 Compute Shader 做光源剔除、深度预 Pass)。
- 深度预 Pass 有额外开销。
- 每个 Tile 的光源列表在光源极度密集时仍可能变长。
现状
现代引擎的主流选择之一。Unity HDRP 的前向路径、DOOM (2016)、许多主机大作都采用 Clustered Forward+。
六、可见性缓冲(Visibility Buffer)—— 更现代的思路
可见性缓冲最早由 Burns & Hunt 在 2013 年提出(The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading),近年随 UE5 Nanite 等虚拟几何技术真正流行起来。它是延迟思想的进一步演化。
动机:两个传统管线在高几何密度下的崩溃
当场景几何密度极高、三角形小到只覆盖一两个像素(微多边形 micro-poly) 时,前面两类管线都会出问题:
1. G-Buffer 延迟渲染 —— 带宽爆炸 每个像素都要写多张肥厚的 RT(法线/Albedo/粗糙度…)。几何越密,写 G-Buffer 的带宽越夸张。
2. 传统光栅 —— Quad 利用率崩溃 GPU 光栅化以 2×2 像素块(Quad) 为单位执行片元着色器(为了用邻居像素算纹理导数 ddx/ddy)。当一个三角形只有约 1 个像素大时,2×2 Quad 里另外 3 个像素是"helper lane(辅助线程)",被浪费掉:
大三角形:Quad 4 个像素几乎都有效 微多边形:一个 Quad 里只有 1 个像素属于该三角形
┌──┬──┐ ┌──┬──┐
│██│██│ 利用率 ~100% │██│░░│ 利用率 ~25%
├──┼──┤ ├──┼──┤ ░ = 被浪费的 helper lane
│██│██│ │░░│░░│
└──┴──┘ └──┴──┘微多边形下,传统光栅可能有 50%~75% 以上的着色算力被 helper lane 浪费。可见性缓冲正是为解决这两个问题而生。
核心原理
思路是把"几何可见性"和"材质着色"彻底分离,且几何 Pass 只写极精简的信息:
- 几何 Pass 不写任何材质属性,每个像素只写一个 可见性 ID:通常是
实例ID + 三角形ID(Instance ID + Primitive ID),打包进 32 或 64 bit(Nanite 用 64-bit:深度 + 三角形/实例 ID)。 - 之后在屏幕空间的材质 Pass里,对每个像素:用 ID 回查它命中的那个三角形,重建出所有顶点属性,再采样贴图、跑材质、着色。
材质属性是怎么"重建"出来的
这是可见性缓冲最精妙、也最烧 ALU 的一步。着色时手上只有一个三角形 ID,需要现场把光栅化本该插值好的属性重新算出来:
- 取回三角形:用 InstanceID + TriID 索引到几何缓冲,取出该三角形的 3 个顶点。
- 重算重心坐标(Barycentric):把 3 个顶点变换到裁剪空间,结合当前像素位置,反解出该像素在三角形内的重心权重。
- 插值所有属性:用重心权重手动插值 UV、法线、切线、顶点色等(做透视校正)。
- 解析法求梯度:传统硬件导数(ddx/ddy)依赖 2×2 Quad 里的邻居,但相邻像素可能属于不同三角形,导数会出错。所以可见性缓冲改用解析法直接算出 UV 对屏幕的偏导,供纹理 mipmap/各向异性采样选择正确的 LOD。
- 跑材质:拿到属性后,正常采样贴图、执行材质逻辑、着色。
关键难题:材质分类(Material Classification)
不同像素命中的三角形可能属于不同材质。若一个 Compute 逐像素 switch(材质),会造成严重的线程发散(divergence)。工程上的解法是先按材质对像素分类/分箱(binning),再对每种材质各发一个 Compute Dispatch —— 同一批线程跑同一段材质代码,消除发散。Nanite 用一种 "material depth / 材质分类" 技巧高效实现这一步。
优点
- 几何 Pass 带宽极低:每像素只写一个 ID,远小于多张 G-Buffer RT。
- 着色与几何复杂度解耦:材质在屏幕空间每个可见像素只算一次,与三角形数量无关,且不受 Quad 利用率影响(Compute 里满线程执行)。
- 天生适配微多边形:这正是 Nanite 能渲染海量三角形的前提。
- 导数质量更高:解析梯度可比硬件 Quad 导数更精确。
缺点 / 代价
- 着色 Pass 的 ALU 与访存开销大:每像素都要回查几何、重算重心坐标与梯度,属于"用计算换带宽"。
- 需要几何数据常驻可随机访问:顶点/索引要能在着色时被随机寻址。
- 材质分类复杂:分箱、按材质 Dispatch 的实现相当繁琐。
- 半透明依旧无法直接处理:和延迟渲染一样,每像素只有一层可见性,透明物体需另走前向。
- 整体实现复杂度高:是这几种方案里最难落地的。
权衡对比
| 维度 | 传统 Deferred (G-Buffer) | Visibility Buffer |
|---|---|---|
| 几何 Pass 带宽 | 大(多张 RT) | 极小(一个 ID) |
| 材质计算时机 | 几何 Pass 时 | 延迟到屏幕空间着色时 |
| 微多边形效率 | 差(带宽 + Quad 浪费) | 好(满线程、带宽低) |
| 着色 ALU/访存 | 低(属性已存好) | 高(现场重建属性) |
| 材质管理 | 简单 | 复杂(需分类/分箱) |
| 适合场景 | 常规几何 | 超高几何密度 |
| 实现复杂度 | 中 | 高 |
现状
UE5 Nanite 的核心渲染方式:Nanite 用软件光栅(针对微三角形,配合 64-bit 原子操作)+ 硬件光栅混合,把结果写入可见性缓冲,再在屏幕空间做材质着色。适合每个三角形只有一两个像素的极高密度场景 —— 此时传统管线的顶点/光栅/带宽效率都会崩溃,而可见性缓冲的开销与几何量近乎无关。
七、三大渲染路径横向对比

| 维度 | 前向渲染 | 延迟渲染 | Forward+ |
|---|---|---|---|
| 多光源能力 | 差 | 强 | 强 |
| MSAA 支持 | 原生支持 | 困难/昂贵 | 支持 |
| 半透明处理 | 原生支持 | 需单独 Pass | 支持 |
| 带宽开销 | 低 | 高(G-Buffer) | 中 |
| 材质自由度 | 高 | 受限 | 高 |
| 移动端友好 | 好 | 差 | 中 |
| 实现复杂度 | 简单 | 中 | 高 |
| 典型使用 | 移动/VR/URP | 3A/HDRP | HDRP/DOOM |
选型建议
- 移动端 / VR / 半透明多:前向渲染(带宽敏感、需要 MSAA)。
- PC/主机、光源密集、几何常规:延迟渲染 或 Clustered Forward+。
- 既要多光源又要 MSAA/材质自由:Clustered Forward+。
- 超高几何密度(虚拟几何):Visibility Buffer(如 Nanite)。
八、Unity 的可编程渲染管线(SRP)
Unity 把渲染管线开放为可编程的 Scriptable Render Pipeline (SRP),官方提供两套预制管线:
| 管线 | 定位 | 默认路径 |
|---|---|---|
| URP (Universal RP) | 跨平台、性能优先,覆盖移动到主机 | 前向为主(也支持延迟) |
| HDRP (High Definition RP) | 高画质、PC/主机专属 | 延迟 + Clustered Forward+ |
| Built-in RP | 旧版内置管线(逐步淘汰) | 前向 / 延迟 |
- SRP 允许开发者用 C# 自定义整帧的渲染流程(剔除、排序、Pass 组织)。
- RenderGraph(URP 17+ / HDRP)进一步把渲染流程抽象为有向无环图,自动管理资源生命周期与 Pass 依赖,优化带宽和内存。
九、一帧的渲染流程总览
一个典型的现代引擎,一帧大致经历如下阶段(以延迟/Forward+ 为例):

剔除 (Culling)
└─ 视锥剔除 + 遮挡剔除 → 可见物体列表
阴影 Pass
└─ 渲染各光源的 Shadow Map
深度预 Pass (可选)
└─ 生成深度,供 Forward+ 光源剔除 / SSAO
主渲染 Pass
└─ 几何 Pass (延迟) 或 前向着色
光照 Pass
└─ 遍历光源计算光照
天空盒 / 半透明
└─ 半透明物体排序 + 前向混合
后处理 (Post-processing)
└─ 抗锯齿(TAA/FXAA) → Bloom → 色调映射 → 颜色分级
UI
└─ 叠加 UI
呈现 (Present)
└─ 提交到屏幕十、核心概念速查
| 概念 | 说明 |
|---|---|
| Draw Call | CPU 通知 GPU 渲染一批图元的命令,数量过多是 CPU 瓶颈 |
| G-Buffer | 延迟渲染存储材质属性的多张全屏渲染目标 |
| MRT | Multiple Render Targets,一次输出到多张 RT |
| Early-Z | 片元着色前的深度测试,提前剔除遮挡像素 |
| Overdraw | 同一像素被多次着色的浪费 |
| Tile / Cluster | Forward+ 中屏幕/视锥的分块单位,用于光源剔除 |
| Depth Pre-pass | 提前渲染深度的独立 Pass |
| SRP / RenderGraph | Unity 可编程渲染管线 / 基于图的资源调度 |
总结
- 渲染管线分为 GPU 硬件管线(固定功能 + 可编程着色器)和引擎渲染路径(前向/延迟/Forward+)两个层次。
- Early-Z 发生在光栅化之后、片元着色器之前,是现代 GPU 减少 Overdraw 的核心优化;片元着色器写深度或 discard 时会导致 Early-Z 失效。
- 前向渲染实现简单、支持 MSAA 和半透明,但多光源场景性能急剧下降(O(物体 x 光源))。
- 延迟渲染将几何与光照解耦,通过 G-Buffer 实现大量光源的高效渲染,但在 MSAA、半透明和带宽方面存在瓶颈。
- Forward+(Tiled/Clustered Forward)通过光源剔除在前向框架下支持大量光源,兼顾了前向的灵活性和延迟的多光源能力。
- 可见性缓冲(Visibility Buffer)是应对超高几何密度的现代方案,几何 Pass 仅写入一个 ID,通过屏幕空间回查重建材质属性 —— UE5 Nanite 即基于此。
- 选型需根据目标平台、光源数量、MSAA 需求、半透明需求和几何密度综合权衡。
