Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Usando transformadores 3D

Introdução

Se você nunca fez jogos 3D antes, trabalhar com rotações em três dimensões pode ser confuso no início. Vindo de 2D, a coisa natural é ter um raciocínio tipo "Ah, é como girar em 2D, exceto que agora as rotações acontecem em X, Y e Z".

A princípio, isso parece fácil. Para jogos simples, essa forma de pensar pode até ser suficiente. Infelizmente, muitas vezes é incorreto.

Ângulos em três dimensões são mais comumente referidos como "Ângulos de Euler".

../../_images/transforms_euler.png

Os ângulos de Euler foram introduzidos pelo matemático Leonhard Euler no início do século XVIII.

../../_images/transforms_euler_himself.png

Essa forma de representar as rotações 3D foi inovadora na época, mas apresenta várias deficiências quando usada no desenvolvimento de jogos (o que é de se esperar de um cara com um chapéu engraçado). A ideia deste documento é explicar o porquê, bem como descrever as melhores práticas para lidar com transformações ao programar jogos 3D.

Problemas dos ângulos de Euler

Embora possa parecer intuitivo que cada eixo tenha uma rotação, a verdade é que não é prático.

Ordem dos eixos

A principal razão para isso é que não existe uma maneira única de construir uma orientação a partir dos ângulos. Não existe uma função matemática padrão que junte todos os ângulos e produza uma rotação 3D real. A única maneira de produzir uma orientação a partir de ângulos é girar o objeto ângulo por ângulo, em uma ordem arbitrária.

Isso pode ser feito pela primeira vez girando em X, então Y e depois em Z. Alternativamente, você pode primeiro girar em Y, então em Z e finalmente em X. Tudo funciona, mas dependendo da ordem, a orientação final do objeto não será necessariamente a mesma . De fato, isso significa que existem várias maneiras de construir uma orientação de 3 ângulos diferentes, dependendo da *ordem das rotações.

A seguir está uma visualização de eixos de rotação (em X, Y, Z) em um "gimbal" (da Wikipedia). Como você pode ver, a orientação de cada eixo depende da rotação do anterior:

../../_images/transforms_gimbal.gif

Você pode estar se perguntando como isso te afeta. Vejamos um exemplo prático:

Imagine que você está trabalhando em um controle de primeira pessoa (por exemplo, num jogo FPS). Movendo o mouse para a esquerda e para a direita controla seu ângulo de visão paralelo ao chão, enquanto move-o para cima e para baixo move a vista do jogador para cima e para baixo.

Neste caso, para alcançar o efeito desejado, a rotação deve ser aplicada primeiro no eixo Y ("para cima" neste caso, uma vez que Godot usa uma orientação "Y-Up"), seguida pela rotação no eixo X.

../../_images/transforms_rotate1.gif

Se tivéssemos que aplicar rotação no eixo X primeiro, e então no Y, o efeito não seria o desejado:

../../_images/transforms_rotate2.gif

Dependendo do tipo de jogo ou efeito desejado, a ordem em que você deseja que as rotações dos eixos sejam aplicadas pode ser diferente. Portanto, aplicar rotações em X, Y e Z não é suficiente: você também precisa de uma ordem de rotação.

Interpolação

Outro problema com o uso de ângulos de Euler é a interpolação. Imagine que você deseja fazer a transição entre duas posições diferentes de câmera ou inimigo (incluindo rotações). Uma maneira lógica de abordar isso é interpolar os ângulos de uma posição para a outra. Seria de esperar que ficasse assim:

../../_images/transforms_interpolate1.gif

Mas isso nem sempre tem o efeito esperado ao usar ângulos:

../../_images/transforms_interpolate2.gif

A câmera realmente girou na direção oposta!

Há algumas razões para que isto tenha acontecido:

  • As rotações não mapeiam linearmente a orientação, portanto interpolá-las nem sempre resulta no caminho mais curto (ou seja, ir de 270 a 0 graus não é o mesmo que ir de 270 a 360, mesmo que os ângulos sejam equivalentes).

  • O gimbal lock está em jogo (o primeiro e o último eixo girado se alinham, portanto, um grau de liberdade é perdido). Veja a `página da Wikipedia sobre Gimbal Lock <https://pt.wikipedia.org/wiki/Gimbal_lock> `_ para uma explicação detalhada deste problema.

Diga não aos ângulos de Euler

The result of all this is that you should not use the rotation property of Node3D nodes in Godot for games. It's there to be used mainly in the editor, for coherence with the 2D engine, and for simple rotations (generally just one axis, or even two in limited cases). As much as you may be tempted, don't use it.

Em vez disso, há uma maneira melhor de resolver seus problemas de rotação.

Introduzindo transformadores

Godot uses the Transform3D datatype for orientations. Each Node3D node contains a transform property which is relative to the parent's transform, if the parent is a Node3D-derived type.

Também é possível acessar as coordenadas de transformação do mundo através da propriedade global_transform.

Um transform tem um Basis (transform.basis sub-property), que consiste em três vetores Vector3. Estes são acessados através da propriedade transform.basis e podem ser acessados diretamente por transform.basis.x, transform.basis.y`, e`transform.basis.z`. Cada ponto vetorial na direção que seu eixo foi girado, então eles efetivamente descrevem a rotação total do nó. A escala (embora seja uniforme) também pode ser inferida a partir do comprimento dos eixos. Uma basis também pode ser interpretada como uma matriz 3x3 e usada como transform.basis[x][y]`.

Uma base padrão (não modificada) é semelhante a:

var basis = Basis()
# Contains the following default values:
basis.x = Vector3(1, 0, 0) # Vector pointing along the X axis
basis.y = Vector3(0, 1, 0) # Vector pointing along the Y axis
basis.z = Vector3(0, 0, 1) # Vector pointing along the Z axis

Isto também é similar a uma matriz de identidade 3x3.

Seguindo a convenção OpenGL, X é o eixo Right, Y é o eixo Up e Z é o eixo Forward.

Juntamente com a base, uma transformação também tem uma origem. Este é um Vector3 especificando quão longe da origem real (0, 0, 0) esta transformação é. Combinando a base com a origem, um transform representa eficientemente uma única translação, rotação e escala no espaço.

../../_images/transforms_camera.png

Uma maneira de visualizar uma transformação é olhar o gizmo 3D de um objeto enquanto está no modo "espaço local".

../../_images/transforms_local_space.png

As setas do gizmo mostram os eixos X, Y, e Z (em vermelho, verde, e azul respectivamente) da base, enquanto o centro do gizmo está na origem do objeto.

../../_images/transforms_gizmo.png

Para mais informações sobre a matemática de vetores e transformações, leia os tutoriais Matemática vetorial.

Manipulação de transformadores

É claro que as transformações não são tão simples de manipular quanto os ângulos e têm problemas próprios.

É possível girar uma transformação, seja multiplicando sua base por outra (isso é chamado de acumulação), ou usando os métodos de rotação.

var axis = Vector3(1, 0, 0) # Or Vector3.RIGHT
var rotation_amount = 0.1
# Rotate the transform around the X axis by 0.1 radians.
transform.basis = Basis(axis, rotation_amount) * transform.basis
# shortened
transform.basis = transform.basis.rotated(axis, rotation_amount)

A method in Node3D simplifies this:

# Rotate the transform around the X axis by 0.1 radians.
rotate(Vector3(1, 0, 0), 0.1)
# shortened
rotate_x(0.1)

Isso gira o nó em relação ao nó pai.

Para girar em relação ao espaço do objeto (a própria transformação do nó), use o seguinte:

# Rotate around the object's local X axis by 0.1 radians.
rotate_object_local(Vector3(1, 0, 0), 0.1)

Erros de precisão

Fazer operações sucessivas de transformação resultará em uma perda de precisão devido a erro de ponto flutuante. Isto significa que a escala de cada eixo pode não ser mais exatamente 1.0, e podem não ser exatamente 90 graus um do outro.

Se uma transformação for girada a cada quadro, ela eventualmente começará a se deformar com o tempo. Isto é inevitável.

Existem duas maneiras diferentes de lidar com isso. A primeira é ortonormalizar a transformação depois de algum tempo (talvez uma vez por quadro, se você modificá-la a cada quadro):

transform = transform.orthonormalized()

Isto fará com que todos os eixos tenham 1.0 de comprimento de novo e estejam 90 graus um do outro. Entretanto, qualquer escala aplicada à transformação será perdida.

It is recommended you not scale nodes that are going to be manipulated; scale their children nodes instead (such as MeshInstance3D). If you absolutely must scale the node, then re-apply it at the end:

transform = transform.orthonormalized()
transform = transform.scaled(scale)

Obtendo informação

Você pode estar pensando neste ponto: "Tá bom, mas como obter ângulos de uma transformação?". A resposta novamente é: você não tem. Você deve fazer o seu melhor para parar de pensar em ângulos.

Imagine que você precisa atirar uma bala na direção que seu jogador está virado. Basta utilizar o eixo dianteiro (geralmente Z ou -Z).

bullet.transform = transform
bullet.speed = transform.basis.z * BULLET_SPEED

O inimigo está olhando para o jogador? Utilize o produto escalar para isso (veja o tutorial Matemática vetorial para uma explicação do produto escalar):

# Get the direction vector from player to enemy
var direction = enemy.transform.origin - player.transform.origin
if direction.dot(enemy.transform.basis.z) > 0:
    enemy.im_watching_you(player)

Desviar à esquerda:

# Remember that +X is right
if Input.is_action_pressed("strafe_left"):
    translate_object_local(-transform.basis.x)

Pulo:

# Keep in mind Y is up-axis
if Input.is_action_just_pressed("jump"):
    velocity.y = JUMP_SPEED

move_and_slide()

Todos os comportamentos e lógicas comuns podem ser feitos apenas com vetores.

Definindo informações

Há, é claro, casos em que você quer definir informações para uma transformação. Imagine um controle em primeira pessoa ou uma câmera orbitando. Estes são definitivamente feitos usando ângulos, porque você quer que as transformações aconteçam em uma ordem específica.

For such cases, keep the angles and rotations outside the transform and set them every frame. Don't try to retrieve and reuse them because the transform is not meant to be used this way.

Exemplo de olhar ao redor, estilo FPS:

# accumulators
var rot_x = 0
var rot_y = 0

func _input(event):
    if event is InputEventMouseMotion and event.button_mask & 1:
        # modify accumulated mouse rotation
        rot_x += event.relative.x * LOOKAROUND_SPEED
        rot_y += event.relative.y * LOOKAROUND_SPEED
        transform.basis = Basis() # reset rotation
        rotate_object_local(Vector3(0, 1, 0), rot_x) # first rotate in Y
        rotate_object_local(Vector3(1, 0, 0), rot_y) # then rotate in X

Como você pode ver, em tais casos é ainda mais simples manter a rotação do lado de fora, então use o transform como a orientação final.

Interpolando com quaternions

A interpolação entre duas transformações pode ser feita eficientemente com quaternions. Mais informações sobre como funcionam os quaternions podem ser encontradas em outros lugares ao redor da Internet. Para uso prático, é suficiente entender que praticamente seu principal uso é fazer uma interpolação do caminho mais próximo. Assim como em duas rotações, um quaternion permitirá a interpolação entre eles usando o eixo mais próximo.

Converter uma rotação para quaternion é simples.

# Convert basis to quaternion, keep in mind scale is lost
var a = Quaternion(transform.basis)
var b = Quaternion(transform2.basis)
# Interpolate using spherical-linear interpolation (SLERP).
var c = a.slerp(b,0.5) # find halfway point between a and b
# Apply back
transform.basis = Basis(c)

The Quaternion type reference has more information on the datatype (it can also do transform accumulation, transform points, etc., though this is used less often). If you interpolate or apply operations to quaternions many times, keep in mind they need to be eventually normalized. Otherwise, they will also suffer from numerical precision errors.

Quaternions são úteis ao fazer interpolações de câmera/percurso/etc., pois o resultado será sempre correto e suave.

Transforms são seus amigos

Para a maioria dos iniciantes, acostumar-se a trabalhar com transforms pode levar algum tempo. No entanto, uma vez acostumado a eles, você vai apreciar sua simplicidade e poder.

Não hesite em pedir ajuda sobre este tópico em qualquer uma das comunidades online da Godot ` <https://godotengine.org/community>`_ e, assim que você se tornar confiante o suficiente, por favor, ajude outros!