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":
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);
}
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:
// 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'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é):
// 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;
ou équivalent...
// 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;
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.