Unity中检测3D物体是否超出相机范围

526 阅读1分钟

一:使用Unity自带的回调函数

当物体身上的Render组件是否被渲染时调用OnBecameVisible和OnBecameInVisible,类似于OnTriggerEnter和OnTriggerExit

  • 物体自身上必须有相关Render组件
  • 它所谓的渲染与不渲染包括Game视图和Scene视图两个,在电脑上测试会出现Game视图中已经不显示物体但是Scene视图中物体还被显示出来的问题,移动端不用考虑这个问题
  • 下面的脚本挂载到被检测的物体身上
using UnityEngine;

public class Test : MonoBehaviour
{
    private bool isInView; //是否在视线范围内
    
    private void Update()
    {
        if (isInView)
        {
            print("yes");
        }
        else
        {
            print("no");
        }
    }

    private void OnBecameVisible()
    {
        isInView = true;
    }

    private void OnBecameInvisible()
    {
        isInView = false;
    }
}

但是测试后发现,这种自带的API有两种问题(还有一个叫OnWillRenderObject的API与这种方法类似)
——游戏运行一开始会执行no两次,这是因为物体被渲染出来可能需要几帧的时间,而在这个几帧的间隔里就会执OnBecameInvisible方法,解决方法是可以在游戏运行后1秒再去判定Update中的isInView,不过这样显得很奇怪
——所有的相机只要有一个渲染了此物体都会执行OnBecameVisible,无法指定相机
推荐以下的方法


二:世界坐标转视口坐标

using UnityEngine;

public class Test : MonoBehaviour
{
    private void Update()
    {
        if (IsInView(transform.position))
        {
            print("yes");
        }
        else
        {
            print("no");
        }
    }

    /// <summary>
    /// 是否在指定相机的视野范围内
    /// </summary>
    /// <param name="worldPos">物体世界坐标</param>
    /// <returns>是否在相机视野范围内</returns>
    public bool IsInView(Vector3 worldPos)
    {
        Transform camTransform = Camera.main.transform;
        Vector2 viewPos = Camera.main.WorldToViewportPoint(worldPos);

        //判断物体是否在相机前面  
        Vector3 dir = (worldPos - camTransform.position).normalized;
        float dot = Vector3.Dot(camTransform.forward, dir);

        if (dot > 0 && viewPos.x >= 0 && viewPos.x <= 1 && viewPos.y >= 0 && viewPos.y <= 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}