【Unity学习笔记】第十六 World space、Parent space和Self space及Quaternion左乘右乘辨析

目录

参考:

转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/138878578

作者:CSDN@|Ringleader|

World space、Parent space和Self space

  1. World space:相对于scene空间原点(0,0,0)的坐标空间。

    • scene场景中存在隐含的所有对象的根父级对象,这个根对象不会移动不会旋转,它的位置就是scene空间原点(0,0,0) (注意这里只是方便理解,并不存在这样的根对象)。这个原点的向右向上向前定义为xyz坐标轴,由这个原点和三个轴组成的坐标系就叫"世界坐标系"。

    • 在这个"世界坐标系"定义下,相对这个世界坐标原点的变化就称作"世界坐标变换"。Tranform中position和rotation就是世界坐标系下的偏移和旋转。

    • 我们会用Vector3和Quaternion表示位置偏移和旋转,但这两个向量都表示的一种变化,为何它又用来表示对象状态呢?其实就是默认用相对于0偏移0旋转的世界坐标原点的变化来表示对象的状态罢了。localPositon、localRotation也是同样的道理。

  2. Parent space:相对于父对象模型原点的坐标空间。

    • 以父对象的pivot(一般为模型中心)为原点,父对象的向右向上向前为xyz轴,所组成的坐标系称作"本地坐标系(Parent空间)"。
    • 在这个"本地坐标系"定义下,相对父对象轴心点(或叫原点)所作的变换就称作"本地坐标变换"。Tranform中localPositon、localRotation就是本地坐标系下的偏移和旋转。
    • 对象的"世界坐标变换" = 父对象的"世界坐标变换"+ 此对象的"本地坐标变换"。
    • 当父节点处于世界坐标原点时,"世界坐标变换"等于"本地坐标变换"。
    • 无父节点时 world transform = local transform。
  3. Self space:相对于模型自身原点的坐标空间。

    • 由对象模型原点和向右向上向前的xyz轴,所组成的坐标系称作"模型坐标系"。

    • 对象本身处于模型空间原点,所以position和rotation始终为0,所以表示对象状态不用模型坐标系。

    • Self space主要用来表明对象移动方向和旋转方向。比如gizmos中Local指的就是模型坐标轴。

    • 对象的Self space就是子对象的Parent space。

不同坐标系下的移动

为了加深理解,用三种坐标系下的移动为例:

csharp 复制代码
public class WhatIsTheLocalSpace : MonoBehaviour
{
    public Transform obt;
    public bool selfSpace = false;
    public bool worldSpace = false;
    public bool parentSpace = false;
    void Update() {
        if (Input.GetKey(KeyCode.D))
        {
            // move along self space
            if (selfSpace && !worldSpace && !parentSpace)
            {
                obt.Translate(Vector3.right*Time.deltaTime,Space.Self);
            }
            
            // move along world space
            if (!selfSpace && worldSpace && !parentSpace)
            {
                obt.Translate(Vector3.right*Time.deltaTime,Space.World);
            }

            // move along parent space
            if (!selfSpace && !worldSpace && parentSpace)
            {
                obt.Translate(obt.parent.TransformDirection(Vector3.right)*Time.deltaTime,Space.World);
            }
        }
    }
}

可以看到,虽然都是Vector3.right,在不同空间下,它可以表示模型自身的右向,也可以是世界空间的右向。因为Unity没提供Space.Parent,所以需要用Transform.TransformDirection()进行坐标空间转换。

这个坐标空间转换不清楚的可以看我这一篇:
【Unity学习笔记】第十五 Transform 查缺补漏 (TransformDirection / InverseTransformDirection解释,三种坐标系下的Translate())

不同坐标系下的旋转------Quaternion左乘右乘的区别辨析

Quaternion和Vector3类似,只是表明相对变换,并未指明相对什么空间下的变换。

例如:

csharp 复制代码
public class OperatorAndOrderTest : MonoBehaviour
{
    // a是b的父节点,c无父节点;测试时b要相对a再作旋转,便于localRotation的测试
    public Transform a, b, c;
    private string str;

    private void Update()
    {
        var quat = Quaternion.AngleAxis(90, Vector3.up);
        // world rotation
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            b.rotation = quat * b.rotation;
            str = "1. b.rotation = quat * b.rotation;\n\r" +
                  "作用是在当前b.rotation旋转基础上再进行quat的旋转,得到的值作为b的rotation,相当于是基于世界坐标系下的quat旋转。本例中相当于绕世界空间的Y轴正向旋转90°。";
        }

        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            b.rotation *= quat;

            str = "2. b.rotation *= quat;\n\r" +
                  " 作用是先相对世界原点作quat旋转,再作先前的b.rotation旋转,得到的值作为b的rotation,相当于是在模型空间下的quat旋转。本例中相当于绕模型自身的Y轴正向旋转90°。";
        }

        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            c.rotation = quat * c.rotation;
            str = "3. c.rotation = quat * c.rotation;\n\r";
        }

        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            c.rotation *= quat;
            str = "4. c.rotation *= quat;\n\r";
        }

        // localRotation
        if (Input.GetKeyDown(KeyCode.Alpha5))
        {
            // b.localRotation = quat * b.localRotation;
            
            //1. 创建旋转,值为 b对象相对父节点的旋转
            Quaternion rotation = b.localRotation;
            //2. 在前面基础上再进行quat旋转
            rotation = quat * b.localRotation;
            //3. 将创建的旋转赋值给b的本地旋转。也就是先相对父节点作b.localRotation旋转(变成当前状态),然后在parent空间作quat旋转
            //相当于对象在原先基础上,在parent空间再作quat旋转。本例中相当于绕父节点的Y轴正向旋转90°。
            b.localRotation = rotation;

            str = "5. b.localRotation = quat * b.localRotation;\n\r" +
                  "相当于对象在原先基础上,在parent空间再作quat旋转。本例中相当于绕父节点的Y轴正向旋转90°。";
        }

        if (Input.GetKeyDown(KeyCode.Alpha6))
        {
            // b.localRotation = b.localRotation * quat;

            // 1.创建旋转,值为quat(并不清楚在什么空间下的旋转)
            Quaternion rotation = quat;
            // 2. 在前面基础上再进行(b对象相对父节点的旋转)的旋转
            rotation = b.localRotation * quat;
            // 3. 将创建的旋转赋值给b的本地旋转。也就是先相对父节点作parent空间的quat旋转,然后再作先前的b.localRotation旋转。
            // 相当于对象在原先基础上,在self空间再作quat旋转。本例中相当于绕模型自身的Y轴正向旋转90°。
            b.localRotation = rotation;

                str = "6. b.localRotation = b.localRotation * quat; \n\r" +
                      "相当于先相对父节点作quat旋转,再作b.localRotation旋转。本例中相当于绕模型自身的Y轴正向旋转90°。";
        }

        if (Input.GetKeyDown(KeyCode.Alpha7))
        {
            c.localRotation = quat * c.localRotation;
            str = "7. c.localRotation = quat * c.localRotation;\n\r";
        }

        if (Input.GetKeyDown(KeyCode.Alpha8))
        {
            c.localRotation *= quat;
            str = "8. c.localRotation *= quat;\n\r";
        }
        // 显示坐标轴
        DrawAxis(a);
        DrawAxis(b);
        DrawAxis(c);
    }
	// 显示坐标轴
    private void DrawAxis(Transform obt)
    {
        DrawAxis(obt, Color.red, Color.green, Color.blue, 5);
    }

    private void DrawAxis(Transform obt, Color xc, Color yc, Color zc, float length)
    {
        if (obt.gameObject.activeInHierarchy)
        {
            var position = obt.position;
            Debug.DrawLine(position, position + obt.right * length, xc);
            Debug.DrawLine(position, position + obt.up * length, yc);
            Debug.DrawLine(position, position + obt.forward * length, zc);
        }
    }

    public Rect rect1 = new Rect(100, 100, 1200, 50);
    public Rect rect2 = new Rect(100, 200, 1200, 50);

    private void OnGUI()
    {
        DrawLabel(rect1, "var quat = Quaternion.AngleAxis(90, Vector3.up);");
        DrawLabel(rect2, str);
    }

    private static void DrawLabel(Rect rect1, String str)
    {
        var style = new GUIStyle
        {
            fontSize = 38,
            wordWrap = true
        };
        GUI.Label(rect1, str, style);
    }
}

世界空间parent空间self空间下的旋转比较

结论就是:

假设待执行的旋转quat = Quaternion.AngleAxis(90, Vector3.up); 那么:

  • b.rotation = quat * b.rotation; //绕世界空间的Y轴正向旋转90° (左乘)
  • b.rotation *= quat; //绕模型自身的Y轴正向旋转90° (右乘)
  • b.localRotation = quat * b.localRotation; //绕父节点的Y轴正向旋转90°(左乘)
  • b.localRotation = b.localRotation * quat; // 绕模型自身的Y轴正向旋转90° (右乘)

总结下来就是,左乘绕世界空间或parent空间旋转(根据rotation或者localRotation判断)右乘绕模型自身空间旋转

总结

Unity中的坐标系比较多,特别是local语义歧义问题,会对后面的学习造成困扰。为此本文详细辨析了World space、Parent space和Self space含义,并用三个坐标系下的移动进行示例。同时辨析了Quaternion左乘右乘的区别,展示了其与坐标空间的关联。本文论述有限,但提供了详细代码,读者需结合代码进行实验方能深刻体会。注意,文章内容不一定对,需自行辨别。

相关推荐
牙膏上的小苏打233337 分钟前
Unity Surround开关后导致获取主显示器分辨率错误
unity·主屏幕
Unity大海3 小时前
诠视科技Unity SDK开发环境配置、项目设置、apk打包。
科技·unity·游戏引擎
浅陌sss8 小时前
Unity中 粒子系统使用整理(一)
unity·游戏引擎
维度攻城狮13 小时前
实现在Unity3D中仿真汽车,而且还能使用ros2控制
python·unity·docker·汽车·ros2·rviz2
为你写首诗ge16 小时前
【Unity网络编程知识】FTP学习
网络·unity
神码编程18 小时前
【Unity】 HTFramework框架(六十四)SaveDataRuntime运行时保存组件参数、预制体
unity·编辑器·游戏引擎
菲fay19 小时前
Unity 单例模式写法
unity·单例模式
火一线21 小时前
【Framework-Client系列】UIGenerate介绍
游戏·unity
ZKY_241 天前
【工具】Json在线解析工具
unity·json
ZKY_241 天前
【Unity】处理文字显示不全的问题
unity·游戏引擎