1. 程式人生 > >unity中camera攝像頭控制詳解

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目 前只是記錄模型位置,後面移動時就會改變。