Je vois des questions souvent soulevées qui ont ce problème sous-jacent, mais elles sont toutes liées aux particularités d'une fonctionnalité ou d'un outil donné. Voici une tentative pour créer une réponse canonique à laquelle nous pouvons renvoyer les utilisateurs lorsque cela se présentera - avec de nombreux exemples animés! :)
Disons que nous fabriquons une caméra à la première personne. L'idée de base est qu'il faut regarder en l'air de gauche à droite et de haut en bas. Nous écrivons donc un peu de code comme ceci (en utilisant Unity comme exemple):
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);
}
ou peut-être
// 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;
Et cela fonctionne généralement, mais avec le temps, la vue commence à être tordue. La caméra semble tourner sur son axe de roulis (z) même si nous lui avons seulement dit de tourner sur le x et le y!
Cela peut aussi arriver si nous essayons de manipuler un objet devant la caméra - disons que c'est un globe terrestre que nous voulons tourner pour regarder autour de nous:
Le même problème - après un moment, le pôle Nord commence à s’éloigner à gauche ou à droite. Nous donnons notre avis sur deux axes, mais nous obtenons cette rotation confuse sur un troisième. Et cela arrive que nous appliquions toutes nos rotations autour des axes locaux de l'objet ou des axes globaux du monde.
L'inspecteur le voit également dans de nombreux moteurs: faites pivoter l'objet dans le monde et tout à coup, les chiffres changent sur un axe que nous n'avons même pas touché!
Alors, est-ce un bug moteur? Comment pouvons-nous dire au programme que nous ne voulons pas qu'il ajoute une rotation supplémentaire?
Cela a-t-il quelque chose à voir avec les angles d'Euler? Devrais-je utiliser plutôt des quaternions ou des matrices de rotation ou des vecteurs de base?
la source
Réponses:
Non, ce n'est pas un bug de moteur ou un artefact d'une représentation de rotation particulière (cela peut arriver aussi, mais cet effet s'applique à tout système représentant des rotations, quaternions inclus).
Vous avez découvert un fait réel sur le fonctionnement de la rotation dans un espace tridimensionnel, et cela nous écarte de notre intuition concernant d'autres transformations telles que la traduction:
Lorsque nous composons des rotations sur plusieurs axes, le résultat obtenu ne correspond pas seulement à la valeur totale / nette que nous avons appliquée à chaque axe (comme on pourrait s'y attendre pour la traduction). L'ordre dans lequel nous appliquons les rotations change le résultat, chaque rotation déplaçant les axes sur lesquels les rotations suivantes sont appliquées (si elles tournent autour des axes locaux de l'objet), ou la relation entre l'objet et l'axe (si elles tournent autour du monde). axes).
Le changement des relations des axes au fil du temps peut confondre notre intuition sur ce que chaque axe est "supposé" faire. En particulier, certaines combinaisons de rotations en lacet et en tangage donnent le même résultat qu'une rotation de roulis!
Vous pouvez vérifier que chaque étape tourne correctement autour de l’axe que nous avons demandé - il n’existe pas de problème moteur ni d’artefact dans notre notation qui interfère avec ou nous remet en question notre entrée - la nature sphérique (ou hypersphérique / quaternion) de la rotation signifie simplement que nos transformations "wrap" autour "les uns sur les autres. Ils peuvent être orthogonaux localement, pour de petites rotations, mais à mesure qu'ils s'empilent, nous constatons qu'ils ne sont pas globalement orthogonaux.
Ceci est particulièrement dramatique et évident pour les virages à 90 degrés comme ceux ci-dessus, mais les axes errants se glissent également dans de nombreuses petites rotations, comme le montre la question.
Alors, que faisons-nous à ce sujet?
Si vous disposez déjà d'un système de rotation en tangage, l'un des moyens les plus rapides d'éliminer les roulements non désirés consiste à modifier l'une des rotations afin qu'elle fonctionne sur les axes de transformation global ou parent au lieu des axes locaux de l'objet. De cette façon, vous ne pouvez pas avoir de contamination croisée entre les deux - un axe reste absolument contrôlé.
Voici la même séquence de pitch-yaw-pitch qui est devenue un roulement dans l'exemple ci-dessus, mais maintenant nous appliquons notre lacet autour de l'axe Y global au lieu de celui de l'objet
Ainsi, nous pouvons réparer la caméra à la première personne avec le mantra "Pitch Locally, Yaw Globally":
Si vous composez vos rotations en utilisant la multiplication, vous devez inverser l'ordre gauche / droite de l'une des multiplications pour obtenir le même effet:
(L'ordre spécifique dépendra des conventions de multiplication de votre environnement, mais gauche = plus global / droit = plus local est un choix courant)
Cela revient à stocker le lacet total net et le pas total souhaité en tant que variables float, puis à appliquer le résultat net en une seule fois, en construisant un seul quaternion ou matrice d'orientation à partir de ces angles uniquement (à condition que vous restiez
totalPitch
bloqué):ou équivalent...
En utilisant cette division globale / locale, les rotations n'ont aucune chance de se combiner et de s'influencer, car elles sont appliquées à des ensembles d'axes indépendants.
La même idée peut aider si c'est un objet du monde que nous voulons faire pivoter. Pour un exemple comme le globe terrestre, nous voudrions souvent l’inverser et appliquer notre lacet localement (pour qu’il tourne toujours autour de ses pôles) et globalement (pour qu’il bascule vers notre vue, plutôt que vers l’Australie. , partout où il pointe ...)
Limites
Cette stratégie hybride globale / locale n'est pas toujours la bonne solution. Par exemple, dans un jeu avec vol / natation 3D, vous pouvez vouloir être en mesure de pointer tout droit vers le haut et toujours tout en gardant le contrôle total. Mais avec cette configuration, vous obtiendrez un blocage du cardan : votre axe de lacet (global) deviendra parallèle à votre axe de roulis (local avant) et vous ne pourrez pas regarder à gauche ou à droite sans torsion.
Ce que vous pouvez faire à la place dans des cas comme celui-ci est d’utiliser des rotations locales pures comme dans la question précédente (pour que vos commandes aient la même apparence, peu importe l’endroit où vous regardez). nous corrigeons pour cela.
Par exemple, nous pouvons utiliser des rotations locales pour mettre à jour notre vecteur "avant", puis utiliser ce vecteur avant avec un vecteur de référence "haut" pour construire notre orientation finale. (En utilisant, par exemple, la méthode Quaternion.LookRotation de Unity ou en construisant manuellement une matrice orthonormée à partir de ces vecteurs) En contrôlant le vecteur haut, nous contrôlons le roulis ou la torsion.
Pour l'exemple de vol / natation, vous souhaiterez appliquer ces corrections progressivement. Si la vue est trop abrupte, la vue peut basculer de manière distrayante. Au lieu de cela, vous pouvez utiliser le vecteur de mise à jour actuel du lecteur et l'indiquer vers la verticale, image par image, jusqu'à ce que la vue soit de niveau. Cela peut parfois être moins nauséeux que de tourner la caméra lorsque les commandes du joueur sont inactives.
la source
TCVector::calcRotationMatrix(totalPitch, totalYaw, identity)
- c’est l'équivalent de l'exemple "tout à la fois d'Euler" ci-dessus.APRES 16 heures de fonctions parentales et de rotation lol.
Je voulais juste que le code ait un vide, en gros, en tournant en rond et aussi en se retournant avant.
Ou, en d'autres termes, l'objectif est de faire pivoter le lacet et le tangage sans aucun effet sur le roulis.
Voici les quelques qui ont réellement fonctionné dans mon application
En unité:
A présent, la création de scripts dans Visual Studio, faites votre configuration et terminez avec ceci dans Update:
Notez que tout s'est cassé pour moi lorsque j'ai essayé de modifier l'ordre des responsabilités parentales. Ou si je change Space.World pour le objet pitch enfant (la rotation de Yaw parent ne dérange pas d'être pivotée dans Space.Self). Déroutant. Je pourrais certainement utiliser des éclaircissements sur la raison pour laquelle je devais prendre un axe local mais l’appliquer à travers l’espace mondial - pourquoi Space.Self gâte-t-il tout?
la source
me.Rotate(me.right, angle, Space.World)
est identique àme.Rotate(Vector3.right, angle, Space.Self
, et vos transformations imbriquées sont équivalentes aux exemples "Pitch Locally, Yaw Globally" de la réponse acceptée.