草庐IT

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

魔术师Dix 2024-02-27 原文

        在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】俯视角相机地面视野范围的计算的更多相关文章

  1. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  2. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  3. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  4. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  5. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  6. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  7. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  8. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  9. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  10. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

随机推荐