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

标签: Unity  unity  旋转矩阵  坐标系转化


前言

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


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

在这里插入图片描述

总结

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

版权声明:本文为yizhe0731原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yizhe0731/article/details/108410116

智能推荐

opencv画坐标系(便于与世界坐标转化)、检测色块(目标物体)

      ① 根据图像尺寸遍历出所有节点; ② 根据行列个数得出边界节点; ③ 画点、两点连线; ④ 将视野中心点特殊标记; ⑤ 实现当色块位于网格节点或者视野中心点时,显示坐标值在图像上; ⑥ 可调整网格间距,10、20或者40等可以被尺寸值整除的数            ...

Java运行原理

1.Java运行原理       我们可通过文本编辑板生成Java源代码(.java)经过dos窗口由Java编译器(javac.exe)生成字节码文件(.class),字节码可由Java虚拟机转化为机器码供计算机读取处理。由于Java可以生成字节码可供虚拟机转译所以可跨平台运行。运行过程如下: 所以相对于C语言还需要转化为exe文件才能运行的权限,Java具有跨平台...

Python由放弃到入门,基础篇七(类)下

类的实例化 有感于现在python教程多如牛毛,且大多高不可攀,多次拜读而不得其门道,遂由入门到放弃。偶有机缘,得一不错教程,得以入门,现博客分享,想要获取完整教程,ff17328081445。 通过对比可以看到,实例化后再使用的格式,①是空着的,意思是这里不再需要@classmethod的声明,并且在第②处,把cls替换成了self。同时,实例化后再使用的格式,需要先赋值然后再调用(第③处): ...

pytorch CNN手写字体识别

数据整体训练一次,对于accuracy都是0的问题,由于刚开始学,有些代码的细节我也没看懂,不过整体结果是对的,可能是由于pytorch版本的更新,导致accuracy的计算方式有所改变 内容转载自:https://www.bilibili.com/video/av15997678/?p=19...

linux后台运行命令总结

linux后台运行命令总结 问题: 我们有时候需要登录远程服务器跑运行时间非常长的脚本,这个时候你要让脚本后台运行,不然占着终端窗口看着不舒服。但万一网络不好,(比如我这儿的破校园网,高峰时几秒钟断一次),终端突然和服务器之间的连接断了,那脚本就会自动停了(因为运行test.sh进程的父进程就是当前的shell终端进程,关闭当前shell终端时,父进程退出,会发送hangup信号给所有子进程,子进...

猜你喜欢

类对象模型和this指针

关于类/对象大小的计算 类只是一种类型定义,它本身是没有大小可言的。 我们这里指的类的大小,其实指的是类的对象所占的大小。因此,如果用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。 首先,类大小的计算遵循结构体的对齐原则 类的大小与普通数据成员有关,与成员函数和静态成员无关。即普通成员函数,静态成员函数,静态数据成员,静态常量数据成员均对类的大小无影响 虚函数对类的大小有影响,...

3D人脸重建——PRNet网络输出的理解

前言 之前有款换脸软件不是叫ZAO么,分析了一下,它的实现原理绝对是3D人脸重建,而非deepfake方法,找了一篇3D重建的论文和源码看看。这里对源码中的部分函数做了自己的理解和改写。 国际惯例,参考博客: 什么是uv贴图? PRNet论文 PRNet代码 本博客主要是对PRNet的输出进行理解。 理论简介 这篇博客比较系统的介绍了3D人脸重建的方法,就我个人浅显的理解,分为两个流派:1.通过算...

javascript简单的正则表达式入门

内容来自百度前端学院javascript入门课程 基本的HTML: 样式: javascript: document.write和innerHTML有什么区别 前者是直接将内容写入文档流,如果写入之前没有调用document.open,那么回自动调用document.open(每打开一次文档流都会清除之前的所有内容包括变量)。每次写完关闭后重新调用该函数的话,会导致页面重写。 innerHTML是...

微信小程序一个你可能需要的功能

根据工作需要。需要做一个图片选中部分区域的效果。百度了很久,都没有见有。于是就自己写了个,需要的可以借鉴下,还有很多需要改善的地方 现在先看看效果 效果图 那这个有什么用呢。。需求是选中图片的某个区域然后给它添加注释。还可以有其他用处。那这个是怎么做到的呢 。。首先我说下基本的思路 ——-> 图片作为一个背景。然后上面是一层canvas 以及最上面生成的view 因为...