【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左乘右乘的区别,展示了其与坐标空间的关联。本文论述有限,但提供了详细代码,读者需结合代码进行实验方能深刻体会。注意,文章内容不一定对,需自行辨别。

相关推荐
5 小时前
Unity开发中常用的洗牌算法
java·算法·unity·游戏引擎·游戏开发
马特说9 小时前
Unity VR手术模拟系统架构分析与数据流设计
unity·系统架构·vr
心前阳光1 天前
Unity WebGL文本输入
unity·游戏引擎·webgl
天涯过客TYGK1 天前
unity A星寻路
unity·游戏引擎
KhalilRuan1 天前
Unity Demo——3D平台跳跃游戏笔记
笔记·游戏·unity·游戏引擎
ttod_qzstudio2 天前
Unity中使用EzySlice实现模型切割与UV控制完全指南
unity
南無忘码至尊2 天前
Unity 实现与 Ollama API 交互的实时流式响应处理
unity·游戏引擎·交互
平行云2 天前
如何实现UE程序大并发多集群的像素流部署
unity·ue5·图形渲染
向宇it3 天前
【unity小技巧】在 Unity 中将 2D 精灵添加到 3D 游戏中,并实现阴影投射效果,实现类《八分旅人》《饥荒》等等的2.5D游戏效果
游戏·3d·unity·编辑器·游戏引擎·材质
向宇it3 天前
Unity Universal Render Pipeline/Lit光照材质介绍
游戏·unity·c#·游戏引擎·材质