Sto ruotando un oggetto su due assi, quindi perché continua a torcere attorno al terzo asse?


38

Vedo spesso sorgere domande che hanno questo problema di fondo, ma sono tutte coinvolte nei dettagli di una determinata funzionalità o strumento. Ecco un tentativo di creare una risposta canonica a cui possiamo fare riferimento agli utenti quando si presenta - con molti esempi animati! :)


Diciamo che stiamo realizzando una fotocamera in prima persona. L'idea di base è che dovrebbe guardare a sinistra e a destra, e inclinare per guardare su e giù. Quindi scriviamo un po 'di codice come questo (usando Unity come esempio):

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    // Yaw around the y axis using the player's horizontal input.        
    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f);

    // Pitch around the x axis using the player's vertical input.
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f);
}

o forse

// Construct a quaternion or a matrix representing incremental camera rotation.
Quaternion rotation = Quaternion.Euler(
                        -Input.GetAxis("Vertical") * speed,
                         Input.GetAxis("Horizontal") * speed,
                         0);

// Fold this change into the camera's current rotation.
transform.rotation *= rotation;

E funziona principalmente, ma col tempo la vista inizia a diventare storta. Sembra che la fotocamera stia ruotando il suo asse di rollio (z) anche se le abbiamo detto di ruotare solo su xey!

Esempio animato di una videocamera in prima persona inclinata lateralmente

Questo può accadere anche se stiamo cercando di manipolare un oggetto davanti alla telecamera - diciamo che è un globo che vogliamo girare per guardarci attorno:

Esempio animato di un globo che si inclina lateralmente

Lo stesso problema: dopo un po 'il polo nord inizia a vagare a sinistra oa destra. Stiamo dando input su due assi ma stiamo ottenendo questa rotazione confusa su un terzo. E succede se applichiamo tutte le nostre rotazioni attorno agli assi locali dell'oggetto o agli assi globali del mondo.

In molti motori lo vedrai anche nell'ispettore: ruota l'oggetto nel mondo e improvvisamente i numeri cambiano su un asse che non abbiamo nemmeno toccato!

Animated example showing manipulating an object next to a readout of its rotation angles. The z angle changes even though the user didn't manipulate that axis.

Quindi, si tratta di un bug del motore? Come facciamo a dire al programma che non lo vogliamo aggiungendo una rotazione extra?

Ha qualcosa a che fare con gli angoli di Eulero? Dovrei usare invece Quaternioni o Matrici di rotazione o Vettori di base?



1
Ho trovato molto utile anche la risposta alla seguente domanda. gamedev.stackexchange.com/questions/123535/…
Travis Pettry,

Risposte:


51

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:

Esempio animato che mostra che l'applicazione di rotazioni in un ordine diverso produce risultati diversi

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!

Un esempio animato che mostra una sequenza di pitch-yaw-pitch locale fornisce lo stesso output di un singolo roll locale

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

Esempio animato di una tazza che ruota con inclinazione locale e imbardata globale, senza problemi di rollioEsempio animato di videocamera in prima persona con imbardata globale

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 totalPitchbloccato):

// 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 ...)

Esempio animato di un globo che mostra una rotazione meglio comportata

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.


1
Posso chiederti come hai fatto alle gif? Hanno un bell'aspetto.
Bálint,

1
@ Bálint Uso un programma gratuito chiamato LICEcap - ti permette di registrare una parte del tuo schermo in una gif. Quindi taglio l'animazione / aggiungo testo aggiuntivo per la didascalia / comprimo il GIF usando Photoshop.
DMGregory

Questa risposta è solo per Unity? Esiste una risposta più generica sul Web?
posfan12,

2
@ posfan12 Il codice di esempio utilizza la sintassi Unity per concretezza, ma le situazioni e la matematica coinvolte si applicano allo stesso modo su tutta la linea. Stai riscontrando difficoltà nell'applicazione degli esempi al tuo ambiente?
DMGregory

2
La matematica è già presente sopra. A giudicare dalla tua modifica, stai solo facendo a pezzi le parti sbagliate. Sembra che tu stia utilizzando il tipo di vettore definito qui . Ciò include un metodo per costruire una matrice di rotazione da una serie di angoli come descritto sopra. Inizia con una matrice di identità e chiama TCVector::calcRotationMatrix(totalPitch, totalYaw, identity)- questo equivale all'esempio "tutto in una volta Eulero" sopra.
DMGregory

1

DOPO 16 ore di funzioni genitoriali e di rotazione lol.

Volevo solo che il codice avesse un vuoto, fondamentalmente, girando in cerchi e anche girando in avanti mentre gira.

O in altre parole, l'obiettivo è ruotare l'imbardata e il beccheggio senza alcun effetto sul rollio.

Ecco i pochi che hanno funzionato nella mia applicazione

In Unity:

  1. Crea un vuoto. Chiamalo ForYaw.
  2. Dagli un bambino vuoto, chiamato ForPitch.
  3. Infine, crea un cubo e spostalo su z = 5 (avanti). Ora possiamo vedere cosa stiamo cercando di evitare, che torcerà il cubo ("rollio" accidentale mentre imbardiamo e incliniamo) .

Ora esegui gli script in Visual Studio, esegui la configurazione e finisci con questo in Aggiornamento:

objectForYaw.Rotate(objectForYaw.up, 1f, Space.World);
    objectForPitch.Rotate(objectForPitch.right, 3f, Space.World);

    //this will work on the pitch object as well
    //objectForPitch.RotateAround
    //    (objectForPitch.position, objectForPitch.right, 3f);

Nota che tutto si è rotto per me quando ho provato a variare l'ordine dei genitori. O se cambio Space.World per l' oggetto pitch figlio (al genitore Yaw non dispiace essere ruotato attraverso Space.Self). Confondere. Potrei sicuramente usare alcuni chiarimenti sul perché ho dovuto prendere un asse locale ma applicarlo attraverso lo spazio mondiale - perché Space.Self rovina tutto?


Non mi è chiaro se questo sia destinato a rispondere alla domanda o se stai chiedendo aiuto per capire perché il codice che hai scritto funziona in questo modo. Se è quest'ultimo, dovresti pubblicare una nuova domanda, collegandoti a questa pagina per il contesto se vuoi. Come suggerimento, me.Rotate(me.right, angle, Space.World)è lo stesso di me.Rotate(Vector3.right, angle, Space.Self, e le tue trasformazioni nidificate sono equivalenti agli esempi "Pitch Locally, Yaw Globally" nella risposta accettata.
DMGregory

Un po 'di entrambi. In parte per condividere la risposta (ad esempio qui è esattamente come ha funzionato per me). E anche per presentare la domanda. Quel suggerimento mi ha fatto pensare. Molte grazie!
Jeh,

Incredibile, questo non è stato votato. Dopo ore a leggere risposte complete che, sebbene educative non funzionassero ancora, la semplice risposta dell'uso dell'asse di rotazione degli oggetti anziché di un asse Vector3.right generico è tutto ciò che è stato necessario per mantenere la rotazione fissa sull'oggetto. Incredibile quello che stavo cercando è stato sepolto qui con 0 voti e sulla 2a pagina di Google di molte, molte domande simili.
user4779
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.