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 组件 |
| 数据未加载 |
数据文件丢失 |
重新烘焙 |
| 对象移动 |
移动对象不支持 |
使用目标单元格树 |
六、检查清单
七、参考资料
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com