一、Unity 渲染管线对比
1.1 内置渲染管线(Built-in Render Pipeline)
固定功能管线:渲染流程是 Unity 官方预先编写好的一套固定流程,开发者只能通过开关选项调整,无法改变核心运作方式。
特点:
- “一个管线应对所有场景”——试图用一套逻辑兼顾从手机到 PC 的所有平台
- 在任何特定场景下都不是最优的——为了兼容性而显得臃肿
- 对开发者来说像一个黑盒,灵活性差
1.2 可编程渲染管线(SRP: Scriptable Render Pipeline)
Unity 提供底层 API(ScriptableRenderContext)和框架,允许用 C# 脚本编写和控制整个渲染流程。URP 和 HDRP 是官方基于这套 API 写好的两套预设。
“为特定目标量身定制”:URP 和 HDRP 是为特定目标从头编写的、极其专注和优化的管线。
1.3 URP vs HDRP 详细对比
| 特性维度 | URP(通用渲染管线) | HDRP(高清渲染管线) |
|---|---|---|
| 核心目标 | 效率、灵活性、跨平台 | 极致图形保真度、物理正确性 |
| 目标平台 | 移动端、PC(中低端)、WebGL | 高性能 PC、高端主机(PS5/Xbox) |
| 渲染路径 | 前向渲染(默认),可选延迟渲染 | 延迟渲染(默认且强大),也支持前向 |
| 光照模型 | 简化版 PBR,优化保证性能 | 完全基于物理的 PBR,更复杂精确 |
| 光源数量 | 每物体 2-4 个像素光,性能友好 | 大量像素光,延迟渲染高效处理成百上千光源 |
| Shader 复杂度 | 相对简单,有限但高效 | 极其复杂,大量 3A 级效果模块 |
| 后处理效果 | Bloom、SSAO、色彩调整等常用效果 | 体积光、SSR、精确景深等电影级效果 |
| 高级特性 | 不支持或简化版 | 光线追踪、体积雾、基于物理的相机、毛发/布料着色器 |
| 性能开销 | 低 | 非常高 |
1.4 为什么 SRP(URP/HDRP)性能更好
SRP Batcher(CPU 性能提升):
传统方式(Built-in):每个使用不同材质的物体,即使材质用同一个 Shader 只是参数不同,也会导致 GPU 中断渲染流程、切换 Shader 状态,产生巨大的 CPU 开销。
SRP Batcher:将所有使用同类 Shader 的物体的材质数据保存在 GPU 显存的特定区域。渲染时只需快速更新材质数据指针,无需进行昂贵的状态切换。
结果:渲染大量使用不同材质但共享同一 Shader 的物体时,CPU 开销大幅降低,帧率显著提升。
更清晰的工作流:URP/HDRP 的渲染流程用 C# 清晰定义,每条管线只包含目标所需的功能,无比精简高效。
二、完整图形渲染管线
图形渲染管线是实时渲染的核心组件。功能是将 3D 场景通过给定虚拟相机、光源等要素渲染为 2D 图像。
管线五大阶段总览
|
|
类比:
- 准备阶段:摄影师布景、调整灯光
- 几何阶段:把模特和景物投影到相机胶片上
- 光栅化:把胶片成像冲印成照片
- 像素处理:给照片调色、加滤镜
- 输出与后处理:照片装裱、展览
2.1 第一阶段:准备阶段(CPU)
CPU 为 GPU 准备好所有需要渲染的数据和指令,是渲染流水线的指挥官。
(1)剔除(Culling):
视锥体剔除(Frustum Culling):剔除完全在摄像机视野之外的物体。这是最基础的优化。
遮挡剔除(Occlusion Culling):剔除被其他物体完全挡住的物体(即使在视野内)。算法更复杂,但能极大提升性能。
(2)排序与合批(Sorting & Batching):
渲染排序:按材质、渲染状态(透明/不透明)排序,减少 GPU 状态切换开销。
动态合批(Dynamic Batching):CPU 将多个小模型的顶点数据合并成一个,用一次 Draw Call 绘制。节省开支但有限制。
静态合批(Static Batching):对不会移动的物体进行预处理合批,大幅减少 Draw Call。
(3)设置渲染状态:材质(Shader、纹理)、混合模式、深度测试等。
(4)构建渲染命令列表:生成一系列 Draw Call 命令。
2.2 第二阶段:几何阶段(GPU)
GPU 接收到 Draw Call 命令和顶点数据后,开始处理每个顶点。
(1)顶点着色器(Vertex Shader):
核心任务:处理每个顶点的位置,完成 MVP 变换,将 3D 顶点坐标转换到 2D 屏幕坐标。
完整坐标变换流程:
|
|
Model 矩阵:将物体从局部坐标系变换到世界坐标系,包含平移、旋转、缩放。
View 矩阵:将世界坐标变换到以相机为原点的观察坐标系。
Projection 矩阵:将观察坐标变换到裁剪坐标(齐次裁剪空间)。两种投影方式:
正交投影:视锥体为长方体,物体大小与距离无关,适合 2D 游戏、UI、小地图。
透视投影:视锥体为台体,近大远小,适合 3D 游戏。通过近平面(Near)、远平面(Far)、垂直视场角(FOV)、屏幕宽高比(Aspect Ratio)四个参数定义。
(2)曲面细分着色器(Tessellation Shader)(可选): 动态增加模型的三角形数量,让表面更平滑细腻(用于地面、角色皮肤等)。
(3)几何着色器(Geometry Shader)(可选): 可以一次处理整个图元(三角形),甚至生成或销毁图元。用法相对小众。
(4)裁剪(Clipping): 将超出屏幕(视锥体)范围的三角形进行裁剪,只保留屏幕内的部分。
完全位于视锥体外的图元被裁剪掉,部分在视锥体内的图元需要裁剪操作。常见裁剪算法:Cohen-Sutherland 算法、Liang-Barsky 算法、Sutherland-Hodgman 多边形裁剪算法。
(5)背面剔除(Back Face Culling): 剔除背对摄像机的图元,可减少约 50% 的渲染图元。
注意:背面剔除默认不开启。半透明物体不能使用背面剔除,否则会出现穿帮的情况。
(6)屏幕映射(Screen Mapping): 将裁剪空间坐标转换到最终的屏幕坐标。
透视除法:除以 w 分量得到 NDC(归一化设备坐标),实现近大远小的视觉效果。
视口变换:NDC 坐标转换为窗口坐标(Window Coordinates)。
2.3 第三阶段:图元装配(Primitive Assembly)
连接顶点处理和光栅化的桥梁。
输入:顶点着色器处理后的一堆离散顶点。
处理过程:根据 Draw Call 指令(图元类型和索引缓冲区),将离散顶点连接成具体的几何图元(三角形)。
输出:完整的、连接好的三角形,每个三角形由三个顶点及其所有插值后的属性组成。
2.4 第四阶段:光栅化阶段(GPU)
将连续的几何图形(三角形)离散化为一个个的像素片段。
三角形组装(Triangle Setup):对顶点的输入数据(颜色、法线、纹理坐标)进行插值,得到各个片段对应的数据值。
三角形遍历(Triangle Traversal)/ 扫描转换(Scan Conversion): 检查每个像素的中心是否在三角形内。如果是,就为该像素生成一个片段(Fragment)。片段包含了插值后的数据(颜色、法线、深度、UV 等)。
对于透视投影,需要使用**透视校正插值(Perspective-Correct Interpolation)**来正确插值片段的颜色、纹理等信息。
2.5 判断点在三角形内部的方法
方法一:重心坐标法(Barycentric Coordinates)—— GPU 实际使用的方法
任何三角形内的点 P 可以表示为三个顶点 A、B、C 的加权和:
|
|
计算权重(基于面积比):
- 计算整个三角形面积的两倍:
area_ABC = (B - A) × (C - A) - 计算子三角形面积:
area_PBC = (B - P) × (C - P) - 归一化:
u = area_PBC / area_ABC,v = area_PCA / area_ABC w = 1 - u - v
判断条件:如果 u >= 0 且 v >= 0 且 w >= 0(即 u + v <= 1),则点 P 在三角形内部。
为什么 GPU 喜欢这个方法? 重心坐标不仅是判断工具,更是完美的插值权重:
|
|
方法二:同向法(叉积法)
计算边向量和点 P 到顶点向量的三次叉积,如果三次结果的符号相同,则点在三角形内部。
2.6 锯齿与抗锯齿
锯齿产生原因:3D 场景中的物体是连续的,而屏幕像素是离散的。用离散像素表示连续物体时丢失了连续信息。
SSAA(超级采样抗锯齿): 将屏幕渲染到更高分辨率的缓冲区(如 4xSSAA 将 800×600 渲染到 1600×1200),然后下采样。效果好但计算量极大(光栅化和片段着色器都是 4 倍)。
MSAA(多重采样抗锯齿): 每个片段使用多个采样点,计算三角面覆盖的采样点数量(覆盖率),片段着色器只计算一次颜色,最终颜色乘以覆盖率。
|
|
MSAA 的问题:与延迟渲染(Deferred Rendering)不兼容,因为光照阶段无法通过 G-Buffer 获取片段覆盖率信息。
重心采样(Centroid Sampling):解决 MSAA 中采样点位于三角面外部的问题。GPU 通过覆盖的采样点确定采样的重心点。
其他抗锯齿技术:CSAA(覆盖采样抗锯齿)、CFAA(可编程过滤抗锯齿)、FXAA(快速近似抗锯齿)、TXAA(时间样本抗锯齿)、MFAA(多帧采样抗锯齿)、MLAA(形态抗锯齿)、SMAA(亚像素形态抗锯齿)。
2.7 第五阶段:像素处理阶段(GPU)
这是最复杂、最灵活的阶段,现代图形学的很多特效都在这里发生。
(1)片段着色器(Fragment Shader)/ 像素着色器(Pixel Shader):
核心任务:计算每个片段的最终颜色。
工作原理:使用从顶点着色器插值过来的数据(如 UV),进行纹理采样,并应用光照模型进行计算。
纹理贴图(Texture Mapping): 将图像信息映射到三角形网格上,增加物体表面细节。纹理上的每个像素称为纹素(Texel)。
常见纹理技术:
- 凹凸贴图(Bump Mapping):通过光照信息产生视觉错觉
- 法线贴图(Normal Mapping):直接存储表面法线,改变光照计算结果(性能更好)
- 高度贴图(Height Mapping):存储高度信息,需要计算法线扰动
- 视差贴图(Parallax Mapping):模拟表面凹凸的深度感
- 位移贴图(Displacement Mapping):实际移动顶点的位置
- 立方体贴图(Cubemap):用于环境反射
- 阴影贴图(Shadowmap):存储光源视角的深度信息
(2)逐片段操作(Per-Fragment Operations):
深度测试(Depth Test):比较片段深度值和深度缓冲区(Z-Buffer)中的值,丢弃被遮挡的片段。这是解决可见性问题的关键技术。
模板测试(Stencil Test):利用模板缓冲区(Stencil Buffer)标记特定区域,实现轮廓、镜子等特效。
混合(Blending):处理片段颜色和帧缓冲区中已有颜色的混合,主要用于渲染透明物体(玻璃、火焰)。
2.8 第六阶段:输出与后处理
输出到帧缓冲区(Frame Buffer):通过所有测试的片段,将其颜色值写入帧缓冲区。
后处理(Post-Processing):对整张渲染好的图像进行屏幕空间的特效处理。后处理通常通过在全屏四边形上执行一个特殊的 Shader 来实现。
常见后处理效果:
- Bloom:泛光效果,让明亮区域扩散出光芒
- 色调映射(Tone Mapping):将 HDR 颜色转换到屏幕能显示的 LDR
- 颜色校正(Color Grading):调整整体颜色和氛围
- 景深(Depth of Field):模拟相机焦点清晰、背景模糊的效果
- SSAO(屏幕空间环境光遮蔽):在屏幕空间计算细微阴影,增加立体感
- SSR(屏幕空间反射):在屏幕空间计算反射效果
三、光照模型
3.1 Phong 模型
计算光照最常用的经验模型,参数信息都是经验得到的,没有实际的物理意义。将光照分为三个部分:
- 漫反射分量:光线照射到粗糙表面后向各个方向均匀反射
- 镜面高光:光线照射到光滑表面后沿反射方向集中反射
- 环境光:模拟场景中其他物体反射的间接光照,在光照信息上叠加一个较小的常量
3.2 Blinn-Phong 模型
对 Phong 模型的改进。Phong 模型在处理高光时,当观察方向和反射光线夹角大于 90 度时,镜面反射分量会被消除,导致高光不连续。
Blinn-Phong 引入了半角向量(Halfway Vector):
|
|
用半角向量 H 和法线 N 的夹角来计算镜面反射。无论观察者在哪个方向,半角向量和法线夹角都不会超过 90 度,不会出现高光不连续的问题。
3.3 PBR(基于物理的渲染)
目标:物理正确——材质参数具有真实的物理意义,在任何光照环境下都能表现出一致的行为。
核心参数:
- 金属度(Metallic):材质的金属特性
- 粗糙度(Roughness):表面的光滑程度
- AO(环境光遮蔽):环境光照下的遮蔽信息
图像-Based Lighting(IBL):使用环境贴图来模拟全局光照,配合 PBR 参数实现逼真的光照效果。
四、渲染路径(Rendering Path)
4.1 前向渲染(Forward Rendering)
原理:在每个物体的片段着色器中,对所有影响该片段的光源进行循环计算。
流程:
|
|
优点:
- MSAA 等抗锯齿支持好
- Shader 编写直观
缺点:
- 光源数量多时性能下降极快
- 复杂度 = O(物体数 × 光源数)
适用场景:移动平台、光源数量较少的场景。
4.2 延迟渲染(Deferred Rendering)
核心思想:将几何和光照计算完全分离。“延迟"了昂贵的光照计算。
Pass 1:几何通道(G-Buffer Pass)
不计算任何光照,只存储所有物体的几何信息到多个渲染目标(MRT)中。这些渲染目标集合称为 G-Buffer(Geometry Buffer)。
G-Buffer 通常包含:
- 漫反射颜色(Albedo)
- 世界空间法线(World Normal)
- 世界空间位置(World Position)/ 深度
- 金属度 / 粗糙度等 PBR 参数
Pass 2:光照通道(Lighting Pass)
利用 G-Buffer 中的信息计算光照。不再绘制任何场景物体,针对每个光源在屏幕上绘制代表其影响范围的形状,对覆盖的每个像素从 G-Buffer 中读取信息进行光照计算。
核心优势:复杂度变为 O(N),只与屏幕像素数和光源数量有关,与场景几何复杂度无关。渲染 1000 个三角形和 100000 个三角形,如果最终屏幕像素数和光源数相同,光照计算开销是一样的。
缺点:
- 与 MSAA 不兼容
- 对透明物体处理困难
- G-Buffer 占用大量显存带宽
适用场景:光源数量多、场景复杂的 PC/主机游戏。
4.3 前向 +(Forward+)
现代引擎(如 URP/HDRP)提供的混合方案。将屏幕分块(Tile),每块记录影响该块的光源列表,在片段着色器中只计算当前块的光源。
五、阴影(Shadows)
5.1 Shadowmap 原理
阴影贴图技术是一种渲染到纹理的技术。原理非常简单:
第一步:从光源位置渲染一遍场景,将深度信息写入阴影贴图。
第二步:正常渲染场景,用阴影贴图判断每个片段是否落在阴影中。
|
|
5.2 阴影粉刺(Shadow Acne)
由于 Shadowmap 精度问题,平面上的某些像素深度比深度贴图大,某些比深度贴图小,导致某些地方有阴影、某些没有的粉刺现象。
解决方案:使用**偏移量(Bias)**对阴影贴图中的深度值进行调整。
|
|
注意:偏移量过大会导致阴影偏离物体的失真现象(Peter Panning)。偏移量大小与平面倾斜度有关,平面越倾斜,需要的偏移量越大。
5.3 阴影走样(Aliasing)
阴影边缘出现锯齿。可以提升 Shadowmap 分辨率来降低走样影响,但同时也增大了显存消耗。
5.4 软阴影
理想化的点光源(体积无限小)会产生硬阴影,因为光线要么完全被挡住(全阴影),要么完全没被挡住。
软阴影的本质是模拟光源的体积,计算片元对光源的可见覆盖率。覆盖率从 0%(本影)到 100%(完全照亮)的连续变化形成半影的过渡区域。
5.5 PCF(百分比渐近过滤)
传统硬阴影:对阴影贴图采样一次,结果非 0 即 1。
PCF:在当前采样点周围取一个区域(如 3×3 的 kernel),进行多次深度比较,得到多个非 0 即 1 的结果,然后取平均值。
|
|
这个平均值不再是二元的 0 或 1,而是介于 0 到 1 之间的灰度值,在视觉上产生边缘模糊效果。
5.6 PCSS(百分比渐近软阴影)
PCSS 是比 PCF 更高级的软阴影技术,很多引擎的标配:
- Blocker Search:在当前片段位置,搜索区域内有多少深度值比当前片段小(遮挡物),计算平均深度
- Penumbra Size Estimation:根据光源大小、遮挡物平均深度和当前片段深度,计算半影宽度(基于相似三角形原理)
- PCF Filtering:用上一步计算出的动态半影宽度决定 PCF 滤波的 Kernel 大小
|
|
六、Draw Call
6.1 什么是 Draw Call
Draw Call 是 CPU 调用图形 API(OpenGL、DirectX)向 GPU 发起的绘制命令。每个 Draw Call 命令都包含了绘制一个或多个图形对象所需的所有信息(顶点数据、纹理、Shader 等)。
6.2 Draw Call 过多为什么会影响性能
每个 Draw Call 都有CPU 开销:
- CPU 准备渲染数据
- CPU 调用图形 API
- 驱动处理和验证命令
- GPU 执行命令
当 Draw Call 过多时,CPU 忙于提交命令,无法处理其他逻辑,成为CPU-Bound瓶颈。
6.3 影响 Draw Call 数量的因素
| 因素 | 说明 |
|---|---|
| 对象数量 | 每个对象至少需要一次 Draw Call |
| 材质数量 | 每个材质需要一次 Draw Call |
| 纹理贴图 | 纹理切换需要额外 Draw Call |
| Shader 复杂度 | 复杂 Shader 可能需要多个 Pass |
示例:
- 100 个立方体 + 同一材质 → 100 个 Draw Call
- 100 个立方体 + 不同材质 → 大于 100 个 Draw Call(因为材质和纹理不同)
- 复杂角色模型 + 多个纹理和材质 → 多个 Draw Call(模型本身 + 每个纹理)
七、合批(Batching)
合批将多个物体的渲染数据合并,用一次 Draw Call 绘制,极大减轻 CPU 负担。
7.1 静态合批(Static Batching)
原理:将不会移动的静态物体在运行前合并成一个大的顶点/索引缓冲区。
工作原理:
- 预处理:Unity 编辑器在构建时将所有标记为
Static且使用相同材质的物体的网格数据合并 - 渲染:合并后只需一次 Draw Call 全部绘制
优点:性能收益巨大,最有效的减少 Draw Call 手段之一。
缺点:
- 内存开销增加(合并后的网格额外占内存)
- 只适用于静态物体(不能再移动、旋转、缩放)
7.2 动态合批(Dynamic Batching)
原理:Unity 运行时每帧自动将小型动态物体的网格数据合并。
工作原理:
- CPU 每帧遍历使用同一材质且满足条件的小型动态物体
- 将顶点数据从本地空间变换到世界空间(CPU 上完成!)
- 将变换后的顶点数据合并到一个公共的顶点缓冲区
- 发起一次 Draw Call
优点:适用于动态物体。
限制条件(极易被打断):
| 条件 | 说明 |
|---|---|
| 顶点数限制 | 通常不超过 300 个 |
| 同一材质 | 必须是完全相同的材质实例 |
| 统一缩放 | 不能有非统一缩放(如 (1,2,1)) |
| 多 Pass | 不能包含多个 Pass 的 Shader |
7.3 GPU Instancing
原理:在一次渲染调用中同时绘制多个相同模型的实例。
优点:
- 减少 CPU 开销
- 适合大量相同物体(草地、人群、粒子)
注意:与动态合批互斥。如果材质开启了 GPU Instancing,物体不会尝试动态合批。
7.4 合批被打断的常见情况
-
使用不同的材质实例(最常见!):两个材质球即使看起来一样,只要是不同的资产,就会被视为不同的材质实例,中断合批。解决方案:共享同一个材质实例,使用
MaterialPropertyBlock修改属性。 -
渲染顺序中断:
|
|
- 不满足动态合批的苛刻条件:顶点超标、非统一缩放、多 Pass Shader。
八、渲染优化技术
8.1 渲染优化核心思路
面试口诀:合并、减面、剔除
| 技术 | 原理 | 效果 |
|---|---|---|
| LOD | 远处物体用低面数模型 | 减少 GPU 渲染量 |
| 视锥剔除 | 剔除视野外的物体 | 减少不必要的绘制 |
| 遮挡剔除 | 剔除被挡住的物体 | 进一步减少绘制 |
| 静态合批 | 合并静态物体 | 大幅减少 Draw Call |
| 动态合批 | 合批小型动态物体 | 减少 Draw Call(有严格限制) |
| GPU Instancing | 一次绘制多个相同模型 | 减少 CPU 开销 |
| 屏幕空间技术 | 在屏幕空间采样计算 | 减少场景计算需求 |
| Shader 指令优化 | 减少不必要的计算和采样 | 降低 GPU 开销 |
8.2 LOD(Level of Detail)
LOD 技术在物体远离相机时使用低细节模型,减少多边形数量。
|
|
Unity 中通过 LOD Group 组件实现,自动根据物体与相机的距离切换 LOD 级别。
8.3 视锥剔除(Frustum Culling)
检查物体是否在相机的视锥体内,不在则不渲染。
Unity 默认启用视锥剔除,不需要手动设置。
8.4 遮挡剔除(Occlusion Culling)
通过**遮挡剔除数据(Occlusion Data)**预计算场景中的遮挡关系,运行时判断被遮挡的物体不渲染。
需要预先 Bake 遮挡数据(Window → Rendering → Occlusion Culling)。
8.5 屏幕空间技术
- SSAO(屏幕空间环境光遮蔽):在屏幕空间计算像素间的遮挡关系,产生细微的环境阴影
- SSR(屏幕空间反射):利用屏幕空间的颜色和深度信息计算反射效果
- SSGI(屏幕空间全局光照):近似全局光照效果
屏幕空间技术的共同特点:只在屏幕空间采样计算,不涉及场景几何体,性能开销相对可控。
8.6 Shader 优化建议
- 减少纹理采样次数
- 使用精度更低的变量(
half代替float) - 避免在片段着色器中做复杂计算(移到顶点着色器)
- 使用宏控制 Shader 变体数量
- 避免使用
if分支(GPU 不擅长分支预测)
8.7 常用渲染分析工具
| 工具 | 用途 |
|---|---|
| Unity Profiler | 分析 CPU/GPU/内存瓶颈 |
| Frame Debugger | 逐帧查看 Draw Call |
| RenderDoc | 深度 GPU 调试和分析 |
| GPU Profiler | 分析 GPU 各阶段耗时 |
| Memory Profiler | 分析纹理和网格内存占用 |
本文涵盖 Unity 渲染的核心知识体系,从渲染管线对比到完整图形渲染管线流程,再到光照模型、阴影、合批、优化技术等,适合 Unity 开发者和图形学学习者系统性复习。