轉載請注明出處: 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
- 總結
主要參考:
- Unity手冊 & Quaternion API
- Unity3D - 詳解Quaternion類(二)
- 【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:
///<para>The red axis of the transform in world space.</para>public Vector3 right{get => this.rotation * Vector3.right;set => this.rotation = Quaternion.FromToRotation(Vector3.right, value);}///<para>The green axis of the transform in world space.</para>public Vector3 up{get => this.rotation * Vector3.up;set => this.rotation = Quaternion.FromToRotation(Vector3.up, value);}///<para>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軸旋轉,對象狀態并不固定。
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平面。
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軸時,可以用LookRotationturret.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區別
區別就是Unclam不會鉗值,而且取負數時會從to→from旋轉。
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求得的是四元數載圓上的圓弧的等分
進行代碼驗證:
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;// 改變朝向時,重置初始位置、重置totalif (!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來說先慢,中間快,最后慢。和上面的弦等分和弧等分理論一致。
Quaternion * operator
關于Quaternion 左乘右乘和坐標系的關系看我這篇:【Unity學習筆記】第十六 World space、Parent space和Self space及Quaternion左乘右乘辨析
總結
本文主要辨析Quaternion中Lerp、Slerp、RotateTowards等方法,并進行代碼驗證。至此,對Quaternion核心方法的理解已比較清晰,但其中的數學原理比如四元數、和歐拉角的關系、萬向鎖、逆和共軛等問題還是有待進一步學習。