【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 包围盒。

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

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值