No, esto no es un error del motor o un artefacto de una representación de rotación en particular (también puede ocurrir, pero este efecto se aplica a todos los sistemas que representan rotaciones, incluidos los cuaterniones).
Has descubierto un hecho real sobre cómo funciona la rotación en el espacio tridimensional, y parte de nuestra intuición sobre otras transformaciones como la traducción:
Cuando componimos rotaciones en más de un eje, el resultado que obtenemos no es solo el valor total / neto que aplicamos a cada eje (como podríamos esperar para la traducción). El orden en el que aplicamos las rotaciones cambia el resultado, ya que cada rotación mueve los ejes en los que se aplican las siguientes rotaciones (si gira alrededor de los ejes locales del objeto), o la relación entre el objeto y el eje (si gira alrededor del mundo ejes).
El cambio de las relaciones de los ejes a lo largo del tiempo puede confundir nuestra intuición sobre lo que se supone que debe hacer cada eje. En particular, ciertas combinaciones de rotaciones de guiñada y cabeceo dan el mismo resultado que una rotación de balanceo.
Puede verificar que cada paso gire correctamente sobre el eje que solicitamos: no hay fallas o artefactos en nuestra notación que interfieran o cuestionen nuestra entrada; la naturaleza de rotación esférica (o hiperesférica / cuaternión) solo significa que nuestras transformaciones se "ajustan" alrededor "el uno del otro. Pueden ser ortogonales localmente, para pequeñas rotaciones, pero a medida que se acumulan, descubrimos que no son ortogonales a nivel mundial.
Esto es más dramático y claro para giros de 90 grados como los anteriores, pero los ejes errantes también se arrastran en muchas pequeñas rotaciones, como se demuestra en la pregunta.
¿Entonces qué hacemos al respecto?
Si ya tiene un sistema de rotación pitch-yaw, una de las formas más rápidas de eliminar el giro no deseado es cambiar una de las rotaciones para operar en los ejes de transformación global o padre en lugar de los ejes locales del objeto. De esa manera no puede contaminarse entre los dos: un eje permanece absolutamente controlado.
Aquí está la misma secuencia de pitch-yaw-pitch que se convirtió en un rollo en el ejemplo anterior, pero ahora aplicamos nuestro guiñada alrededor del eje Y global en lugar del objeto
Entonces podemos arreglar la cámara en primera persona con el 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 estás combinando tus rotaciones usando la multiplicación, cambiarías el orden izquierdo / derecho de una de las multiplicaciones para obtener el mismo efecto:
// 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.
(El orden específico dependerá de las convenciones de multiplicación en su entorno, pero izquierda = más global / derecha = más local es una opción común)
Esto es equivalente a almacenar el guiñada total neta y el tono total que desee como variables flotantes, luego aplicar siempre el resultado neto de una vez, construyendo un solo cuaternión o matriz de orientación nueva solo desde estos ángulos (siempre que lo mantenga totalPitch
sujeto):
// 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 equivalente...
// 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 esta división global / local, las rotaciones no tienen la oportunidad de combinarse e influenciarse entre sí, porque se aplican a conjuntos de ejes independientes.
La misma idea puede ayudar si es un objeto en el mundo que queremos rotar. Para un ejemplo como el mundo, a menudo queremos invertirlo y aplicar nuestro guiñada localmente (por lo que siempre gira alrededor de sus polos) y lanzar globalmente (por lo que se inclina hacia / lejos de nuestra vista, en lugar de hacia / lejos de Australia , donde sea que apunte ...)
Limitaciones
Esta estrategia híbrida global / local no siempre es la solución correcta. Por ejemplo, en un juego con vuelo / natación en 3D, es posible que desee apuntar hacia arriba / hacia abajo y aún así tener el control total. Pero con esta configuración, golpeará el bloqueo del cardán : su eje de guiñada (global hacia arriba) se vuelve paralelo a su eje de balanceo (local hacia adelante), y no tiene forma de mirar hacia la izquierda o hacia la derecha sin girar.
Lo que puede hacer en casos como este es usar rotaciones locales puras como comenzamos en la pregunta anterior (para que sus controles se sientan igual sin importar dónde esté mirando), lo que inicialmente permitirá que algo de rollo se arrastre, pero luego corregimos por ello.
Por ejemplo, podemos usar rotaciones locales para actualizar nuestro vector "hacia adelante", luego usar ese vector hacia adelante junto con un vector "hacia arriba" de referencia para construir nuestra orientación final. (Usando, por ejemplo, Unity's Quaternion.LookRotation método , o construyendo manualmente una matriz ortonormal a partir de estos vectores) Al controlar el vector hacia arriba, controlamos el giro o giro.
Para el ejemplo de vuelo / natación, querrá aplicar estas correcciones gradualmente con el tiempo. Si es demasiado abrupto, la vista puede tambalearse de manera distractora. En su lugar, puede usar el vector ascendente actual del jugador e insinuarlo hacia la vertical, cuadro por cuadro, hasta que su vista se nivele. Aplicar esto durante un turno a veces puede ser menos nauseabundo que girar la cámara mientras los controles del jugador están inactivos.