🎮 Unity GPU 渲染优化完全手册:从 DrawCall 到 Shader 的性能飞跃

💡 GPU 优化的价值

  • DrawCall 居高不下,帧率提不上来?
  • OverDraw 太严重,GPU 压力过大?
  • 想优化 Shader,却不知从何入手?
  • 移动平台优化有哪些特殊注意事项?

这篇文章! 将系统讲解 Unity GPU 优化技术,从渲染管线到着色器,让画面更流畅!


一、GPU 性能瓶颈分析

1.1 GPU 瓶颈类型

瓶颈类型 特征 检测方法
像素填充受限 分辨率高、OverDraw 严重 Frame Debugger / RenderDoc
顶点处理受限 顶点数多、复杂几何体 减少顶点数测试
带宽受限 纹理采样多、高分辨率纹理 降低纹理分辨率测试
算力受限 复杂着色器计算 简化 Shader 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────────────────┐
│ GPU 渲染管线瓶颈定位 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 应用阶段 几何阶段 光栅化阶段 像素处理阶段 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ CPU │──────>│ 顶点着色 │──────>│ 裁剪/ │─────>│ 像素着色 │ │
│ │ 准备数据 │ │ 器 │ │ 透视除法│ │ 器 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Draw Calls 顶点数量 图元数量 像素填充率 │
│ 合批优化 LOD 优化 裁剪优化 OverDown 优化 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1.2 性能分析工具

工具 平台 用途
Unity Profiler 全平台 整体性能分析
Frame Debugger Editor 单帧渲染分析
RenderDoc Desktop 详细 GPU 分析
Xcode Instruments iOS GPU Driver / Tiler
Snapdragon Profiler Android Adreno GPU 分析
Mali Graphics Debugger Android Mali GPU 分析

二、DrawCall 优化

2.1 DrawCall 原理

1
2
3
4
5
6
7
单个 DrawCall 的开销:
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ CPU 准备 │ 数据传输 │ GPU 状态 │ 渲染执行 │
│ 渲染状态 │ 到 GPU │ 切换 │ │
└─────────────┴─────────────┴─────────────┴─────────────┘
↑ ↑ ↑
[固定开销] [带宽开销] [状态切换开销]

2.2 合批技术

技术 原理 适用场景 限制
Static Batch 运行前合并静态几何体 不移动的场景物体 内存增加
Dynamic Batch 运行时合并小网格 <900顶点的动态物体 顶点数限制
GPU Instancing 单次绘制多实例 相同材质的重复物体 需要 Shader 支持
SRP Batcher 按Shader变体分组 可编程渲染管线 Shader 兼容性

2.3 Static Batching

1
2
3
4
5
6
7
8
// ✅ 设置静态标志
gameObject.isStatic = true;

// 或在 Inspector 中勾选 Static

// 运行时 Static Batching:
// Editor: Build Player 时自动合并
// Runtime: StaticBatchingUtility.CombineRoots(combined);
优点 缺点
大幅减少 DC 内存增加 (合并多份网格)
运行时无开销 不适合动态物体
跨平台兼容 合并后无法单独移动

2.4 Dynamic Batching

1
2
3
4
5
6
7
8
9
// ✅ 启用动态批处理
PlayerSettings.SetMobileMTRendering(BuildTargetGroup.Android, false);
PlayerSettings.SetMobileMTRendering(BuildTargetGroup.iOS, false);

// 动态批处理限制:
// - 顶点数 < 900 (移动端更严格)
// - 相同 Material
// - 相同 Shader 变体
// - 不使用多 Pass Shader

2.5 GPU Instancing

1
2
3
4
5
6
7
8
9
10
// ✅ Shader 启用 Instancing
#pragma multi_compile_instancing

// MeshRenderer 启用
MeshRenderer renderer = GetComponent<MeshRenderer>();
renderer.enableInstancing = true;

// Graphics.DrawMeshInstanced
Matrix4x4[] matrices = new Matrix4x4[instancesCount];
Graphics.DrawMeshInstanced(mesh, 0, material, matrices);

💡 提示:GPU Instancing 是移动端的首选合批方式!


三、OverDraw 优化

3.1 OverDraw 问题

1
2
3
4
5
6
7
8
9
10
11
OverDraw 示意图:
相机视野


┌─────────────────────┐
│ ╔═══╗ ╔═══╗ │ 每个方块被绘制多次
│ ║ A ║ ║ B ║ │ A(背景) -> B(中景) -> C(前景)
│ ╚═══╝ ╚═══╝ │ OverDraw = 3x
│ ╔══════╗ │
│ ║ C ║ │
└────┴───────┴────────┘

3.2 OverDraw 检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ✅ Frame Debugger 查看 OverDraw
// Window > Analysis > Frame Debugger
// 选择 OverDraw 模式

// ✅ 创建 OverDraw 可视化 Shader
Shader "Custom/OverDraw" {
SubShader {
Tags { "Queue"="Transparent+1" }
Pass {
Blend One One
ZWrite Off ZTest Always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata { float4 vertex : POSITION; };
struct v2f { float4 vertex : SV_POSITION; };
v2f vert(appdata v) {
v2f o; o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(0.1, 0.1, 0.1, 0);
}
ENDCG
}
}
}

3.3 OverDraw 优化策略

策略 说明 效果
渲染顺序 不透明物体从前向后 减少 Early-Z 剔除失败
剔除背面 剔除相机看不见的背面 减少填充率
遮挡剔除 不渲染被遮挡物体 大幅减少 OverDraw
透明合并 合并透明 UI 元素 减少 UI OverDraw

四、LOD (Level of Detail)

4.1 LOD 原理

1
2
3
4
5
6
7
8
9
10
11
12
距离 / LOD 级别:
┌─────────────────────────────────────────┐
│ │
│ Far LOD2 (低模) ◄─────────────────┐ │
│ 30m │ │
│ Mid LOD1 (中模) ◄──────────┐ │ │
│ 15m │ │ │
│ Near LOD0 (高模) ◄────┐ │ │ │
│ 5m │ │ │ │
│ ▼ ▼ ▼ │
│ Camera │
└─────────────────────────────────────────┘

4.2 LOD Group 使用

1
2
3
4
5
6
7
8
9
10
11
// ✅ 代码设置 LOD
LODGroup lodGroup = gameObject.AddComponent<LODGroup>();

// 创建 LOD 级别
LOD[] lods = new LOD[3];
lods[0] = new LOD(0.5f, new Renderer[] { highPolyRenderer }); // 50%
lods[1] = new LOD(0.2f, new Renderer[] { midPolyRenderer }); // 20%
lods[2] = new LOD(0.01f, new Renderer[] { lowPolyRenderer }); // 1%

lodGroup.SetLODs(lods);
lodGroup.RecalculateBounds();

4.3 LOD 切换距离建议

平台 LOD 数量 切换距离
PC/Console 3-4 级 较远
Mobile High 3 级 中等
Mobile Low 2-3 级 较近

五、遮挡剔除 (Occlusion Culling)

5.1 遮挡剔除原理

1
2
3
4
5
6
7
8
9
10
11
12
13
无遮挡剔除:
Camera

├──> 渲染 A (可见)
├──> 渲染 B (被 A 挡住但仍渲染)
└──> 渲染 C (被 A 挡住但仍渲染)

有遮挡剔除:
Camera

├──> 渲染 A (可见)
├──> 跳过 B (被遮挡)
└──> 跳过 C (被遮挡)

5.2 遮挡剔除配置

1
2
3
4
5
6
7
8
// ✅ 启用遮挡剔除
// Project Settings > Player > Occlusion Culling

// 烘焙遮挡数据:
// Window > Rendering > Occlusion Culling > Bake

// 运行时控制
GameObject.GetComponent<OcclusionPortal>().open = true;

5.3 Occlusion Portal

1
2
3
4
// ✅ 门/窗等动态开口使用 Occlusion Portal
OcclusionPortal portal = gameObject.AddComponent<OcclusionPortal>();
portal.open = false; // 门关闭时遮挡生效
portal.open = true; // 门开启时遮挡失效

六、Shader 优化

6.1 着色器复杂度优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ 复杂计算
fixed4 frag(v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv);
// 大量 sin/cos/pow 等复杂运算
col.rgb *= sin(_Time.y * 10) * pow(col.r, 2.0);
return col;
}

// ✅ 简化计算
fixed4 frag(v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv);
// 使用查表或预计算
col.rgb *= _ColorMultiplier.rgb;
return col;
}

6.2 移动平台 Shader 注意事项

问题 优化方案
pow/exp/log 使用查找表
sin/cos 使用近似函数
纹理采样 使用 Mipmaps
精度 mobile 使用 half/fixed
分支 尽量避免动态分支

6.3 精度优化

1
2
3
4
5
6
7
8
9
// ✅ 移动平台使用低精度
// Vertex Shader
float4 pos : POSITION; // 需要高精度
half3 normal : NORMAL; // 法线用 half
float2 uv : TEXCOORD0; // UV 用 float

// Fragment Shader
fixed4 col : SV_Target; // 颜色用 fixed
half3 viewDir : TEXCOORD0; // 方向用 half

七、纹理优化

7.1 纹理格式

平台 推荐格式 说明
iOS ASTC 4x4 / PVRTC 硬件支持
Android ETC2 / ASTC 兼容性考虑
PC DXT / BC7 压缩率高

7.2 纹理尺寸

1
2
3
4
5
6
7
// ✅ 最大纹理尺寸设置
PlayerSettings.SetTextureStreamingBudgetPerScene(512 * 1024 * 1024);

// Mipmap Streaming
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
importer.streamingMipmapsActive = true;
importer.streamingMipmapsAddAllCameras = true;

7.3 纹理图集

1
2
3
4
5
6
7
8
9
10
11
12
图集优势:
┌─────────────────────────────────────┐
│ 单张纹理 纹理图集 │
│ ┌───┐ ┌───┐ ┌───┐ ┌───────┐ │
│ │ A │ │ B │ │ C │ │ A B C │ │
│ └───┘ └───┘ └───┘ │ D E F │ │
│ ┌───┐ ┌───┐ ┌───┐ │ G H I │ │
│ │ D │ │ E │ │ F │ └───────┘ │
│ └───┘ └───┘ └───┘ │
│ │
│ 6 DrawCalls 1 DrawCall │
└─────────────────────────────────────┘

八、光照优化

8.1 实时光照 vs 烘焙光照

类型 性能开销 质量 用途
实时点光 动态光源
实时聚光灯 很高 手电筒等
烘焙光照 静态场景
Light Probes 动态物体
LPPV 大场景动态物体

8.2 光照优化策略

1
2
3
4
5
6
7
8
9
// ✅ 设置光渲染模式
Light light = GetComponent<Light>();
light.renderMode = LightRenderMode.ForcePixel; // 重要光源
light.renderMode = LightRenderMode.ForceVertex; // 次要光源
light.shadows = LightShadows.None; // 无阴影

// ✅ 限制实时光数量
// 移动端建议: 1-2 个实时光
// PC 建议: 3-4 个重要实时光

九、粒子系统优化

9.1 粒子优化策略

优化项 说明
减少粒子数 使用最大粒子数限制
使用 Prewarm 预热避免启动开销
禁用不必要的模块 Collision / Trails 等
使用 Sprite 粒子 比 Mesh 粒子快
降低更新频率 使用 Fixed Timestep

9.2 粒子配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ✅ 优化粒子系统
ParticleSystem ps = GetComponent<ParticleSystem>();

var main = ps.main;
main.maxParticles = 100; // 限制最大粒子数
main.prewarm = true; // 启用预热
main.simulationSpace = ParticleSystemSimulationSpace.Local; // 使用局部空间
main.scalingMode = ParticleSystemScalingMode.Local;

// 禁用碰撞
var collision = ps.collision;
collision.enabled = false;

// 降低渲染优先级
var renderer = ps.GetComponent<ParticleSystemRenderer>();
renderer.renderMode = ParticleSystemRenderMode.Stretch;
renderer.alignment = ParticleSystemRenderSpace.View;

十、UI 渲染优化

10.1 UGUI 优化要点

优化项 说明
Canvas 分割 不同频率更新的 UI 分离
隐藏空 Text 空 Text 会占用大量内存
避免 Layout 减少 Layout Group 嵌套
使用 Sprite Atlas 合并 UI 图集
禁用 Raycast Target 不需要交互的元素

10.2 Canvas 设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ✅ UI Canvas 优化
Canvas canvas = GetComponent<Canvas>();

// 高频更新 UI (血条等)
canvas.renderMode = RenderMode.ScreenSpaceOverlay;

// 低频更新 UI
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = uiCamera;

// 静态 UI
Canvas additionalCanvas = uiCanvas.gameObject.AddComponent<Canvas>();
additionalCanvas.overrideSorting = true;
additionalCanvas.sortingOrder = 100;

十一、移动平台特殊优化

11.1 移动 GPU 特性

GPU 特点 优化建议
Adreno TBDR 架构 控制 OverDraw
Mali TBDR 架构 减少带宽压力
PowerVR TBDR 架构 利用 HSR
Tegra IMR 架构 关注像素复杂度

💡 TBDR (Tile-Based Deferred Rendering):移动 GPU 主流架构,对 OverDraw 敏感。

11.2 移动平台检查清单

  • 禁用实时阴影
  • 使用烘焙光照
  • 限制粒子数量
  • 简化 Shader 复杂度
  • 使用 ASTC 纹理压缩
  • 启用 Mipmap Streaming
  • 控制渲染分辨率
  • 使用 16bit RenderTexture

十二、优化总结

12.1 快速检查清单

类别 检查项 目标
DrawCall 减少 DC <100 (Mobile)
OverDraw 控制填充率 <2x 平均
Shader 简化复杂度 移动端 <50 指令
纹理 压缩格式 全部压缩
光照 烘焙为主 <2 实时光
粒子 限制数量 <500 同屏

12.2 性能目标

平台 目标帧率 GPU 时间预算
PC 60/144 FPS <16ms / <6ms
Console 30/60 FPS <33ms / <16ms
Mobile High 60 FPS <16ms
Mobile Mid 30 FPS <33ms
Mobile Low 30 FPS <33ms

十三、参考资料


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com