unity中camera攝像頭控制詳解
在Unity的Transform中,rotation屬性對應的就是尤拉角,一共分為3個軸,x、 y和z,而每一個數值對應的是繞對應的軸旋轉的度數。
如上圖所示,表示按照座標順序旋轉,X軸旋轉30°,Y軸旋轉90°,Z軸旋轉 10°。尤拉角的優點:只需使用3個值,即三個座標軸的旋轉角度;缺點:必須 嚴格按照順序進行旋轉(順序不同結果就不同;容易造成“萬向節鎖”現象,造 成這個現象的原因是因為尤拉旋轉是按順序先後旋轉座標軸的,並非同時旋轉, 所以當旋轉中某些座標重合就會發生萬向節鎖,這時就會丟失一個方向上的選擇 能力,除非打破原來的旋轉順序或者三個座標軸同時旋轉;由於萬向節鎖的存在, 尤拉旋轉無法實現球面平滑插值。
四元數是用於表示旋轉的一種方式,而且transform中的rotation屬性的資料類 型就是四元數,那麼四元數該如何表示呢?從本質上來講,四元數就是一個高階 複數,也就是一個四維空間。話說當時十九世紀的時候,愛爾蘭的數學家 Hamilton一直在研究如何將複數從2D擴充套件至3D,他一直以為擴充套件至3D應該有兩個 虛部(可是他錯了,哈哈)。有一天他在路上突發奇想,我們搞搞三個虛部的試 試!結果他就成功了,於是乎他就把答案刻在了Broome橋上。說到這裡,也就明 白了,四元數其實就是定義了一個有三個虛部的複數w xi yj zk。記法 [w,(x,y,z)]。四元數優點:可以避免萬向節鎖現象;只需要一個4維的四元數就 可以執行繞任意過原點的向量的旋轉,方便快捷,在某些實現下比旋轉矩陣效率 更高;可以提供平滑插值;缺點:比尤拉旋轉稍微複雜了一點點,因為多了一個 維度;理解更困難,不直觀。四元數與尤拉角轉換:
// 獲取攝像機尤拉角 Vector3 angles = transform.eulerAngles; // 設定攝像頭尤拉角 targetRotation.eulerAngles = new Vector3(euler.y, euler.x, 0);
現在讓我們再看Update裡的旋轉程式碼:
if (Mathf.Abs(dx) > 0 || Mathf.Abs(dy) > 0) { // 獲取攝像機尤拉角 Vector3 angles = transform.rotation.eulerAngles; // 尤拉角表示按照座標順序旋轉,比如angles.x=30,表示按x軸旋轉30°,dy改變引起x軸的變化angles.x = Mathf.Repeat(angles.x + 180f, 360f) - 180f; angles.y += dx; angles.x -= dy; // 設定攝像頭旋轉 Quaternion rotation = Quaternion.identity; rotation.eulerAngles = new Vector3(angles.x, angles.y, 0); transform.rotation = rotation; // 重新設定攝像頭位置 Vector3 position = model.position; Vector3 distance = rotation * new Vector3(0, 0, default_distance); transform.position = position - distance; }
首先我們從四元數(transform.rotation)取得尤拉角angles,由於尤拉角表示按 照座標順序旋轉,比如angles.x=30,表示按x軸旋轉30°,dy改變引起尤拉角的 x軸的變化,所以angles.y+=dx,然後再設定攝像頭旋轉,即設定攝像頭四元數 rotation,現在明白了設定旋轉的寫法了吧。
下面是重點,重新設定攝像頭位置,我們看到rotation*new Vector3(0,0,default_distance)這句,一個Quaternion例項和一個Vector3相乘 的運算,作用是對引數座標點進行rotation變換,也就是說對 Vector3(0,0,default_distance)進行rotation旋轉,最後一句 transform.position = position - distance,進行一個Vector3的向量計算, 最終結果就是攝像頭沿著選中後的方向移動-distance的距離,就是我們要的結果。
如果對向量計算不清楚,請看下面的向量計算這節
在進行下面開發之前我們把程式西安優化一下,我們不在Update函式裡直接修改 攝像頭旋轉和位置,而是記錄旋轉變化,在FixUpdate函式裡設定攝像頭最終的 旋轉和位置,Update和FixedUpdate的區別:Update跟當前平臺的幀數有關,而 FixedUpdate是真實時間,所以處理物理邏輯的時候要把程式碼放在FixedUpdate而 不是Update。
using UnityEngine; /** * 自由攝像頭 * 2018-10-03 by flysic, [email protected] */ public class FreeCameraController : MonoBehaviour { // 模型 public Transform model; // 預設距離 private const float default_distance = 5f; // 計算移動 private Vector3 position; // 計算旋轉 private Quaternion rotation; // Use this for initialization void Start () { // 旋轉歸零 transform.rotation = Quaternion.identity; // 初始位置是模型 position = model.position; } // Update is called once per frame void Update() { float dx = Input.GetAxis("Mouse X"); float dy = Input.GetAxis("Mouse Y"); // 滑鼠右鍵旋轉 if (Input.GetMouseButton(1)) { if (Mathf.Abs(dx) > 0 || Mathf.Abs(dy) > 0) { // 獲取攝像機尤拉角 Vector3 angles = transform.rotation.eulerAngles; // 尤拉角表示按照座標順序旋轉,比如angles.x=30,表示按x軸旋轉30°,dy改變引起x軸的變化 angles.x = Mathf.Repeat(angles.x + 180f, 360f) - 180f; angles.y += dx; angles.x -= dy; // 計算攝像頭旋轉 rotation.eulerAngles = new Vector3(angles.x, angles.y, 0); } } } private void FixedUpdate() { // 設定攝像頭旋轉 transform.rotation = rotation; // 設定攝像頭位置 transform.position = position - rotation * new Vector3(0, 0, default_distance); } }
最上面定義了兩個私有屬性,private Vector positon,private Quaternion rotation,position在Start函式裡記錄模型的位置(目前不變化,後面移動時要 變化),rotation用於記錄Update裡計算的旋轉,FixedUpdate函式里根據 rotation、position、default_distance計算攝像頭最終的位置。
我們操作一下發現,雖然旋轉達到要求,但是操作感覺很生硬,現在給旋轉加上 速度和阻尼,效果就會好很多。
using UnityEngine; /** * 自由攝像頭 * 2018-10-03 by flysic, [email protected] */ public class FreeCameraController : MonoBehaviour { // 模型 public Transform model; // 旋轉速度 public float rotateSpeed = 32f; public float rotateLerp = 8; // 計算移動 private Vector3 position; // 計算旋轉 private Quaternion rotation, targetRotation; // 預設距離 private const float default_distance = 5f; // Use this for initialization void Start () { // 旋轉歸零 transform.rotation = Quaternion.identity; // 初始位置是模型 position = model.position; } // Update is called once per frame void Update() { float dx = Input.GetAxis("Mouse X"); float dy = Input.GetAxis("Mouse Y"); // 滑鼠右鍵旋轉 if (Input.GetMouseButton(1)) { dx *= rotateSpeed; dy *= rotateSpeed; if (Mathf.Abs(dx) > 0 || Mathf.Abs(dy) > 0) { // 獲取攝像機尤拉角 Vector3 angles = transform.rotation.eulerAngles; // 尤拉角表示按照座標順序旋轉,比如angles.x=30,表示按x軸旋轉30°,dy改變引起x軸的變化 angles.x = Mathf.Repeat(angles.x + 180f, 360f) - 180f; angles.y += dx; angles.x -= dy; // 計算攝像頭旋轉 targetRotation.eulerAngles = new Vector3(angles.x, angles.y, 0); } } } private void FixedUpdate() { rotation = Quaternion.Slerp(rotation, targetRotation, Time.deltaTime * rotateLerp); // 設定攝像頭旋轉 transform.rotation = rotation; // 設定攝像頭位置 transform.position = position - rotation * new Vector3(0, 0, default_distance); } }
最上面增加了旋轉速度(rotateSpeed)和蘇尼(rotateLerp),rotateSpeed越高旋 轉越快,rotateLerp越高阻尼越小,阻尼使用了四元數的球面差值(前面說過, 只有四元數能做到球面差值),使旋轉有個漸變過程,大家可以在Inspector的 tabFree Camera Controller指令碼處修改引數嘗試最佳的設定;定義了新的變數 targetRotation,用於計算最終旋轉,配合rotation實現阻尼效果;positon目 前只是記錄模型位置,後面移動時就會改變。