Unity俯视角相机地面视野范围的计算

Posted 魔术师Dix

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity俯视角相机地面视野范围的计算相关的知识,希望对你有一定的参考价值。

        在SLG等游戏中,相机总是固定为俯视角(上帝视角)。为了更好地管理游戏数据,需要对地图进行分块,只处理视野内的部分。判定某个单位是否在视野内有很多方法了,但是要么不够精确,要么性能不够,要么无法与AOI配合。

        一个可行的方案就是将相机在地面上的视野计算出一个AABB 2D 包围盒,然后基于此包围盒来计算 AOI、显隐等。这个方案效率够高,而且对俯视角适配较好。

        下面讲讲原理和具体实现。

1、俯视角的相机视野

        相机在俯视角下,一般在地面的视野是个梯形,如下图所示:        

        绿色的部分就是相机的视野范围,红框部分为其外接的 AABB 2D 包围盒。然后我们把这个红框部分视为相机的视野,虽然有一定的冗余,但是在计算效率、地图格子筛选上有很大优势。

        这里的计算原理就是,将 Unity 相机视野的四条射线打到地面上,会得到 4 个点。再通过这 4 个点就能获取到外接矩形。(当然,实现上不会使用射线,效率较低)。

2、相机远裁剪面的影响

        还有一种情况,一般出现在相机较高的时候,相机只能看到部分地面(相机的远裁剪面不是全部在地面以下):

         如图所示,从侧面看相机,O为相机位置,AB为其远裁剪面,图中黑线为地面。相机视野边界的射线OB与地面相较于D没什么问题,但是视野OA与地面不相交,在A点已经是极限距离了。在这种情况下,取A点与地面的投影C点,视为相机的远裁剪极限。

        在这种处理方式下,视野范围则为红色线段 CD 的表示范围。

3、示例代码

        在清楚原理之后,直接看代码。

        3.1、计算相机视野        

        private static CameraGroundCrossPoint getCameraGroundCrossPoint()
        
            var fov = FieldOfView;//相机的Fov
            var camera = MainCamera;
            var asp = camera.aspect;

            var yf = Mathf.Tan(fov / 2 * Mathf.Deg2Rad);
            var xf = yf * asp;
            //获取相机视野的四条射线;
            Matrix4x4 l2w = MainCamera.transform.localToWorldMatrix;
            Vector3 f0 = l2w * new Vector3(-xf, -yf, 1);
            Vector3 f1 = l2w * new Vector3(-xf, yf, 1);
            Vector3 f2 = l2w * new Vector3(xf, -yf, 1);
            Vector3 f3 = l2w * new Vector3(xf, yf, 1);

            CameraGroundCrossPoint crossPoint = new CameraGroundCrossPoint();
            //获取视野与地面的交点,或是远裁剪面垂直投射到地面的交点;
            crossPoint.LeftBottom = CheckGroundSignPoint(f0);
            crossPoint.LeftTop = CheckGroundSignPoint(f1);
            crossPoint.RightBottom = CheckGroundSignPoint(f2);
            crossPoint.RightTop = CheckGroundSignPoint(f3);

            return crossPoint;
        

        private static Vector3 CheckGroundSignPoint(Vector3 dri)
        
            Vector3 cpt = Position;
            Vector3 farPlaneNormal = Forward;
            Vector3 farPlanePoint = Position + (farPlaneNormal * FarClipPlane);

            float height = GroundHeight;

            //计算与远裁剪面的交点;
            var signPoint = GetIntersectWithLineAndPlane(cpt, dri, farPlaneNormal, farPlanePoint);

            //这里相机先到达了远裁剪面,而没有与地面相交;
            if (signPoint.y > height)
            
                //将远裁剪面的位置投影到地面上返回
                signPoint.y = height;
                return signPoint;
            
            //此时被地面截断;
            Vector3 groundPoint = new Vector3(0, GroundHeight, 0);
            signPoint = GetIntersectWithLineAndPlane(cpt, dri, Vector3.up, groundPoint);

            return signPoint;
        

                GetIntersectWithLineAndPlane 是计算射线与平面交点的API:

        /// <summary>
        /// 计算直线与平面的交点
        /// </summary>
        /// <param name="point">直线上某一点</param>
        /// <param name="direct">直线的方向</param>
        /// <param name="planeNormal">垂直于平面的的向量</param>
        /// <param name="planePoint">平面上的任意一点</param>
        /// <returns></returns>
        public static Vector3 GetIntersectWithLineAndPlane(Vector3 point, Vector3 direct, Vector3 planeNormal, Vector3 planePoint)
        
            float d = Vector3.Dot(planePoint - point, planeNormal) / Vector3.Dot(direct.normalized, planeNormal);
            //直线与平面的交点
            Vector3 hitPoint = (d * direct.normalized) + point;
            return hitPoint;
        

        3.2 、结构体 CameraGroundCrossPoint 

        上面的代码用到了结构体 CameraGroundCrossPoint ,其源代码如下:        

    /// <summary>
    /// 相机地面交点
    /// Y 轴为地面高度
    /// </summary>
    public struct CameraGroundCrossPoint
    
        public Vector3 LeftBottom;
        public Vector3 LeftTop;
        public Vector3 RightBottom;
        public Vector3 RightTop;

        /// <summary>
        /// 最小位置
        /// </summary>
        public Vector3 minPosition
        
            get
            
                float minX = Mathf.Min(Mathf.Min(LeftTop.x, RightTop.x), Mathf.Min(LeftBottom.x, RightBottom.x));
                float minZ = Mathf.Min(Mathf.Min(LeftTop.z, RightTop.z), Mathf.Min(LeftBottom.z, RightBottom.z));
                Vector3 mainCamPos = CameraUtils.Position;
                return new Vector3(Mathf.Min(mainCamPos.x, minX), CameraUtils.GroundHeight, Mathf.Min(mainCamPos.z, minZ));
            
        

        /// <summary>
        /// 最大位置
        /// </summary>
        public Vector3 maxPosition
        
            get
            
                float maxX = Mathf.Max(Mathf.Max(LeftTop.x, RightTop.x), Mathf.Max(LeftBottom.x, RightBottom.x));
                float maxZ = Mathf.Max(Mathf.Max(LeftTop.z, RightTop.z), Mathf.Max(LeftBottom.z, RightBottom.z));
                Vector3 mainCamPos = CameraUtils.Position;
                return new Vector3(Mathf.Max(maxX, mainCamPos.x), CameraUtils.GroundHeight, Mathf.Max(mainCamPos.z, maxZ));
            
        

    

        外部直接可以通过获取 minPosition 和 maxPosition 就可以构建 AABB 包围盒。

        注意:这个计算其实是基于俯视角游戏的特殊优化,所以如果不是俯视角,这个计算思路其实并不适用。

以上是关于Unity俯视角相机地面视野范围的计算的主要内容,如果未能解决你的问题,请参考以下文章

Unity 相机

Unity中实现垂直角度的相机在不同分辨率下的适配

小松教你手游开发unity实用技能计算目标物体是否在自己的扇形视野范围

Unity判断物体是否在视野内

Unity中正面视图的相机最大距离定位

关于Unity中渲染顺序问题