Não, isso não é um bug do mecanismo ou um artefato de uma representação de rotação específica (isso também pode acontecer, mas esse efeito se aplica a todos os sistemas que representam rotações, inclusive quaterniões).
Você descobriu um fato real sobre como a rotação funciona no espaço tridimensional e parte de nossa intuição sobre outras transformações, como a tradução:
![Exemplo animado mostrando que a aplicação de rotações em uma ordem diferente fornece resultados diferentes](https://i.stack.imgur.com/q0lJy.gif)
Quando compomos rotações em mais de um eixo, o resultado obtido não é apenas o valor total / líquido que aplicamos a cada eixo (como seria de esperar para a tradução). A ordem em que aplicamos as rotações altera o resultado, à medida que cada rotação move os eixos nos quais as próximas rotações são aplicadas (se estiver girando sobre os eixos locais do objeto) ou a relação entre o objeto e o eixo (se estiver girando sobre o mundo) eixos).
A mudança das relações dos eixos ao longo do tempo pode confundir nossa intuição sobre o que cada eixo deve "fazer". Em particular, certas combinações de rotação de guinada e inclinação produzem o mesmo resultado que uma rotação de rolagem!
![Exemplo animado que mostra uma sequência de pitch-yaw-pitch local fornece a mesma saída que um único rolo local](https://i.stack.imgur.com/So2HM.gif)
Você pode verificar se cada etapa está girando corretamente em torno do eixo que solicitamos - não há falhas ou artefatos no motor em nossa notação que interfiram ou adivinhem nossa entrada - a natureza esférica (ou hiperesférica / quaternária) da rotação significa apenas as transformações " ao redor "um sobre o outro. Eles podem ser ortogonais localmente, para pequenas rotações, mas à medida que se acumulam, descobrimos que não são ortogonais globalmente.
Isso é mais dramático e claro em curvas de 90 graus, como as anteriores, mas os eixos errantes também se arrastam por muitas pequenas rotações, como demonstrado na pergunta.
Então, o que fazemos sobre isso?
Se você já possui um sistema de rotação de inclinação da guinada, uma das maneiras mais rápidas de eliminar o rolo indesejado é alterar uma das rotações para operar nos eixos de transformação global ou pai, em vez dos eixos locais do objeto. Dessa forma, você não pode obter contaminação cruzada entre os dois - um eixo permanece absolutamente controlado.
Aqui está a mesma sequência de pitch-yaw-pitch que se tornou um exemplo no exemplo acima, mas agora aplicamos nossa guinada ao redor do eixo Y global em vez da do objeto
![Exemplo animado de caneca rodando com pitch local e guinada global, sem problemas de rolagem](https://i.stack.imgur.com/dadM6.gif)
![Exemplo animado de câmera em primeira pessoa usando guinada global](https://i.stack.imgur.com/QJRYD.gif)
Assim, podemos consertar a câmera em primeira pessoa com o 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 você estiver compondo suas rotações usando a multiplicação, inverta a ordem esquerda / direita de uma das multiplicações para obter o mesmo efeito:
// 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.
(A ordem específica dependerá das convenções de multiplicação em seu ambiente, mas esquerda = mais global / direita = mais local é uma escolha comum)
Isso equivale a armazenar a guinada total líquida e a inclinação total que você deseja como variáveis de flutuação, aplicando sempre o resultado líquido de uma só vez, construindo uma única nova orientação ou matriz de orientação a partir desses ângulos (desde que você mantenha-se totalPitch
preso):
// 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 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 essa divisão global / local, as rotações não têm chance de se compor e influenciar uma à outra, porque são aplicadas a conjuntos independentes de eixos.
A mesma idéia pode ajudar se for um objeto no mundo que queremos rotacionar. Para um exemplo como o globo, muitas vezes queremos invertê-lo e aplicar nossa guinada localmente (para que ela sempre gire em torno de seus polos) e incline-se globalmente (para que se incline em direção à nossa visão, em vez de na Austrália. , onde quer que esteja apontando ...)
![Exemplo animado de um globo mostrando rotação com melhor comportamento](https://i.stack.imgur.com/L68j6.gif)
Limitações
Essa estratégia híbrida global / local nem sempre é a solução correta. Por exemplo, em um jogo com vôo / natação em 3D, convém apontar para cima / para baixo e ainda ter controle total. Mas com esta configuração, você apertará o bloqueio do cardan - seu eixo de guinada (ascendente global) se torna paralelo ao seu eixo de rolagem (local para a frente) e você não tem como olhar para a esquerda ou direita sem torcer.
O que você pode fazer em casos como esse é usar rotações locais puras, como começamos na pergunta acima (para que seus controles pareçam o mesmo, não importa para onde você esteja olhando), o que inicialmente permitirá que alguns movimentos entrem em ação - mas depois nós corrigimos isso.
Por exemplo, podemos usar rotações locais para atualizar nosso vetor "forward" e, em seguida, usar esse vetor forward junto com um vetor "up" de referência para construir nossa orientação final. (Usando, por exemplo, o Quaternion da Unity. LookRotation método , ou construindo manualmente uma matriz ortonormal a partir desses vetores). Controlando o vetor up, controlamos o roll ou twist.
Para o exemplo de voo / natação, convém aplicar essas correções gradualmente ao longo do tempo. Se for muito abrupto, a vista pode mudar de uma maneira perturbadora. Em vez disso, você pode usar o vetor atual do player e apontá-lo para a vertical, quadro a quadro, até que a visualização seja nivelada. Aplicar isso durante um turno às vezes pode ser menos nauseante do que girar a câmera enquanto os controles do player estão ociosos.