文章目录
前言
近期由于项目需求,研究了一下通过矩阵来转换不同坐标系,接下来就简单记录一下。
一、矩阵 Matrix4x4?
UnityEngine.Matrix4x4 是 Unity 提供的方便矩阵计算而封装的一个结构体,提供了很多相对便于日常开发的函数。除了 官方API 之外,相信网上有很多关于这个矩阵的定义的解释了,这里就不介绍了,给个 友情链接 吧。
这里我们结合一个具体例子来看吧:我们想知道一只蚂蚁在一个很大的球上的可视范围(别和我纠结蚂蚁的视力问题啊!)。
二、应用过程
1、案例分析
首先,我们能确定的是,蚂蚁在球面上是相对于球面有一个偏转的。所以,蚂蚁自身是有一个 “模型空间坐标系”的,同时,不可避免的,蚂蚁也处于另外的一个“Unity世界空间坐标系”中。在Unity中,蚂蚁的位置是相对世界空间坐标系来说的。
我们想知道蚂蚁的可视范围,可以想到的方式有,以蚂蚁为圆心,向四周打一圈射线,判断碰撞,得出可视范围,可以此绘制出Mesh。
2、函数介绍
Matrix4x4.TRS
该函数可用于构建旋转矩阵。三个参数分别为位置、旋转与缩放。上述案例中,世界空间坐标系为标准三维空间坐标系,而模型空间坐标系可以用蚂蚁的位置与旋转来表示。
Matrix4x4.MultiplyVector
上述旋转矩阵中,该函数可用于转换世界空间坐标系中的向量至模型空间坐标系。
Matrix4x4.MultiplyPoint
上述旋转矩阵中,该函数可用于转换世界空间坐标系中的坐标点至模型空间坐标系。
Matrix4X4.inverse
矩阵的逆。可获取该矩阵的逆矩阵。结合上面两个函数,我们就可转换模型空间坐标系中的信息至世界空间。
Matrix4X4.rotation
用于获取该旋转矩阵中的旋转四元数。可以乘以另一个四元数以获取它在另一个坐标系下的表示。
public bool isPoint;
public bool inverse;
public Transform orgCoor; // 原始坐标系
public Transform newCoor; // 新坐标系
public Transform tranOrg; // 原始位置
public Transform tranNew; // 转换后的位置
/// <summary>
/// 转点
/// </summary>
private void MultiplyPoint()
{
Matrix4x4 matrix4X4 = Matrix4x4.TRS(newCoor.position, newCoor.rotation, newCoor.localScale);
if (!inverse)
tranNew.position = matrix4X4.MultiplyPoint(tranOrg.position);
else
tranNew.position = matrix4X4.inverse.MultiplyPoint(tranOrg.position); // 这里的 position 依然是相对于世界空间坐标系(orgCoor)来说的
//Vector4 a = matrix4X4.inverse * (tranOrg.position);
}
/// <summary>
/// 转向量
/// </summary>
private void MultiplyVector()
{
Matrix4x4 matrix4X4 = Matrix4x4.TRS(newCoor.position, newCoor.rotation, newCoor.localScale);
if (!inverse)
{
tranNew.position = matrix4X4.MultiplyVector(tranOrg.position - orgCoor.position) + newCoor.position;
tranNew.rotation = matrix4X4.rotation * tranOrg.rotation;
}
else
{
tranNew.position = matrix4X4.inverse.MultiplyVector(tranOrg.position - newCoor.position) + orgCoor.position;
tranNew.rotation = matrix4X4.inverse.rotation * tranOrg.rotation; // 四元数相乘 改朝向
}
}
3、实际代码
利用世界空间坐标系与Mesh的绘制坐标系相同,我们可以将理想的锥形在世界空间中表示出来,再转换到模型空间坐标系中(此时虽然是模型空间中,但还是用的世界空间坐标系中的坐标点表示位置)利用碰撞计算Mesh的顶点位置,最后再转换回世界空间坐标系中。此时,Mesh顶点的坐标表示是相对世界空间的,可以用于生成 Meshfilter.mesh 的赋值。
最后,附上相关代码
/// <summary>
/// 球面上的地面侦察范围Mesh创建(带碰撞)
/// </summary>
/// <param name="trans">球面上某点的Transform</param>
/// <param name="radius">侦察半径</param>
/// <param name="layer">层级</param>
/// <returns></returns>
public static Mesh CreateAntDetectionRange(Transform trans, float radius, int layer, int upHeight = 20)
{
// 此处涉及2个空间坐标系,分别为 unity世界空间坐标系、物体的模型空间坐标系
Matrix4x4 matrix4X4 = Matrix4x4.TRS(trans.position, trans.rotation, trans.localScale); // 构造从 unity世界空间坐标系 到 物体的模型空间坐标系 矩阵
Vector3 orgPoint = trans.position;
Vector3 upPoint = matrix4X4.MultiplyPoint(Vector3.up * upHeight); // 中间点的 unity世界坐标点
//List<Vector3> vertices = new List<Vector3> { Vector3.up * upHeight };
List<Vector3> vertices = new List<Vector3>();
for (int i = 0; i <= 360; i += 3)
{
Vector3 endPosSelf = Quaternion.AngleAxis(i, Vector3.up) * Vector3.forward * radius; // 模型空间中的位置信息
Vector3 endPos = matrix4X4.MultiplyPoint(endPosSelf); // 模型空间相对位置信息 转化为 世界空间位置信息
if (Physics.Raycast(upPoint, (endPos - upPoint).normalized, out RaycastHit hit, radius, layer))
{
endPos = hit.point;
Debug.DrawLine(upPoint, endPos, Color.yellow);
}
else
{
Debug.DrawLine(upPoint, endPos, Color.red);
}
// //Debug.DrawLine(Vector3.up * upHeight + orgPoint, Quaternion.AngleAxis(i, Vector3.up) * Vector3.forward * radius + orgPoint, Color.green);
//Vector3 direction = matrix4X4.MultiplyVector(Quaternion.AngleAxis(i, Vector3.up) * Vector3.forward * radius - Vector3.up * upHeight); // 自身射线方向 转到 世界空间射线方向
//Vector3 endPos = upPoint + direction;
//if (Physics.Raycast(upPoint, direction.normalized, out RaycastHit hit, radius, layer))
//{
// endPos = hit.point;
// Debug.DrawLine(upPoint, endPos, Color.yellow);
//}
//else
//{
// Debug.DrawLine(upPoint, endPos, Color.red);
//}
vertices.Add(matrix4X4.inverse.MultiplyPoint(endPos));
}
return CreatePolygonMesh(matrix4X4.inverse.MultiplyPoint(upPoint), vertices.ToArray());
}
/// <summary>
/// 简单多边形网格(单中心点连边)
/// </summary>
/// <param name="center">中点</param>
/// <param name="points">边</param>
/// <returns></returns>
public static Mesh CreatePolygonMesh(Vector3 center, Vector3[] points, bool coverEnd = true)
{
List<Vector3> vertices = new List<Vector3>();
vertices.Add(center);
vertices.AddRange(points);
List<int> triangles = new List<int>();
for (int i = 1; i < vertices.Count; i++)
{
if (i == vertices.Count - 1)
{
if (coverEnd)
{
triangles.Add(0);
triangles.Add(i);
triangles.Add(1);
}
}
else
{
triangles.Add(0);
triangles.Add(i);
triangles.Add(i + 1);
}
}
Mesh mesh = new Mesh()
{
vertices = vertices.ToArray(),
triangles = triangles.ToArray()
};
//mesh.RecalculateNormals();
return mesh;
}
总结
坐标系之间的转化需要一定的空间想象能力,这方面不是很强的同学可以试试用三支笔模拟一个坐标系,方便理解。