【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-31UI/DebugUI 相机,独立渲染

14. 性能优化要点

  • 减少 Layer 切换:避免帧间频繁修改 cullingMask
  • 批处理优先:相同材质对象同 Layer
  • LOD 集成:远距离降级到低细节 Layer
  • XR 优化:单次渲染 + 共享静态层

15. 常见问题解决

问题原因解决方案
物体不可见Layer 未包含在 cullingMask检查位掩码,1 << layerIndex
性能无提升材质碎片化按材质分配 Layer,提升批处理
UI 穿透UI Layer 与游戏重叠UI 相机独立 cullingMask + 深度排序
XR 重复渲染双目相机相同掩码共享静态层,UI 单眼渲染

通过系统化的 cullingMask 管理,可实现渲染性能和视觉控制的双赢。如果需要特定场景的 Layer 配置或自定义 Render Feature 示例,请提供更多细节!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注