【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

码农世界 2024-05-22 后端 63 次浏览 0个评论

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

作者:CSDN@|Ringleader|

目录

    • Quaternion API 速览
    • FromToRotation在Transform中的应用
    • LookRotation 中upwards取Vector3.up和 transform.up的区别
    • 旋转时如何保持Y轴不变,但朝向目标旋转呢?
    • 不同旋转方法案例
    • lerp与LerpUnclamped区别
    • Quaternion.Slerp 、Quaternion.RotateTowards区别
    • Lerp、Slerp比较
    • Quaternion * operator
    • 总结

      主要参考:

      1. Unity手册 & Quaternion API
      2. Unity3D - 详解Quaternion类(二)
      3. 【Unity编程】Unity中关于四元数的API详解

      Quaternion API 速览

      创建旋转:

      • FromToRotation 创建一个从 fromDirection 旋转到 toDirection 的旋转。
      • LookRotation 使用指定的 forward 和 upwards 方向创建旋转。
      • AngleAxis 创建一个围绕 axis 旋转 angle 度的旋转。
      • Angle 返回两个旋转 a 和 b 之间的角度(以度为单位)。(180°以内)

        操作旋转:

        • Lerp 在 a 和 b 之间插入 t,然后对结果进行标准化处理。参数 t 被限制在 [0, 1] 范围内。
        • Slerp 在四元数 a 与 b 之间按比率 t 进行球形插值。参数 t 限制在范围 [0, 1] 内。
        • RotateTowards 将旋转 from 向 to 旋转。

          Transform 类还提供了一些方法可用于处理 Quaternion 旋转:Transform.Rotate & Transform.RotateAround

          FromToRotation在Transform中的应用

          Transform中有很多Quaternion的应用,比如获取对象的xyz轴向量在世界坐标系下的表示,就用到了FromToRotation:

             ///The red axis of the transform in world space.
              public Vector3 right
              {
                get => this.rotation * Vector3.right;
                set => this.rotation = Quaternion.FromToRotation(Vector3.right, value);
              }
              ///The green axis of the transform in world space.
              public Vector3 up
              {
                get => this.rotation * Vector3.up;
                set => this.rotation = Quaternion.FromToRotation(Vector3.up, value);
              }
              ///Returns a normalized vector representing the blue axis of the transform in world space.
              public Vector3 forward
              {
                get => this.rotation * Vector3.forward;
                set => this.rotation = Quaternion.LookRotation(value);
              }
          

          LookRotation 中upwards取Vector3.up和 transform.up的区别

          public static Quaternion LookRotation (Vector3 forward, Vector3 upwards= Vector3.up);

          因为LookRotation 会使对象Z轴与forward参数向量对齐,X 轴与Vector3.Cross(upwards,forward)这个叉乘结果对齐,Y 轴与 Z 和 X 的叉乘(Vector3.Cross(transform.forward,transform.right) )对齐。(注意unity左手坐标系,叉乘方向)

          所以会看到当upwards取世界空间的向上和模型空间的向上是有区别的。

          或者说,upwards取模型空间的向上时对象可以绕自身z轴旋转,对象状态并不固定。

          【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

          public class LookRotationTest : MonoBehaviour
          {
              public Transform obt_forward;
              public Transform obt_worldUp;
              public Transform obt_selfUp;
              
              public bool showCrossResult = false;// 验证叉乘方向用
              private void Update()
              {
                  // upwards取世界空间的向上
                  LookForward(obt_worldUp, obt_forward.position - obt_worldUp.position, Vector3.up);
                  // upwards取模型空间的向上
                  LookForward(obt_selfUp,obt_forward.position - obt_selfUp.position, obt_selfUp.up);
              }
              private void LookForward(Transform obt, Vector3 forward,Vector3 upwards)
              {
                  obt.rotation = Quaternion.LookRotation(forward, upwards);
                  var position = obt.position;
                  Debug.DrawLine(obt_forward.position, position);
                  Debug.DrawLine(position, position + obt.right * 5, Color.red);
                  Debug.DrawLine(position, position + obt.up * 5, Color.green);
                  Debug.DrawLine(position, position + obt.forward * 5, Color.blue);
                  
                  // 验证叉乘方向, 叉乘结果与LookFoward结果一致
                  if (showCrossResult)
                  {
                      var X_cross = Vector3.Cross(upwards,forward);
                      var Y_cross = Vector3.Cross(obt.forward,obt.right);
                  
                      Debug.DrawLine(position, position + X_cross * 10, Color.cyan);
                      Debug.DrawLine(position, position + Y_cross * 10, Color.yellow);
                  }
              }
          }
          

          旋转时如何保持Y轴不变,但朝向目标旋转呢?

          使用Vector3.ProjectOnPlane将目标方向投影到xz平面。

          【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

          Lookat, FromToRotation and LookRotation?

          不同旋转方法案例

          下面代码比较了transform.forward 、Transform.LookAt、Quaternion.FromToRotation、LookRotation、Quaternion.RotateTowards、Vector3.ProjectOnPlane等不同方法

          public class CompareImmediateAndStep : MonoBehaviour
          {
              public Transform turret;
              public Transform enemy;
              private string str;
              private void Update()
              {
                  var targetTowards = enemy.position - turret.position;
                  // 立刻跟随敌人
                  if (Input.GetKey(KeyCode.Alpha1))
                  {
                      turret.forward = targetTowards;
                      str = "使用turret.forward = targetTowards;";
                  }
                  // 上面本质也是使用FromToRotation方法
                  if (Input.GetKey(KeyCode.Alpha2))
                  {
                      turret.rotation = Quaternion.FromToRotation(Vector3.forward, targetTowards);
                      str = "使用turret.rotation = Quaternion.FromToRotation(Vector3.forward, targetTowards);";
                  }
                  if (Input.GetKey(KeyCode.Alpha3))
                  {
                      //当FromToRotation的fromDirection参数是forward轴时,可以用LookRotation
                      turret.rotation = Quaternion.LookRotation(targetTowards);
                      str = "turret.rotation = Quaternion.LookRotation(targetTowards);";
                  }
                  if (Input.GetKey(KeyCode.Alpha4))
                  {
                      turret.LookAt(enemy.transform);
                      str = "turret.LookAt(enemy.transform);";
                  }
                  // 插值方式,加入旋转速度
                  if (Input.GetKey(KeyCode.Alpha5))
                  {
                      var fromToRotation = Quaternion.LookRotation(targetTowards);
                      turret.rotation = Quaternion.RotateTowards(turret.rotation, fromToRotation, 45 * Time.deltaTime);
                      str = "使用worldUp的Quaternion.RotateTowards";
                  }
                  // 保持对象Y轴朝向不变,将targetTowards进行投影
                  if (Input.GetKey(KeyCode.Alpha6))
                  {
                      var fromToRotation = Quaternion.LookRotation(Vector3.ProjectOnPlane(targetTowards,Vector3.up),Vector3.up);
                      turret.rotation = Quaternion.RotateTowards(turret.rotation, fromToRotation, 45 * Time.deltaTime);
                      
                      str = "保持Y轴朝向不变,将targetTowards进行投影";
                  }
                  DrawAxis(turret);
                  DrawAxis(enemy);
              }
              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);
                  }
              }
              
              private Rect rect = new Rect(100, 100, 600, 50);
              private void OnGUI()
              {
                  DrawLabel(rect, str);
              }
              private static void DrawLabel(Rect rect1, String str)
              {
                  var style = new GUIStyle
                  {
                      fontSize = 38,
                      wordWrap = true
                  };
                  GUI.Label(rect1, str, style);
              }
          }
          

          lerp与LerpUnclamped区别

          区别就是Unclamp不会钳值,还能取负值。

          【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

          Vector3: Lerp vs LerpUnclamped

          Quaternion.Slerp 、Quaternion.RotateTowards区别

          RotateTowards本质也是使用了SlerpUnclamped方法,但其旋转速度恒定,不会因target变化而改变。

          public static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta)
              {
                float num = Quaternion.Angle(from, to);
                return (double) num == 0.0 ? to : Quaternion.SlerpUnclamped(from, to, Mathf.Min(1f, maxDegreesDelta / num));
              }
          

          这篇帖子 Use Quaternion.RotateTowards() instead of Quaternion.Slerp() 提到了:

          如果旋转操作的from和to都已知,使用 Quaternion.Slerp() - 例如打开一扇门或箱子盖。

          如果要以恒定角速度转向某物,则使用 Quaternion.RotateTowards() - 例如,在塔防类游戏中要转向塔的炮塔。

          Lerp、Slerp比较

          Quaternion的插值分析及总结

          Lerp求得的是四元数在圆上的弦上的等分,而Slerp求得的是四元数载圆上的圆弧的等分

          【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

          进行代码验证:

          public class SlerpTest : MonoBehaviour
          {
              public Transform obtLerp;
              public Transform obtSlerp;
              public Transform towardsObt;
              public bool towardsNotChanged;
              private Quaternion currentLookRotation,lastLookRotation;
              private Quaternion initRotationLerp, initRotationSlerp;
              
              public float speed = 0.1f;
              float total = 0.0f;
              private void Start()
              {
                  lastLookRotation = Quaternion.LookRotation(towardsObt.position-obtLerp.position);
                  initRotationLerp = obtLerp.rotation;
                  initRotationSlerp = obtSlerp.rotation;
              }
              void Update()
              {
                  CompareSlerpAndLerp();
              }
              private void CompareSlerpAndLerp()
              {
                  currentLookRotation = Quaternion.LookRotation(towardsObt.position - obtLerp.position);
                  towardsNotChanged = currentLookRotation == lastLookRotation;
                  // 改变朝向时,重置初始位置、重置total
                  if (!towardsNotChanged)
                  {
                      lastLookRotation = currentLookRotation;
                      // 重置执行Lerp、Slerp时的初始旋转
                      initRotationLerp = obtLerp.rotation;
                      initRotationSlerp = obtSlerp.rotation;
                      total = 0;
                      return;
                  }
                  lastLookRotation = currentLookRotation;
                  total += Time.deltaTime * speed;
                  if (total >= 1.0f)
                      total = 1.0f;
                  obtLerp.rotation = Quaternion.Lerp(initRotationLerp, currentLookRotation, total);
                  obtSlerp.rotation = Quaternion.Slerp(initRotationSlerp, currentLookRotation, total);
                  DrawAxis(obtLerp);
                  DrawAxis(obtSlerp, Color.cyan, Color.magenta, Color.yellow, 10);
              }
              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);
                  }
              }
              private Rect rect = new Rect(100, 100, 600, 50);
              private void OnGUI()
              {
                  DrawLabel(rect,"Total:"+total);
              }
              private void DrawLabel(Rect rect1, String str)
              {
                  var style = new GUIStyle
                  {
                      fontSize = 38,
                      wordWrap = true
                  };
                  GUI.Label(rect1, str, style);
              }
          }
          

          如下图,RGB颜色的轴是Lerp方法,青紫黄轴是Slerp方法。可以看到Lerp相对Slerp来说先慢,中间快,最后慢。和上面的弦等分和弧等分理论一致。

          【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

          Quaternion * operator

          【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证

          关于Quaternion 左乘右乘和坐标系的关系看我这篇:【Unity学习笔记】第十六 World space、Parent space和Self space及Quaternion左乘右乘辨析

          总结

          本文主要辨析Quaternion中Lerp、Slerp、RotateTowards等方法,并进行代码验证。至此,对Quaternion核心方法的理解已比较清晰,但其中的数学原理比如四元数、和欧拉角的关系、万向锁、逆和共轭等问题还是有待进一步学习。

转载请注明来自码农世界,本文标题:《【Unity学习笔记】第十七 Quaternion 中 LookRotation、Lerp、Slerp、RotateTowards等方法辨析与验证》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,63人围观)参与讨论

还没有评论,来说两句吧...

Top