Unity 遮挡剔除 (Occlusion Culling) 完全指南

深入解析 Unity 遮挡剔除系统的工作原理、配置方法和最佳实践,有效降低 DrawCall 并提升渲染性能。


一、什么是遮挡剔除

1.1 核心概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
遮挡剔除原理:
┌─────────────────────────────────────────────────────────┐
│ │
│ 问题: Overdraw (过度绘制) │
│ ┌─────────────────────────────────────────────┐ │
│ │ 👁 摄像机 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ 墙 │ │ 障 │ │ 墙 │ ← 墙被遮挡但仍绘制 │ │
│ │ └────┘ └────┘ └────┘ │ │
│ │ ┌────┐ │ │
│ │ │ 敌 │ ← 可见 │ │
│ │ └────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 解决方案: Occlusion Culling │
│ → 只渲染摄像机可见的对象 │
│ → 减少 DrawCall → 提升性能 │
│ │
└─────────────────────────────────────────────────────────┘

1.2 Occlusion Culling vs Frustum Culling

特性 Frustum Culling (视锥体剔除) Occlusion Culling (遮挡剔除)
剔除范围 摄像机视野之外的对象 被其他对象遮挡的对象
实现方式 Unity 内置自动 需要手动烘焙数据
剔除对象 视野外物体 OverDraw 隐藏的物体
性能开销 中等(需要查询数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
剔除层次:
┌─────────────────────────────────────────────────────────┐
│ │
│ 第一步: Frustum Culling (Unity 自动) │
│ ┌─────────────┐ │
│ │ │ │ ← 摄像机视野 │
│ │ ┌──┼──┐ │ │
│ │ │ ● │ │ ← 视野外对象被剔除 │
│ │ └─────┘ │ │
│ └─────────────┘ │
│ ↓ │
│ 第二步: Occlusion Culling (需要烘焙) │
│ ┌─────────────────────────────────────────┐ │
│ │ 👁 墙1 [墙2] [敌] [墙3] │ │
│ │ ✗ ✗ ✓ ✗ │ │
│ │ ↑ ↑ ↑ │ │
│ │ 被剔除 被剔除 可见 被剔除 │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

二、工作原理

2.1 数据结构

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
28
29
30
遮挡剔除数据结构:
┌─────────────────────────────────────────────────────────┐
│ │
│ 场景包围盒 (Scene Bounding Box) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 单元格 (Cells) │ │
│ │ ┌───┬───┬───┬───┬───┬───┐ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ ├───┼───┼───┼───┼───┼───┤ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ ├───┼───┼───┼───┼───┼───┤ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ └───┴───┴───┴───┴───┴───┘ │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 二叉树 (Binary Tree) │ │
│ │ │ │
│ │ [根节点] │ │
│ │ / \ │ │
│ │ [左子] [右子] │ │
│ │ / \ / \ │ │
│ │ [叶] [叶] [叶] [叶] │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

2.2 双树结构

树类型 用途 对象类型
视图单元格树 存储静态对象的可见性索引 静态对象 (Static)
目标单元格树 存储移动对象的单元格位置 移动对象 (Dynamic)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
双树工作流程:
┌─────────────────────────────────────────────────────────┐
│ │
│ 视图单元格树 (View Cells Tree): │
│ ┌─────────────────────────────────────┐ │
│ │ Cell_1 → [对象A, 对象B, 对象C] │ │
│ │ Cell_2 → [对象A, 对象D] │ │
│ │ Cell_3 → [对象E, 对象F] │ │
│ │ ... │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 目标单元格树 (Target Cells Tree): │
│ ┌─────────────────────────────────────┐ │
│ │ 移动对象位置追踪 │ │
│ │ 动态更新可见性判断 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

2.3 工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行时流程:
┌─────────────────────────────────────────────────────────┐
│ │
│ 烘焙阶段 (Editor 中): │
│ ┌─────────────────────────────────────┐ │
│ │ 1. 虚拟摄像机在场景中移动 │ │
│ │ 2. 构建潜在可见对象集 (PVS) │ │
│ │ 3. 生成层级视图数据 │ │
│ │ 4. 保存为二进制数据文件 │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 运行阶段 (Runtime): │
│ ┌─────────────────────────────────────┐ │
│ │ 1. 摄像机查询当前单元格数据 │ │
│ │ 2. 获取潜在可见对象集 │ │
│ │ 3. 识别真正可见对象 │ │
│ │ 4. 只发送可见对象进行渲染 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

三、配置方法

3.1 Occlusion Culling 窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
配置窗口:
┌─────────────────────────────────────────────────────────┐
│ Window → Rendering → Occlusion Culling │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Occlusion Areas: │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ 可视区域预览 │ │ │
│ │ │ │ │ │
│ │ │ ┌────┐ │ │ │
│ │ │ │ 🎥 │ 摄像机位置 │ │ │
│ │ │ └────┘ │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ │ Bake: 烘焙遮挡数据 │ │
│ │ Clear: 清除已有数据 │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

3.2 Occludee Static 标记

对象特征 是否标记为 Occludee 原因
大型墙体 ✅ 是 能有效遮挡其他对象
大型建筑 ✅ 是 能有效遮挡其他对象
完全透明对象 ❌ 否 无法遮挡其他对象
半透明对象 ❌ 否 无法遮挡其他对象
小型对象 ❌ 否 遮挡效果不明显
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Occluder vs Occludee:
┌─────────────────────────────────────────────────────────┐
│ │
│ Occluder (遮挡物): │
│ ┌─────────┐ │
│ │ 墙 │ ← 能遮挡其他对象 │
│ │ │ │
│ └─────────┘ │
│ │
│ Occludee (被遮挡物): │
│ ┌─────────┐ │
│ │ 敌 │ ← 能被其他对象遮挡 │
│ │ │ │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

四、最佳实践

4.1 适用场景

场景类型 是否推荐 原因
室内场景 ✅ 强烈推荐 墙体多,遮挡明显
城市场景 ✅ 强烈推荐 建筑密集,遮挡多
开放地形 ⚠️ 谨慎使用 遮挡较少,开销可能大于收益
2D 游戏 ❌ 不推荐 无真实遮挡关系

4.2 优化建议

建议 说明
合理划分区域 按场景空间分布设置 Occlusion Areas
控制烘焙精度 平衡数据大小与剔除精度
静态对象优先 静态对象剔除效果更明显
测试效果 烘焙后在 Scene 视图预览剔除效果

4.3 性能权衡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
性能权衡分析:
┌─────────────────────────────────────────────────────────┐
│ │
│ 收益: │
│ • 减少 DrawCall │
│ • 降低 OverDraw │
│ • 提升帧率 │
│ │
│ 成本: │
│ • 增加包体大小 (~几MB) │
│ • 内存占用增加 │
│ • CPU 查询开销 │
│ • 烘焙时间开销 │
│ │
│ 决策: │
│ • 室内/城市场景: 收益 > 成本 ✅ │
│ • 开放地形: 收益 < 成本 ❌ │
│ │
└─────────────────────────────────────────────────────────┘

五、常见问题

5.1 烘焙失败

问题 可能原因 解决方案
无有效对象 场景中没有 Static 对象 标记对象为 Static
区域过小 Occlusion Area 太小 扩大区域范围
数据过大 场景过于复杂 简化场景或拆分区域

5.2 运行时无效

问题 可能原因 解决方案
未剔除 摄像机未设置 检查 Camera 组件
数据未加载 数据文件丢失 重新烘焙
对象移动 移动对象不支持 使用目标单元格树

六、检查清单

  • 确认场景类型适合使用遮挡剔除
  • 标记大型静态对象为 Occludee Static
  • 避免标记透明/小型对象
  • 设置合理的 Occlusion Area
  • 烘焙遮挡数据
  • 在编辑器中预览剔除效果
  • 运行测试验证性能提升
  • 监控内存和包体大小变化

七、参考资料


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