在3D游戏开发中,场景中可能包含大量模型,而许多模型可能同时处于玩家视野之外。这些不可见的模型仍然消耗CPU和GPU资源,导致性能下降。本文将介绍如何实现一个自动隐藏视野外模型的系统,通过两个Unity脚本实现性能优化。
系统概述
这个系统由两个核心脚本组成:
- ModelVisibilityManager1:管理器脚本,负责跟踪所有需要自动隐藏的模型
- 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);
}
}
- 维护一个列表存储所有需要自动隐藏的模型组件
- 提供
Register
和Unregister
方法管理组件列表
可见性检查
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);
}
- 当对象被销毁时,从管理器的注册列表中移除自身
关键功能解析:
渲染器依赖:
- 使用
[RequireComponent]
确保模型有渲染器 - 无渲染器时自动禁用脚本
- 使用
可见性状态管理:
isVisible
变量跟踪当前可见状态- 避免重复激活/停用模型
视野检测算法:
- 使用
GeometryUtility
类计算相机视锥体 - 通过包围盒(AABB)与视锥体平面相交测试判断可见性
- 高效且精确的3D空间检测方法
- 使用
生命周期管理:
Awake
中注册到管理器OnDestroy
中从管理器注销- 确保没有内存泄漏