Unity矩阵matrix4x4的应用:世界空间与模型空间的坐标系转化


前言

近期由于项目需求,研究了一下通过矩阵来转换不同坐标系,接下来就简单记录一下。


一、矩阵 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;
    }

在这里插入图片描述

总结

坐标系之间的转化需要一定的空间想象能力,这方面不是很强的同学可以试试用三支笔模拟一个坐标系,方便理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值