No, questo non è un bug del motore o un artefatto di una particolare rappresentazione di rotazione (anche questi possono accadere, ma questo effetto si applica a tutti i sistemi che rappresentano rotazioni, quaternioni incluse).
Hai scoperto un fatto reale su come funziona la rotazione nello spazio tridimensionale e parte dalla nostra intuizione su altre trasformazioni come la traduzione:
Quando componiamo rotazioni su più di un asse, il risultato che otteniamo non è solo il valore totale / netto che abbiamo applicato a ciascun asse (come potremmo aspettarci per la traduzione). L'ordine in cui applichiamo le rotazioni cambia il risultato, poiché ogni rotazione sposta gli assi su cui vengono applicate le rotazioni successive (se ruotano attorno agli assi locali dell'oggetto) o la relazione tra l'oggetto e l'asse (se ruota attorno al mondo assi).
Il cambiamento delle relazioni degli assi nel tempo può confondere la nostra intuizione su ciò che ogni asse "dovrebbe" fare. In particolare, alcune combinazioni di rotazioni di imbardata e beccheggio danno lo stesso risultato di una rotazione del rullo!
Puoi verificare che ogni passo stia ruotando correttamente attorno all'asse che abbiamo richiesto - nella nostra notazione non ci sono difetti o artefatti del motore che interferiscono con o indovinano il nostro input - la natura sferica (o ipersferica / quaternione) significa semplicemente che le nostre trasformazioni "avvolgono intorno "l'uno sull'altro. Possono essere ortogonali localmente, per piccole rotazioni, ma quando si accumulano troviamo che non sono globalmente ortogonali.
Questo è più drammatico e chiaro per curve a 90 gradi come quelle sopra, ma gli assi erranti si insinuano anche in molte piccole rotazioni, come dimostrato nella domanda.
Quindi cosa facciamo a riguardo?
Se disponi già di un sistema di rotazione pece-imbardata, uno dei modi più rapidi per eliminare il rollio indesiderato è quello di cambiare una delle rotazioni per operare sugli assi di trasformazione globale o genitore anziché sugli assi locali dell'oggetto. In questo modo non è possibile ottenere la contaminazione incrociata tra i due: un asse rimane assolutamente controllato.
Ecco la stessa sequenza di pitch-yaw-pitch che è diventata un rotolo nell'esempio sopra, ma ora applichiamo la nostra imbardata attorno all'asse Y globale invece che dell'oggetto
Quindi possiamo riparare la videocamera in prima persona con il mantra "Pitch Locally, Yaw Globally":
void Update() {
float speed = lookSpeed * Time.deltaTime;
transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f, Space.World);
transform.Rotate(-Input.GetAxis("Vertical") * speed, 0f, 0f, Space.Self);
}
Se stai combinando le tue rotazioni utilizzando la moltiplicazione, capovolgi l'ordine sinistro / destro di una delle moltiplicazioni per ottenere lo stesso effetto:
// Yaw happens "over" the current rotation, in global coordinates.
Quaternion yaw = Quaternion.Euler(0f, Input.GetAxis("Horizontal") * speed, 0f);
transform.rotation = yaw * transform.rotation; // yaw on the left.
// Pitch happens "under" the current rotation, in local coordinates.
Quaternion pitch = Quaternion.Euler(-Input.GetAxis("Vertical") * speed, 0f, 0f);
transform.rotation = transform.rotation * pitch; // pitch on the right.
(L'ordine specifico dipenderà dalle convenzioni di moltiplicazione nel proprio ambiente, ma sinistra = più globale / destra = più locale è una scelta comune)
Ciò equivale a memorizzare l'imbardata totale netta e l'intonazione totale che si desidera come variabili float, quindi applicare sempre il risultato netto tutto in una volta, costruendo un singolo nuovo quaternione o matrice di orientamento da questi soli angoli (purché si mantenga totalPitch
bloccato):
// Construct a new orientation quaternion or matrix from Euler/Tait-Bryan angles.
var newRotation = Quaternion.Euler(totalPitch, totalYaw, 0f);
// Apply it to our object.
transform.rotation = newRotation;
o equivalentemente ...
// Form a view vector using total pitch & yaw as spherical coordinates.
Vector3 forward = new Vector3(
Mathf.cos(totalPitch) * Mathf.sin(totalYaw),
Mathf.sin(totalPitch),
Mathf.cos(totalPitch) * Mathf.cos(totalYaw));
// Construct an orientation or view matrix pointing in that direction.
var newRotation = Quaternion.LookRotation(forward, new Vector3(0, 1, 0));
// Apply it to our object.
transform.rotation = newRotation;
Usando questa divisione globale / locale, le rotazioni non hanno la possibilità di mescolarsi e influenzarsi a vicenda, perché sono applicate a insiemi di assi indipendenti.
La stessa idea può aiutare se è un oggetto al mondo che vogliamo ruotare. Per un esempio come il globo, vorremmo spesso capovolgerlo e applicare la nostra imbardata localmente (quindi gira sempre attorno ai suoi poli) e inclinarsi a livello globale (quindi punta verso / lontano dalla nostra vista, piuttosto che verso / lontano dall'Australia , ovunque indichi ...)
limitazioni
Questa strategia ibrida globale / locale non è sempre la soluzione giusta. Ad esempio, in un gioco con volo / nuoto 3D, potresti voler puntare verso l'alto / verso il basso e avere comunque il pieno controllo. Ma con questa configurazione premi il blocco del gimbal : l'asse di imbardata (su globale) diventa parallelo all'asse di rollio (avanti locale) e non hai modo di guardare a sinistra oa destra senza torcere.
Quello che puoi fare invece in casi come questo è usare rotazioni locali pure come abbiamo iniziato con la domanda sopra (quindi i tuoi controlli si sentono uguali indipendentemente da dove stai guardando), che inizialmente lascerà insinuarsi un po 'di rollio - ma poi lo correggiamo.
Ad esempio, possiamo usare le rotazioni locali per aggiornare il nostro vettore "in avanti", quindi utilizzare quel vettore in avanti insieme a un vettore "in alto" di riferimento per costruire il nostro orientamento finale. (Usando, ad esempio, il metodo Quaternion.LookRotation di Unity o costruendo manualmente una matrice ortonormale da questi vettori) Controllando il vettore up, controlliamo il rollio o il twist.
Per l'esempio di volo / nuoto, ti consigliamo di applicare queste correzioni gradualmente nel tempo. Se è troppo brusco, la vista può oscillare in modo distratto. Invece, puoi usare il vettore in alto del giocatore e suggerirlo verso la verticale, fotogramma per fotogramma, fino a quando la loro vista non si livella. Applicare questo durante un turno a volte può essere meno nauseabondo che torcere la telecamera mentre i controlli del giocatore sono inattivi.