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俯视角相机地面视野范围的计算的主要内容,如果未能解决你的问题,请参考以下文章