【Unity笔记】Unity Camera.cullingMask 使用指南:Layer 精准控制、XR 多视图与性能提升
引言
Camera.cullingMask 是 Unity 渲染管线中控制相机视野的关键属性,通过 Layer 位掩码(bitmask)决定相机渲染哪些对象层。默认值为 -1(全层渲染),但在复杂场景中(如多相机系统、XR 双目渲染、性能优化),精准控制 cullingMask 可显著提升性能并实现视觉分层。Layer 系统支持 32 层(0-31),通过位运算实现精确筛选。
核心原理:
- 位掩码:每位代表一个 Layer(bit 0 = Layer 0,bit 1 = Layer 1…)
- 性能提升:减少渲染对象数,降低 Draw Call(可达 50%+ 优化)
- 视觉控制:多相机分层渲染(如 UI/游戏/特效分离)
- XR 适配:双目相机独立 cullingMask,避免重复渲染
适用场景:开放世界、多相机系统、VR/AR、移动优化、LOD 系统。基于 Unity 2022.3 LTS+(兼容 Unity 6),URP/HDRP 中 cullingMask 同样有效,但需注意 Render Feature 兼容。
Layer 系统与位掩码基础
1. Layer 位运算原理
// Layer 位掩码计算
// Layer 0: 1 << 0 = 1 (二进制: 00000001)
// Layer 1: 1 << 1 = 2 (二进制: 00000010)
// Layer 5: 1 << 5 = 32 (二进制: 00100000)
// 多层组合: Layer0 | Layer1 = 3 (二进制: 00000011)
public static class LayerMaskUtils
{
public static int GetLayerMask(int layerIndex)
{
return 1 << layerIndex; // 单个 Layer
}
public static int CombineLayers(params int[] layerIndices)
{
int mask = 0;
foreach (int layer in layerIndices)
{
mask |= (1 << layer); // 位或运算组合
}
return mask;
}
public static bool IsLayerInMask(int layer, int mask)
{
return (mask & (1 << layer)) != 0; // 位与运算检查
}
}
2. Inspector 中的 cullingMask 设置
- 默认:-1(全选,32 位全 1)
- 手动选择:Inspector > Culling Mask > 勾选 Layer(如 UI、Player、Enemies)
- 代码设置:
public class CameraLayerSetup : MonoBehaviour
{
private Camera mainCamera;
void Start()
{
mainCamera = GetComponent<Camera>();
// 方法1:单个 Layer
mainCamera.cullingMask = 1 << LayerMask.NameToLayer("Default"); // Layer 0
// 方法2:多 Layer 组合
int gameLayers = LayerMaskUtils.CombineLayers(0, 5, 8); // Default + Enemies + Environment
mainCamera.cullingMask = gameLayers;
// 方法3:排除特定 Layer(~ 按位取反)
mainCamera.cullingMask = ~LayerMask.GetMask("UI"); // 渲染除 UI 外的所有层
}
}
精准 Layer 控制实战
3. 多相机分层渲染系统
场景:主游戏相机、UI 相机、特效相机分离渲染,提升性能和视觉控制。
public class MultiCameraLayerSystem : MonoBehaviour
{
[Header("Layer Assignments")]
public int playerLayer = 6;
public int enemyLayer = 7;
public int environmentLayer = 8;
public int uiLayer = 5;
public int effectsLayer = 9;
[Header("Cameras")]
public Camera gameCamera;
public Camera uiCamera;
public Camera effectsCamera;
void Start()
{
SetupCameraLayers();
}
void SetupCameraLayers()
{
// 主游戏相机:渲染游戏世界(玩家、敌人、环境)
int gameMask = LayerMaskUtils.CombineLayers(playerLayer, enemyLayer, environmentLayer);
gameCamera.cullingMask = gameMask;
gameCamera.depth = 0; // 渲染顺序最低
// UI 相机:仅渲染 UI 层
uiCamera.cullingMask = 1 << uiLayer;
uiCamera.clearFlags = CameraClearFlags.Depth; // 深度清除,不清除颜色
uiCamera.depth = 1; // 渲染在游戏之上
// 特效相机:仅渲染粒子/特效
effectsCamera.cullingMask = 1 << effectsLayer;
effectsCamera.clearFlags = CameraClearFlags.Depth;
effectsCamera.depth = 0.5f; // 游戏与 UI 之间
}
}
4. 动态 cullingMask 切换
场景:潜行模式切换(隐藏敌人)、不同区域渲染控制。
public class DynamicCullingController : MonoBehaviour
{
private Camera playerCamera;
private int baseMask;
void Start()
{
playerCamera = Camera.main;
baseMask = playerCamera.cullingMask; // 保存基础掩码
}
// 潜行模式:隐藏敌人
public void EnterStealthMode()
{
int stealthMask = baseMask & ~(1 << LayerMask.NameToLayer("Enemies")); // 排除敌人层
playerCamera.cullingMask = stealthMask;
}
// 正常模式:恢复所有层
public void ExitStealthMode()
{
playerCamera.cullingMask = baseMask;
}
// 区域切换:仅渲染当前区域
public void SwitchToArea(int areaLayer)
{
int areaMask = (1 << areaLayer) | (1 << LayerMask.NameToLayer("Player")) | (1 << LayerMask.NameToLayer("UI"));
playerCamera.cullingMask = areaMask;
}
}
XR 多视图优化
5. VR/AR 双目渲染优化
问题:XR 中双目相机重复渲染相同对象,导致性能浪费。
using UnityEngine.XR;
public class XRCullingOptimizer : MonoBehaviour
{
private Camera leftEyeCamera;
private Camera rightEyeCamera;
void Start()
{
// XR 双目相机自动创建,获取引用
Camera[] xrCameras = FindObjectsOfType<Camera>();
leftEyeCamera = xrCameras.FirstOrDefault(c => c.stereoTargetEye == StereoTargetEyeMask.Left);
rightEyeCamera = xrCameras.FirstOrDefault(c => c.stereoTargetEye == StereoTargetEyeMask.Right);
OptimizeXRCameras();
}
void OptimizeXRCameras()
{
int sharedLayers = LayerMaskUtils.CombineLayers(
LayerMask.NameToLayer("Environment"),
LayerMask.NameToLayer("StaticObjects")
);
// 共享层:环境/静态物体(不变),单目渲染后复用
leftEyeCamera.cullingMask = sharedLayers | (1 << LayerMask.NameToLayer("UI"));
rightEyeCamera.cullingMask = sharedLayers;
// UI 仅左眼渲染(性能优化)
// 动态物体(如玩家)两个相机都渲染
}
}
6. XR 单次渲染(Single Pass Instanced)适配
public class XRSinglePassCulling : MonoBehaviour
{
void Start()
{
if (XRSettings.enabled && XRSettings.singlePassInstancingEnabled)
{
// 单次实例化渲染:一个 Draw Call 渲染双目
// cullingMask 对两个视图同时生效
Camera.main.cullingMask = OptimizeForSinglePass();
}
}
private int OptimizeForSinglePass()
{
// 排除高频变化层(如粒子),降低 Instancing 开销
int optimizedMask = ~LayerMask.GetMask("Particles", "DynamicEffects");
return optimizedMask;
}
}
性能提升与优化技巧
7. Layer-based 渲染批处理
原理:相同材质/Shader 的对象集中同一 Layer,提升批处理效率。
public class LayerBatchOptimizer : MonoBehaviour
{
[Header("材质到 Layer 映射")]
public Material[] staticMaterials;
public Material[] dynamicMaterials;
void Start()
{
AssignLayersByMaterial();
}
void AssignLayersByMaterial()
{
// 静态材质 → 低 Layer(0-7),利于批处理
for (int i = 0; i < staticMaterials.Length && i < 8; i++)
{
int layer = i;
AssignMaterialToLayer(staticMaterials[i], layer);
}
// 动态材质 → 高 Layer(24-31),独立渲染
for (int i = 0; i < dynamicMaterials.Length && i < 8; i++)
{
int layer = 24 + i;
AssignMaterialToLayer(dynamicMaterials[i], layer);
}
}
void AssignMaterialToLayer(Material mat, int layer)
{
// 查找使用该材质的对象,设置 Layer
Renderer[] renderers = FindObjectsOfType<Renderer>();
foreach (var renderer in renderers)
{
if (renderer.sharedMaterial == mat)
{
renderer.gameObject.layer = layer;
}
}
}
}
8. LOD 与 cullingMask 集成
public class LODCullingSystem : MonoBehaviour
{
public Camera mainCamera;
public float lodDistance = 50f;
void Update()
{
UpdateLODLayers();
}
void UpdateLODLayers()
{
Renderer[] renderers = FindObjectsOfType<Renderer>();
foreach (var renderer in renderers)
{
float distance = Vector3.Distance(mainCamera.transform.position, renderer.transform.position);
if (distance > lodDistance)
{
// 远距离:移到低细节 Layer
renderer.gameObject.layer = LayerMask.NameToLayer("LowLOD");
}
else
{
// 近距离:高细节 Layer
renderer.gameObject.layer = LayerMask.NameToLayer("HighLOD");
}
}
// 动态更新相机 cullingMask
UpdateCameraForLOD();
}
void UpdateCameraForLOD()
{
int lodMask = LayerMask.GetMask("HighLOD", "LowLOD");
mainCamera.cullingMask = lodMask;
}
}
高级应用:渲染管线适配
9. URP/HDRP 中的 cullingMask
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class URPCullingAdapter : MonoBehaviour
{
private UniversalRenderPipelineAsset urpAsset;
void Start()
{
urpAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
SetupURPRenderLayers();
}
void SetupURPRenderLayers()
{
// URP Render Layers(实验性功能)
// 需要自定义 Render Feature 支持 Layer 筛选
Camera mainCamera = Camera.main;
// 基础 cullingMask 仍然有效
mainCamera.cullingMask = LayerMask.GetMask("Default", "Transparent");
// URP 特定优化
urpAsset.opaqueSortMode = OpaqueSortMode.FrontToBack; // 按深度排序
}
}
10. 自定义 Render Feature 中的 Layer 控制
// Custom Render Pass 示例
public class LayeredRenderPass : ScriptableRenderPass
{
private FilteringSettings filteringSettings;
private LayerMask renderLayers;
public LayeredRenderPass(LayerMask layers)
{
renderLayers = layers;
filteringSettings = new FilteringSettings(RenderQueueRange.opaque, layers);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 仅渲染指定 Layer 的对象
DrawingSettings drawingSettings = CreateDrawingSettings(shaderTagIdList, ref renderingData, SortingCriteria.CommonOpaque);
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
}
}
调试与性能监控
11. cullingMask 调试工具
#if UNITY_EDITOR
using UnityEditor;
public class CullingMaskDebugger : EditorWindow
{
private Camera targetCamera;
private int[] layerStats = new int[32];
[MenuItem("Tools/Debug Culling Mask")]
static void OpenWindow() => GetWindow<CullingMaskDebugger>();
void OnGUI()
{
targetCamera = (Camera)EditorGUILayout.ObjectField("Camera", targetCamera, typeof(Camera), true);
if (GUILayout.Button("Analyze Rendered Layers"))
{
AnalyzeLayers();
}
// 显示各 Layer 渲染统计
for (int i = 0; i < 32; i++)
{
EditorGUILayout.LabelField($"Layer {i}: {layerStats[i]} objects");
}
}
void AnalyzeLayers()
{
if (targetCamera == null) return;
Renderer[] renderers = FindObjectsOfType<Renderer>();
foreach (var renderer in renderers)
{
int layer = renderer.gameObject.layer;
if ((targetCamera.cullingMask & (1 << layer)) != 0)
{
layerStats[layer]++;
}
}
}
}
#endif
12. 性能监控
public class RenderingStats : MonoBehaviour
{
private Camera monitoredCamera;
void Start()
{
monitoredCamera = Camera.main;
StartCoroutine(MonitorPerformance());
}
IEnumerator MonitorPerformance()
{
while (true)
{
int renderedObjects = CountRenderedObjects();
Debug.Log($"Camera rendering {renderedObjects} objects");
yield return new WaitForSeconds(1f);
}
}
int CountRenderedObjects()
{
int count = 0;
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
foreach (var renderer in allRenderers)
{
if ((monitoredCamera.cullingMask & (1 << renderer.gameObject.layer)) != 0)
{
count++;
}
}
return count;
}
}
最佳实践总结
13. Layer 分配策略
Layer 范围 | 用途 | cullingMask 策略 |
---|---|---|
0-7 | 静态环境(Terrain, Buildings) | 全相机渲染,GPU Instancing |
8-15 | 动态物体(Player, NPCs) | 主要相机 + 阴影相机 |
16-23 | 特效/粒子 | 特效相机,降低频率 |
24-31 | UI/Debug | UI 相机,独立渲染 |
14. 性能优化要点
- 减少 Layer 切换:避免帧间频繁修改 cullingMask
- 批处理优先:相同材质对象同 Layer
- LOD 集成:远距离降级到低细节 Layer
- XR 优化:单次渲染 + 共享静态层
15. 常见问题解决
问题 | 原因 | 解决方案 |
---|---|---|
物体不可见 | Layer 未包含在 cullingMask | 检查位掩码,1 << layerIndex |
性能无提升 | 材质碎片化 | 按材质分配 Layer,提升批处理 |
UI 穿透 | UI Layer 与游戏重叠 | UI 相机独立 cullingMask + 深度排序 |
XR 重复渲染 | 双目相机相同掩码 | 共享静态层,UI 单眼渲染 |
通过系统化的 cullingMask 管理,可实现渲染性能和视觉控制的双赢。如果需要特定场景的 Layer 配置或自定义 Render Feature 示例,请提供更多细节!