Unity Camera.cullingMask 使用指南:Layer 精准控制、XR 多视图与性能提升
关键词:Unity、Camera、Culling Mask、Layer 控制、XR 渲染分离、UI 显隐、性能优化
特别说明:
本文为近期项目所遇问题的总结,仅纯文字记录,截图不便分享。见谅!
文章目录
- Unity Camera.cullingMask 使用指南:Layer 精准控制、XR 多视图与性能提升
- 业务故事:一场“UI 不该出现在眼镜里”的现场事故
- 一、故事起源:展厅演示的第一天
- 二、事故现场:UI “失控”现身
- 三、初步排查与手动修复
- 四、团队决策:封装一键Layer控制工具
- 技术实践指南:Unity 相机 Culling Mask 控制
- 1. 背景与动机
- 2. Culling Mask 原理解析
- 2.1 定义
- 2.2 示例代码
- 3. 实现目标与功能设计
- 3.1 设计要求
- 4. 完整脚本实现
- 5. 示例调用与调试方法
- 5.1 示例代码
- 5.2 编辑器拓展建议
- 6. 应用场景分析
- 6.1 XR 项目中 UI 分离显示
- 6.2 开发调试隔离
- 6.3 Portal 镜头或切换效果
- 7. 扩展功能建议
- 7.1 支持 LayerMask 类型参数
- 7.2 添加排除层功能
- 8. 总结与最佳实践
- 9. 参考资料
业务故事:一场“UI 不该出现在眼镜里”的现场事故
“当你的UI界面在VR眼镜里挡住了用户视野,却在PC上毫无问题时,你会怎么做?”
一、故事起源:展厅演示的第一天
展馆模型真实还原,观众佩戴 VR 头显后,可漫游各个展区;同时,操作员在 PC 端可通过鼠标实时监控视角、触发高亮或切屏指令。
为了满足双端显示需求,我们配置了三台相机:
- PC 主相机:渲染环境、UI、调试信息;
- VR 头显相机:只渲染环境与玩家视角;
- UI 专用相机:渲染所有 Canvas 元素,包含菜单、指南、调试层。
二、事故现场:UI “失控”现身
内部测试环境一切正常:PC 端界面完整,VR 端只见清爽的展示,无多余遮挡。
客户演示当天,他戴上头显,第一反应却是:“这 UI 是怎么回事?我眼前都是菜单,根本看不到展品!”
现场:
- VR 端 UI 堆叠、遮挡用户视野;
- 点击操作失效(实际上点击走的是 PC 相机);
- 用户无法退出 UI,只能取下头显。
我们一看,果然:VR 相机和 UI 相机都在渲染相同的 UI Layer,“打包”给了头显。
三、初步排查与手动修复
打开 Unity Inspector,发现:
XRCamera.cullingMask = Everything
UICamera.cullingMask = Everything
两台相机都在渲染所有 Layer。
于是,测试人员反复手动:
- 取消 XRCamera 的 UI Layer;
- 勾选 UICamera 的 UI Layer;
- 切场景、切模式,效果断断续续,易出错;
手动操作效率低,且在频繁切换场景时容易遗漏。
四、团队决策:封装一键Layer控制工具
为避免 “手动取消勾选” 的痛苦,我们决定封装一个可复用、一键切换的 Culling Mask 控制组件,满足:
- 只渲染指定 Layer
- 恢复渲染所有 Layer
- 回退到上一次状态
- 自动识别 Camera.main
- 支持多 Layer 名称输入
一旦写出工具,任何相机都可在 Inspector 或运行时,通过简短代码切换渲染范围。
上述故事纯属虚构,下面开始正文。
技术实践指南:Unity 相机 Culling Mask 控制
1. 背景与动机
在 Unity 项目开发中,尤其是涉及 多层级 UI 管理、XR/VR 分视图渲染、动态视角切换与性能调试 时,我们经常需要灵活控制相机渲染对象的范围。
本质上,我们希望控制:某个 Camera 渲染哪些 Layer。
常见应用如:
- XR 项目中:仅 PC 屏幕显示 UI,VR 头显中不显示;
- Portal 或多视角切换场景:主相机与子视角相机渲染不同对象;
- 开发调试模式:仅在 Debug 模式下显示 Gizmo 层;
- 镜头特效系统:仅渲染特定对象做后处理。
这些都离不开对 Unity 相机的 Culling Mask
的精细控制。
2. Culling Mask 原理解析
2.1 定义
Unity 中的每个 Camera 都有一个 Culling Mask 属性,它是一个 32 位的位掩码(bitmask),用于表示该相机应该 “看到” 哪些 Layer。
Camera.cullingMask: int → 每一位代表一个 Layer(最多支持 32 个)
2.2 示例代码
// 只显示 UI 层
camera.cullingMask = 1 << LayerMask.NameToLayer("UI");// 同时显示多个层(使用按位或)
camera.cullingMask = (1 << Layer1) | (1 << Layer2);// 使用 LayerMask 工具方法
camera.cullingMask = LayerMask.GetMask("Default", "UI", "GuideLayer");
3. 实现目标与功能设计
我们封装一个组件 CameraCullingMaskController
,具备以下功能:
方法名 | 功能描述 | ||
---|---|---|---|
`ApplyLayersOnly("UI | Default")` | 仅显示指定 Layer(多个以“ | ”分隔) |
RestoreEverything() | 恢复显示所有 Layer(即 LayerMask.All) | ||
RestoreOriginal() | 回退到上一次设置前的原始状态 |
3.1 设计要求
- 可自动识别并使用
Camera.main
; - 支持传入多个 Layer 名;
- Layer 合法性校验与报错提示;
- 可扩展性好,便于嵌入 XR 项目。
4. 完整脚本实现
using UnityEngine;namespace XRCoreRuntime.Runtime.Component
{/// <summary>/// 控制 Camera 的 CullingMask 显示特定 Layer 或恢复状态/// </summary>public class CameraCullingMaskController : MonoBehaviour{[Header("指定相机,未指定则使用 Camera.main")]public Camera? mainCam;private int originalCullingMask;public void ApplyLayersOnly(string targetLayers){if (string.IsNullOrEmpty(targetLayers)) {Debug.LogWarning("传入的 Layer 字符串为空!");return;}mainCam ??= Camera.main;if (mainCam == null) {Debug.LogWarning("Camera 未找到!");return;}originalCullingMask = mainCam.cullingMask;var names = targetLayers.Split('|');int mask = 0;foreach (var name in names){int layer = LayerMask.NameToLayer(name.Trim());if (layer == -1){Debug.LogError($"Layer \"{name}\" 不存在!");continue;}mask |= 1 << layer;}if (mask == 0){Debug.LogWarning("未能解析出有效 Layer!");return;}mainCam.cullingMask = mask;}public void RestoreEverything(){mainCam ??= Camera.main;if (mainCam != null)mainCam.cullingMask = ~0;}public void RestoreOriginal(){mainCam ??= Camera.main;if (mainCam != null)mainCam.cullingMask = originalCullingMask;}}
}
5. 示例调用与调试方法
5.1 示例代码
var ctrl = GetComponent<CameraCullingMaskController>();// 显示 UI 和 GuideLayer
ctrl.ApplyLayersOnly("UI|GuideLayer");// 恢复所有
ctrl.RestoreEverything();// 回退到原状态
ctrl.RestoreOriginal();
5.2 编辑器拓展建议
可通过 CustomEditor
创建按钮,直接点击控制:
@startuml
actor Developer
participant InspectorButton
participant CameraCullingMaskControllerDeveloper -> InspectorButton : 点击 "显示 UI"
InspectorButton -> CameraCullingMaskController : ApplyLayersOnly("UI")Developer -> InspectorButton : 点击 "恢复全部"
InspectorButton -> CameraCullingMaskController : RestoreEverything()
@enduml
6. 应用场景分析
6.1 XR 项目中 UI 分离显示
场景:希望 UI 仅显示在 PC 上,头显中不显示。
做法:
// VR 主相机只渲染环境与默认层
ctrl.ApplyLayersOnly("Environment|Default");
// UI 相机只渲染 UI
ctrl.ApplyLayersOnly("UI");
6.2 开发调试隔离
将调试工具放入 DebugLayer
,仅调试时开启:
ctrl.ApplyLayersOnly("Default|DebugLayer");
6.3 Portal 镜头或切换效果
用多台 Camera 渲染不同视角,组合效果类似:
主相机:环境 + 玩家
镜头相机:仅渲染特效层
7. 扩展功能建议
7.1 支持 LayerMask 类型参数
将 ApplyLayersOnly(string)
改为 ApplyLayersOnly(LayerMask mask)
,便于在编辑器中使用 MaskField
。
7.2 添加排除层功能
增加方法:
public void ApplyExcludeLayers(string excludeLayers);
使用位操作方式:
mainCam.cullingMask = ~LayerMask.GetMask("Debug", "UI");
8. 总结与最佳实践
本文从业务事故切入,结合实际项目需求,详尽讲解了 Unity 中相机 Culling Mask 的使用方法。通过封装 CameraCullingMaskController
,实现了:
- 动态 Layer 渲染控制;
- UI 显示隔离;
- XR 多视图调度;
- Portal 场景优化;
- 性能剔除带来的帧率提升。
技术的价值在于解决实际问题。
下次再遇到“UI 不该出现在眼镜里”的烦恼时,试试这套工具,让每个相机都“只看”自己应该看的内容。
9. 参考资料
-
Unity 官方文档
- Camera.cullingMask
- LayerMask
从最初的“UI 失灵”到完整的 Culling Mask 控制方案,我们体会到:
一行代码的变化,能让整个系统更加健壮、可维护,也让开发效率成倍提升。
希望这篇文章,既让你在项目演示不再手忙脚乱,也为日后更复杂的多相机、多视图场景打下坚实基础。