Parte: 3 — Física y matemáticas de juegos aplicadas · Fuente: Godot Engine 4.x — Using 3D transforms y clase Quaternion ⏱️ Duración estimada: 60 min · Nivel: Intermedio
Entender por qué las rotaciones con ángulos de Euler fallan (gimbal lock) y cómo los cuaterniones las resuelven. Trabajaremos la intuición, no el álgebra pesada: te bastará saber crear un cuaternión a partir de un eje y un ángulo, componer rotaciones e interpolar suavemente entre orientaciones con slerp. Usaremos Quaternion y Basis de Godot 4.
Al finalizar, el alumno podrá:
Quaternion(eje, angulo).slerp para lograr giros suaves.Quaternion y Basis, y aplicar el resultado al transform de un nodo 3D.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Rotaciones con Euler | Es lo intuitivo, pero tiene una trampa |
| 2 | Gimbal lock | Bloquea un eje y arruina cámaras y naves |
| 3 | Intuición del cuaternión | Eje de giro + cantidad de giro |
| 4 | Construcción eje-ángulo | Forma más práctica de crearlos |
| 5 | Composición por multiplicación | Encadenar rotaciones sin acumular error |
| 6 | Interpolación con slerp | Giros suaves de cámara y personajes |
| 7 | Quaternion ↔ Basis en Godot | Aplicar la rotación al mundo 3D |
Quaternion(eje_normalizado, angulo_rad). Clave: el eje debe estar normalizado.q2 * q1 aplica primero q1; el orden importa.Quaternion y Basis son intercambiables en Godot..normalized() tras muchas operaciones.Usa Godot 4.x con un proyecto 3D. Crea una escena con un Node3D raíz, un MeshInstance3D (una caja o cápsula sirve para ver la orientación) y una cámara. Consulta la referencia de Quaternion y el tutorial Using 3D transforms. El giro será lo observable: verás la malla interpolar suavemente entre dos poses.
Rotaremos una malla suavemente entre dos orientaciones y luego demostraremos el problema de Euler.
Paso 1 — Definir dos orientaciones con eje-ángulo. Adjunta este script al MeshInstance3D.
extends MeshInstance3D
var q_inicio: Quaternion
var q_fin: Quaternion
var t := 0.0
func _ready() -> void:
# Orientación inicial: sin rotar.
q_inicio = Quaternion.IDENTITY
# Orientación final: 120° alrededor de un eje diagonal.
var eje := Vector3(1, 1, 0).normalized()
q_fin = Quaternion(eje, deg_to_rad(120.0))
Paso 2 — Interpolar con slerp cada frame.
func _physics_process(delta: float) -> void:
t = min(t + delta * 0.5, 1.0) # avanza durante 2 segundos
var q := q_inicio.slerp(q_fin, t)
transform.basis = Basis(q) # aplica la rotación a la malla
Ejecuta la escena (F6). La malla gira de forma fluida, sin tirones, y se detiene en la pose final. Cambia t para que rebote (invierte la dirección al llegar a 1) y observa el giro de ida y vuelta.
Paso 3 — Componer rotaciones. Añade un giro extra de 45° sobre Y sin recalcular todo:
var giro_extra := Quaternion(Vector3.UP, deg_to_rad(45.0))
var q_total := giro_extra * q # primero q, luego el giro extra
transform.basis = Basis(q_total)
Paso 4 — Demostrar gimbal lock con Euler. En un script aparte, rota con Euler llevando el pitch a 90° y observa que yaw y roll se vuelven el mismo eje:
func demostrar_gimbal() -> void:
var pitch := deg_to_rad(90.0) # eje X al límite
var yaw := deg_to_rad(30.0)
var roll := deg_to_rad(30.0)
var b := Basis.from_euler(Vector3(pitch, yaw, roll))
# Con pitch = 90°, cambiar yaw o roll produce el MISMO giro visible:
print("euler recuperado: ", b.get_euler()) # los ejes se confunden
Verás que al recuperar los ángulos, yaw y roll ya no son independientes: eso es gimbal lock. El cuaternión del Paso 1-2 nunca tiene ese problema.
Vector3.RIGHT y aplícalo a la malla.slerp entre tres poses en secuencia (A→B→C) usando dos tramos.slerp con una interpolación lineal ingenua de Euler y describe la diferencia.q2 * q1 difiere de q1 * q2 (la rotación no es conmutativa).Basis de la cámara a Quaternion con transform.basis.get_rotation_quaternion().Programa una torreta 3D que apunte suavemente a un objetivo: cada frame calcula el cuaternión que mira hacia el objetivo (Quaternion desde una Basis construida con looking_at) e interpola desde la orientación actual con slerp a velocidad configurable.
Criterio de aceptación: al mover el objetivo a una nueva posición, la torreta gira sin saltos y queda apuntando al objetivo en menos de 2 segundos, sin bloquearse aunque el objetivo pase justo por encima (pitch cercano a 90°).
| Síntoma | Causa y arreglo |
|---|---|
| La rotación sale deformada o escala la malla | Pasaste un cuaternión no normalizado; usa .normalized() |
| El giro va por el camino largo | Los cuaterniones tienen doble cobertura; slerp de Godot ya elige el arco corto, revisa que no niegues uno |
| El orden de la rotación es incorrecto | Recuerda: q2 * q1 aplica q1 primero; invierte el orden |
Quaternion(eje, ang) da resultados raros |
El eje no estaba normalizado; llama .normalized() |
| Cerca de 90° la cámara "salta" | Estás usando Euler; migra a cuaterniones o a looking_at |
¿Necesito entender el álgebra de cuaterniones? No para usarlos. Basta con crear eje-ángulo, multiplicar para componer e interpolar con slerp. La intuición "eje + cuánto giro" es suficiente.
¿Cuándo uso Euler entonces? Para exponer valores legibles en el editor o entradas simples (girar 90° en un eje). Para rotaciones dinámicas y cámaras, cuaterniones.
¿slerp o lerp entre cuaterniones? slerp mantiene velocidad angular constante y sigue el arco esférico; lerp (o nlerp) es más rápido pero puede acelerar en el medio. Para cámaras suaves, slerp.
¿Basis y Quaternion son lo mismo? Representan la misma orientación. Basis es una matriz (puede incluir escala); Quaternion es compacto y estable para interpolar. Godot convierte entre ambos.
Clase 068 - Repaso aplicado: vectores y transformaciones en el motor
Clase 070 - Integración numérica en la práctica (Euler y Verlet)