Unity歐拉旋轉與萬向鎖

以前不在意,但現在旋轉的時候亂七八糟的,搞的很煩,特來研究一下

這裏不講基礎知識,比如x軸指向物體的右方,Inspector面板中x表示繞x旋轉等等。若有疏漏,敬請指出。

 

unity中使用歐拉改變角度時並不會按照直覺中的來,你以爲旋轉是按照物體本身座標系旋轉的。像這樣:

然後你寫了一段歐拉的代碼:

 //inputEulerX爲判斷是否按着x返回的值,按着返回1,否則返回0。shiftMultiplier表示按住shift時輸入取反。
transform.eulerAngles += new Vector3(inputEulerX, inputEulerY, inputEulerZ)*shiftMultiplier ;

試一下效果:

難以控制,繞x軸旋轉即是飛機朝自身上方爬升,然而爬到90度後,繼續按住x,飛機就會劇烈抖動,無法繼續爬升,此時按住y軸和x軸旋轉方向皆是繞世界y軸旋轉,導致像圖裏一樣無法控制,最終飛出屏幕。而且這個寫法在運行一會後就會發現角度逐漸混亂,角度會出現偏差。在unity中,即是你只控制歐拉角的x值,在一定條件下,其他軸也會產生微小的變化,偏差的這個鍋扔給unity吧。而y軸和x軸旋轉方向一致則是所謂的萬向鎖。

詳細說一下萬向鎖的形成原因吧:

在unity中,進行一次歐拉旋轉,是按照先繞y軸旋轉,再繞x軸旋轉,再繞Z軸旋轉。繞三個軸旋轉時形成各自形成形成一個旋轉平面(或者說旋轉環):

綠色代表繞y軸形成的旋轉平面,紅色代表繞x軸旋轉形成的旋轉平面,藍色則是繞Z軸旋轉形成的旋轉平面。
而在Unity中!!!Y軸永遠指向世界座標的Y座標,所以Y軸的旋轉平面永遠水平!!!

當繞y軸旋轉時,會帶動x軸旋轉平面旋轉,繞x軸旋轉時又會帶動Z軸選轉平面旋轉。什麼意思呢,看下面這個圖

將其想象成這樣的一個機械結構,綠色是繞y軸,紅色是繞x軸,藍色(灰色?)是繞Z軸。注意其中的機械結構,綠色環,即y軸的旋轉平面,固定在水平面上,只能水平旋轉。x軸旋轉平面嵌套在y軸旋轉平面中,z軸嵌套在x軸旋轉平面中,注意嵌套出的接口表示每個環只能朝向固定的方向旋轉。旋轉綠色環(繞y軸旋轉),紅色環跟隨着轉動,藍色環也跟隨着紅色環轉動,這就是unity的繞y軸旋轉,轉動完成後再轉動x軸(紅色環),藍色環跟着紅色環轉動,當紅色環繞x軸轉動90度後,藍色環就會和綠色環處於同一水平面,即y軸旋轉平面和z軸旋轉平面水平。此時旋轉y軸和z軸,飛機都表現爲只能繞世界y軸轉動,這就是所謂的萬向鎖。當然還有其他萬向鎖,基本上都是其中兩個旋轉平面重合導致失去其中一個方向的旋轉能力。這就是unity中的萬向鎖的形成原因。在unity的日常旋轉控制中,只要限制x軸的角度不要達到90度基本上就可以避免萬向鎖的產生。

相關視頻,一定要看https://www.bilibili.com/video/av76536794?from=search&seid=6785398138233897786。感謝這位up的搬運。

還可以參考另一篇博客:https://blog.csdn.net/fengya1/article/details/50721768

至於飛機的抖動和無法爬升,在unity官方文檔中,明確說明eulerAngles只可獲取並賦值,不能累加,因爲超過360度就會失效。雖然我沒有理解這句話,不過以後別累加了。(明明才90度啊,沒有到360度啊,怎麼就這樣了呢,劇烈抖動又是因爲什麼呢?爲什麼無法繼續旋轉了呢,無法理解,有人明白的話麻煩解釋一下)。總而言之,這段話可以無視。

 

那麼我們不累加,而是使用一個額外的值來計算旋轉值,然後將這個值賦值給歐拉:

  void Start()
    {
        InitialEuler = transform.eulerAngles;
       
    }
InitialEuler += new Vector3(inputEulerX, inputEulerY, inputEulerZ) * shiftMultiplier;
InitialEuler = new Vector3(Mathf.Clamp(InitialEuler.x, -80, 80), InitialEuler.y%360, InitialEuler.z%360);
Debug.Log(transform.eulerAngles + "_____" + transform.localEulerAngles);
transform.eulerAngles =InitialEuler ;

這裏對x值進行了限制,不過不限制也並沒有問題。使用這個寫法,可以無限制的進行旋轉,在x軸旋轉90度後也能繼續旋轉,雖然此時依然有萬向鎖。在後面對y,z取餘360的原因是避免值無限增大,如若物體不停旋轉,超過了Vector3中數值類型的最大值就炸了。

 

你以爲這樣就能想最開始那張圖一樣運行了(至少我之前是那麼想的)?錯!好好思考一下,會有什麼問題。

飛機在側旋後想要朝飛機自身上方爬升時,爬升方向卻莫名其妙(圖中的急轉彎)。

這並不算錯誤,而是用歐拉不適合這一用法,在之前的學習中我們瞭解到,飛機想要爬升,就要繞自身x軸旋轉,而只有繞y軸旋轉才能使x軸的旋轉平面旋轉,側旋只是繞z軸旋轉,所以無法影響到x軸旋轉平面,所以當按下x時進行的爬升只與當前y軸平面有關係,只有繞y軸旋轉後,爬升平面纔會改變。

要想飛機像圖1一樣能夠側旋後爬升只需使用以下代碼:

transform.Rotate(Vector3.right * inputEulerX * shiftMultiplier);
transform.Rotate(Vector3.up * inputEulerY * shiftMultiplier);
transform.Rotate(Vector3.forward * inputEulerZ * shiftMultiplier);

“你說半天歐拉結果給我用Rotate?”

不急,我將這麼多是爲了讓你深入瞭解萬向鎖的原因和歐拉的旋轉原理。歐拉適合類似第一人稱視角或者第三人稱視角的算法,只繞x和y軸旋轉:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FPSCam : MonoBehaviour
{
    float mouseX = 0;
    float mouseY = 0;
    Vector3 InitialEuler = new Vector3(0, 0, 0);
    // Start is called before the first frame update
    void Start()
    {
        InitialEuler = transform.eulerAngles;

        Cursor.lockState = CursorLockMode.Locked;
    }
    // Update is called once per frame
    void Update()
    {
        mouseX = Input.GetAxis("Mouse X");
        mouseY = Input.GetAxis("Mouse Y");
        InitialEuler += new Vector3(-mouseY, mouseX, 0) ;
        InitialEuler = new Vector3(Mathf.Clamp(InitialEuler.x, -80, 80), InitialEuler.y, InitialEuler.z);
        transform.localEulerAngles = InitialEuler;
    }
}

只需這麼一點代碼,一個簡單的帶角度限制的第一人稱相機就做好了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章