一、当物体有渲染(Renderer)组件时:
这种情况比较简单,监听两个Unity事件即可:
#region 可见性判断
public bool IsVisableInCamera { get; private set; }
private void OnBecameVisible() { IsVisableInCamera = true; }
private void OnBecameInvisible() { IsVisableInCamera = false; }
#endregion
当然,这种判断是有条件的:
1、必须是个MonoBehaviour;
2、必须挂载渲染组件;
两种条件不满足的话,这两个Unity消息函数是不会调用的。
二、直接用坐标判断:
这种情况就需要借用相机来判断,上代码:
#region 可见性判断
public bool IsVisableInCamera
{
get
{
Camera mCamera = Camera.main;
Vector3 pos = transform.position;
//转化为视角坐标
Vector3 viewPos = mCamera.WorldToViewportPoint(pos);
// z<0代表在相机背后
if (viewPos.z < 0) return false;
//太远了!看不到了!
if (viewPos.z > mCamera.farClipPlane)
return false;
// x,y取值在 0~1之外时代表在视角范围外;
if (viewPos.x < 0 || viewPos.y < 0 || viewPos.x > 1 || viewPos.y > 1) return false;
return true;
}
}
#endregion
这里的调用的API:WorldToViewportPoint ,用来转化成视角的坐标,返回一个Vector3。
这里可以参考Unity的API:Unity - Scripting API: Camera.WorldToViewportPoint
其中Z值代表在屏幕前或者后,为正代表在屏幕前方,为负代表在屏幕后方。
X、Y值代表在视角内的坐标,在屏幕内的话取值为0~1 。
这下判定就简单了,如代码所示即可。
注意:
正式代码不要直接调用 Camera.main ,性能不好。
三、使用相机视野包围盒判定
上面调用相机的API是有缺陷的:他只能判定一个点的情况。然而对于大部分3D模型,都不会只判定一个点,而是需要判定包围盒。
所以还可以使用相机视野包围盒来判定,这样判定的结果就是非常精确的了。判定方式可以直接调用Unity的API即可,有现成的:
private Plane[] mTempCameraPlanes = new Plane[6];//相机包围盒
public Plane[] CalculateFrustumPlanes() { return mTempCameraPlanes; }
//通过相机包围盒来判定物体是否在视野中。
public bool Check_WoldPointIsVisableInMainCamera_ByBonds(Vector3 center, Vector3 size)
{
var planes = CalculateFrustumPlanes();
Bounds bound = new Bounds(center, size);//这里的Size是半径
return GeometryUtility.TestPlanesAABB(planes, bound);
}
这个原理就非常简单了,相机的视野由六个平面组成:近裁剪面、远裁剪面,再加上上下左右4个平面。然后3D模型,简化成一个方形包围盒(中心点+半径),然后与相机的包围盒进行碰撞检测(Test AABB),据此来判定是否在相机视野内。
当然,相机的六个平面也是需要实时读取的,不过只需要在LateUpdate里缓存一次即可:
private void LateUpdate()
{
//调用Unity的API,获取相机包围盒
if (mainCamera)
GeometryUtility.CalculateFrustumPlanes(mainCamera, mTempCameraPlanes);
}
这个性能消耗显然是比判断单个点要高的,但是结果也更为精准。在性能压力不大的情况下,建议使用这个方式来判定,能够获得更好的显示效果。