Unity优化技巧:自动隐藏视野外的3D模型

发布于:2025-08-04 ⋅ 阅读:(11) ⋅ 点赞:(0)

在3D游戏开发中,场景中可能包含大量模型,而许多模型可能同时处于玩家视野之外。这些不可见的模型仍然消耗CPU和GPU资源,导致性能下降。本文将介绍如何实现一个自动隐藏视野外模型的系统,通过两个Unity脚本实现性能优化。

系统概述

这个系统由两个核心脚本组成:

  1. ModelVisibilityManager1:管理器脚本,负责跟踪所有需要自动隐藏的模型
  2. AutoHideWhenOutOfView1:组件脚本,附加到需要自动隐藏的模型上

系统工作流程:

  • 管理器每帧检查所有注册模型的可见性
  • 当模型离开相机视野时自动隐藏
  • 当模型重新进入视野时自动显示
  • 避免不必要的渲染和计算开销

脚本1:ModelVisibilityManager1(管理器)

using System.Collections.Generic;
using UnityEngine;

public class ModelVisibilityManager1 : MonoBehaviour
{
    public static ModelVisibilityManager1 Instance { get; private set; }
    private List<AutoHideWhenOutOfView1> registeredControllers = new List<AutoHideWhenOutOfView1>();
    public Camera targetCamera;

    void Awake()
    {
        // 单例模式实现
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

        // 设置默认相机
        if (targetCamera == null)
        {
            targetCamera = Camera.main;
        }
    }

    // 注册模型控制器
    public void Register(AutoHideWhenOutOfView1 controller)
    {
        if (!registeredControllers.Contains(controller))
        {
            registeredControllers.Add(controller);
        }
    }

    // 注销模型控制器
    public void Unregister(AutoHideWhenOutOfView1 controller)
    {
        if (registeredControllers.Contains(controller))
        {
            registeredControllers.Remove(controller);
        }
    }

    void Update()
    {
        // 每帧检查所有注册模型的可见性
        foreach (var controller in registeredControllers)
        {
            controller.ManualVisibilityCheck(targetCamera);
        }
    }
}

单例模式实现

public static ModelVisibilityManager1 Instance { get; private set; }

使用静态属性确保全局只有一个实例,其他脚本可以通过ModelVisibilityManager1.Instance访问它。

初始化设置

void Awake()
{
    if (Instance == null)
    {
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);
    }

    if (targetCamera == null)
    {
        targetCamera = Camera.main;
    }
}
  • 如果当前实例为空,则设置当前实例为单例,并标记为在场景切换时不销毁
  • 如果已存在实例,则销毁当前对象,保证单例的唯一性
  • 如果targetCamera未被赋值,则使用场景中的主相机作为目标相机

注册管理

private List<AutoHideWhenOutOfView1> registeredControllers = new List<AutoHideWhenOutOfView1>();

public void Register(AutoHideWhenOutOfView1 controller)
{
    if (!registeredControllers.Contains(controller))
    {
        registeredControllers.Add(controller);
    }
}

public void Unregister(AutoHideWhenOutOfView1 controller)
{
    if (registeredControllers.Contains(controller))
    {
        registeredControllers.Remove(controller);
    }
}
  • 维护一个列表存储所有需要自动隐藏的模型组件
  • 提供RegisterUnregister方法管理组件列表

可见性检查

public void CheckVisibility(Camera camera)
{
    foreach (var controller in registeredControllers)
    {
        controller.CheckVisibility(camera);
    }
}
  • 遍历所有已注册的控制器
  • 调用每个控制器的CheckVisibility方法进行可见性检测

    关键功能解析:

  • 单例模式

    • 确保场景中只有一个管理器实例
    • 使用DontDestroyOnLoad保持管理器在场景切换时不被销毁
  • 相机设置

    • 默认使用场景主相机
    • 可通过Inspector自定义目标相机
  • 模型注册系统

    • Register方法添加需要自动隐藏的模型
    • Unregister方法移除已销毁的模型
    • 避免重复注册
  • 每帧可见性检查

    • 在Update中遍历所有注册的模型
    • 调用每个模型的可见性检查方法

脚本2:AutoHideWhenOutOfView1(模型组件)

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class AutoHideWhenOutOfView1 : MonoBehaviour
{
    private Renderer modelRenderer;
    private bool isVisible = true;

    void Awake()
    {
        // 获取渲染器组件
        modelRenderer = GetComponent<Renderer>();
        if (modelRenderer == null)
        {
            enabled = false; // 无渲染器则禁用脚本
        }

        // 向管理器注册自身
        ModelVisibilityManager1.Instance.Register(this);
    }

    void OnEnable()
    {
        isVisible = true;
    }

    // 由管理器调用的可见性检查方法
    public void ManualVisibilityCheck(Camera camera)
    {
        bool isInView = IsInCameraView(camera);

        // 从不可见到可见:激活模型
        if (!isVisible && isInView)
        {
            gameObject.SetActive(true);
            isVisible = true;
        }
        // 从可见到不可见:隐藏模型
        else if (isVisible && !isInView)
        {
            gameObject.SetActive(false);
            isVisible = false;
        }
    }

    // 检查模型是否在相机视野内
    bool IsInCameraView(Camera camera)
    {
        Bounds bounds = modelRenderer.bounds;
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
        return GeometryUtility.TestPlanesAABB(planes, bounds);
    }

    void OnDestroy()
    {
        // 从管理器中注销
        ModelVisibilityManager1.Instance.Unregister(this);
    }
}

组件初始化

private bool isVisible = true;
private Renderer modelRenderer;

void Awake()
{
    modelRenderer = GetComponent<Renderer>();
}
  • isVisible:记录当前模型是否处于激活状态
  • 获取模型上的渲染器组件用于后续检测

可见性检测逻辑

public void CheckVisibility(Camera camera)
{
    bool isInView = IsInCameraView(camera);

    if (!isVisible && isInView)
    {
        gameObject.SetActive(true);
        isVisible = true;
        //Debug.Log(name + " 已重新进入视野,已激活");
    }
    else if (isVisible && !isInView)
    {
        gameObject.SetActive(false);
        isVisible = false;
        //Debug.Log(name + " 离开视野,已隐藏");
    }
}
  • 调用IsInCameraView检测模型是否在相机视野内
  • 当模型从不可见变为可见时激活模型
  • 当模型从可见变为不可见时停用模型

视锥体检测算法

private bool IsInCameraView(Camera camera)
{
    Bounds bounds = modelRenderer.bounds;
    Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
    return GeometryUtility.TestPlanesAABB(planes, bounds);
}
  • 获取模型的包围盒(AABB)
  • 计算相机的视锥体平面
  • 使用Unity内置方法检测包围盒是否与视锥体相交

清理注册

void OnDestroy()
{
    ModelVisibilityManager1.Instance.Unregister(this);
}
  • 当对象被销毁时,从管理器的注册列表中移除自身

关键功能解析:

  1. 渲染器依赖

    • 使用[RequireComponent]确保模型有渲染器
    • 无渲染器时自动禁用脚本
  2. 可见性状态管理

    • isVisible变量跟踪当前可见状态
    • 避免重复激活/停用模型
  3. 视野检测算法

    • 使用GeometryUtility类计算相机视锥体
    • 通过包围盒(AABB)与视锥体平面相交测试判断可见性
    • 高效且精确的3D空间检测方法
  4. 生命周期管理

    • Awake中注册到管理器
    • OnDestroy中从管理器注销
    • 确保没有内存泄漏

网站公告

今日签到

点亮在社区的每一天
去签到