Creando un mundo abierto en el navegador, parte 17: Animaciones que no necesitaron retargeting y una búsqueda de recursos en tiempo real
Por Oleg Sidorkin, CTO y cofundador de Cinevva
¿Es tu primera vez aquí? Consulta la guía de la serie. Explica qué es un spike y enlaza todas las partes.
La parte 16 nos dio un mundo en el que colocar objetos. Esta parte ofrece al jugador algo mejor que hacer en él: un conjunto de animaciones apto para el combate y una forma de incorporar al mundo cualquiera de entre mil modelos CC0 con solo escribir una búsqueda.
262 clips, un esqueleto y tres correcciones de retargeting
Abrir el Spike 35 en una pestaña nueva ↗ · Ver el código fuente
El rig del jugador es un esqueleto 3MIKE al estilo CC4 con 213 huesos, incluidos un rig facial de más de 80 huesos y todas las articulaciones de los dedos. Las fuentes de animación no coinciden con él. La primera versión aplicaba retargeting a clips de combate de Mixamo seleccionados manualmente, pero el conjunto de fuentes era escaso y la locomoción estaba dividida de forma incómoda entre Mixamo y unas pocas animaciones de reposo BVH de Kimodo. El verdadero avance llegó cuando cambiamos la biblioteca de origen por los dos paquetes Universal Animation Library de Quaternius: 262 clips en total sobre un único maniquí coherente de 65 huesos al estilo UE5, con combos de espada, tiro con arco, escalada, carreras por paredes, esquivas, reacciones a impactos, gestos y un conjunto de locomoción mucho más limpio.
Eso supone pasar de una fuente de 65 huesos a un destino de 213, sin nombres de huesos, orientaciones en pose T ni proporciones de extremidades compartidos. Cada clip se reasigna en tres pasos: un mapa de nombres de huesos, una alineación de la pose de enlace para que ambos rigs compartan una orientación de referencia y un escalado de las pistas de posición para que el rig de origen, más bajo, no deje al personaje hundido hasta las rodillas en el suelo. Hacerlo bien implicó perseguir una serie de errores, cada uno de los cuales nos enseñó algo concreto.
El personaje aparecía excesivamente retorcido, con todos los hombros y codos girados unos 30° de más. La causa era una discrepancia de poses: la fuente UAL viene con una pose T real, la pose de enlace de CC4 es una pose A y el sistema de retargeting suponía que ambos rigs estaban en poses de referencia coincidentes, por lo que la diferencia entre A y T se añadía a cada delta por fotograma. La corrección fuerza la cadena de los brazos de CC4 a adoptar una pose T real para capturar el enlace y después aplica el retargeting, de modo que los deltas se mantengan pequeños.
Después, el cuerpo dejó de trasladarse. Los retrocesos desplazaban la parte superior del cuerpo, pero dejaban los pies clavados. UAL coloca el desplazamiento en el hueso root, no en la pelvis, por lo que leer la posición de la pelvis producía un movimiento casi nulo. La corrección suma root.position y pelvis.position, escala la parte horizontal y escribe una única pista de posición de la cadera. Al hacerlo descubrimos que los desplazamientos verticales se desviaban alrededor de un 12 %, porque habíamos usado la proporción global de las extremidades para el eje Y, cuando la altura de la pelvis necesita la proporción entre la cadera y el suelo. Dos proporciones, dos ejes: RL_BoneRoot.position resultó deberse a que asignábamos el root de UAL, situado en
La corrección más pequeña fue la más satisfactoria de ver. El personaje sujetaba la espada con los dedos flácidos de la pose de enlace porque el mapa de huesos no incluía entradas para los dedos, y el sistema de retargeting solo escribe pistas para los huesos asignados. Añadir 30 huesos de los dedos —cinco dedos, tres segmentos, dos manos, omitiendo el cuarto hueso auxiliar de la punta de UAL, que no deforma— hizo que la mano se cerrara sobre la empuñadura y se abriera al soltarla.
Verificar 262 clips sin tener que ver 262 clips
No se puede verificar visualmente una biblioteca de 262 clips, así que creamos una prueba de paridad de trayectorias sin conexión: un script de Node sin interfaz que carga cada paquete, muestrea el esqueleto de origen a 60 Hz, ejecuta el pipeline de retargeting y compara las posiciones y rotaciones globales de cada hueso con las de la fuente después del escalado. La deriva máxima de la pelvis en Y fue de 0,003 m. Las manos mostraban un desplazamiento constante de 2,5° que al principio interpretamos como «los dedos no siguen la animación», pero un desplazamiento constante es el delta de enlace entre la mano plana de UAL y la pose de enlace ligeramente curvada de CC4, y permanece invariable durante todo el clip. Los errores reales de animación se manifiestan como una deriva que varía de un fotograma a otro. Una vez aclarado esto, la prueba se convirtió en una comprobación de regresión de una sola pasada: si la deriva de un clip deja de mantener esa referencia constante, un cambio reciente ha roto el retargeting.
Un maniquí de referencia mostrado en paralelo hizo decisiva la parte visual de la depuración. Al pulsar la barra invertida aparece junto al jugador el rig de origen al que pertenece el clip actual, de modo que la pregunta «¿por qué está retorcido el hombro?» se convierte en «¿el giro está en la fuente o lo ha añadido el retargeting?». Las pruebas numéricas detectan regresiones, la referencia visual detecta errores de la pose de enlace que los números no revelan y, juntas, eliminaron las conjeturas.
Con el pipeline consolidado, cambiar la base de WASD, salto y natación de Mixamo a UAL solo requirió un pequeño mapa de alias: la máquina de estados finitos sigue usando nombres de estado genéricos como idle y walk, que se resuelven a nombres de clips de UAL durante la reproducción. La natación necesitaba desplazamientos de rig específicos para cada clip porque las poses de estilo libre y de mantenerse a flote anclan la pelvis a distintas alturas anatómicas, así que sumergimos el rig medio metro para la natación activa y algo más para mantenerse a flote, interpolando suavemente entre ambas a 5 Hz.
Escribe una palabra y obtén un modelo
Abrir el Spike 36 en una pestaña nueva ↗ · Ver el código fuente
El Spike 34 consumió un día entero seleccionando manualmente un paquete CC0. La solución a largo plazo es un cuadro de búsqueda. Polyhaven publica unos 1.100 modelos CC0 mediante una API JSON permisiva y una CDN determinista, y este spike conecta todo el recorrido —consulta, miniaturas, carga y renderizado— desde una página estática sin ningún paso de compilación, en unas 300 líneas de JavaScript puro más three.js.
Al iniciarse, descarga una vez el catálogo completo, de unos 600 KB. La búsqueda usa un sistema de puntuación puramente del lado del cliente —el nombre tiene prioridad sobre el identificador, este sobre la categoría y esta sobre la etiqueta— con un retardo de 120 ms, y muestra las 60 tarjetas principales. Las miniaturas se cargan de forma diferida mediante un IntersectionObserver, para que escribir no dispare 60 solicitudes a la vez. La parte interesante es la carga. El endpoint de archivos de Polyhaven expone glTF de varios archivos, no GLB, con texturas compartidas entre resoluciones y divididas en archivos separados, y devuelve un mapa include que relaciona cada ruta relativa con una URL absoluta de la CDN. En lugar de descargar y modificar nosotros mismos el JSON, pasamos ese mapa por LoadingManager.setURLModifier, que se ejecuta para cada dependencia que necesita el cargador —el .bin y cada textura— y la resuelve mediante la CDN. Un clic, un único archivo en apariencia. Tanto la API como la CDN establecen CORS permisivo, algo que verificamos con curl antes de escribir código de cliente, así que no hace falta ningún proxy. Los materiales PBR se renderizan correctamente con RoomEnvironment y los valores predeterminados del mapeo de tonos ACES, sin ajustes específicos por recurso, y las texturas de 1k mantienen un modelo típico entre 2 y 5 MB en lugar de los 20 a 40 MB de las texturas 4k.
Una pasada WASM de meshoptimizer completa el sistema con un reductor no destructivo: cada malla guarda un clon de su geometría original y, al cambiar la proporción, reconstruye un búfer de índices a partir de ese clon en lugar de simplificar de forma acumulativa. La geometría con varios materiales se simplifica por grupo y reconstruye geometry.groups para que las ranuras de materiales no colapsen. Un sillón pasa de 5.626 triángulos en calidad completa a 2.812 a la mitad.
Tecnología mencionada en este capítulo
Retargeting de esqueletos con alineación de la pose de enlace. Transferir una animación de un esqueleto a otro con distintos nombres de huesos, proporciones y poses de referencia requiere tres correcciones: un mapa de nombres de huesos, una alineación de la pose de enlace para que ambos rigs compartan una orientación de referencia —forzando la cadena de brazos en pose A del destino a adoptar la pose T de la fuente— y el escalado de las pistas de posición. Una discrepancia de poses añade la rotación de A a T a cada delta por fotograma, duplicando la rotación de las articulaciones. Los huesos auxiliares sin asignar deben descartarse, ya que un hueso raíz en
Dos proporciones de escala para un solo rig. El desplazamiento horizontal usa la proporción global de las extremidades —el tamaño total del esqueleto—, pero el desplazamiento vertical de la pelvis usa la proporción entre la cadera y el suelo root, no en la pelvis, así que ambos deben sumarse en una única pista de posición de la cadera para conservar el movimiento en clips de retroceso, escalada y locomoción.
Pruebas sin conexión de paridad de trayectorias. Un script sin interfaz muestrea el esqueleto de origen a 60 Hz, ejecuta el pipeline de retargeting y compara las transformaciones globales de cada hueso con la fuente. Un desplazamiento constante por fotograma es el delta de enlace inofensivo, mientras que una deriva que varía de un fotograma a otro es un error real; por eso, la prueba se convierte en una comprobación de regresión que se activa cuando un cambio elimina o distorsiona una pista. El aislamiento de los GLB por paquete —carga nueva y liberación antes del siguiente— evita la contención de caché durante el recorrido completo.
LoadingManager.setURLModifier para grafos glTF alojados en una CDN. Cuando una CDN distribuye glTF como un grafo de URI relativas acompañado de un mapa de inclusiones —ruta relativa a URL absoluta—, setURLModifier resuelve mediante la CDN cada dependencia solicitada por el cargador sin reescribir el JSON. Esto convierte una distribución de varios archivos y resoluciones en una carga con un solo clic. Liberar la geometría, los materiales y los mapas de texturas de cada modelo anterior antes de cargar el siguiente evita que se acumulen cientos de MB de memoria de GPU durante una sesión de exploración.
Simplificación no destructiva de mallas. Guardar un clon de la geometría original de cada malla y reconstruir solo el búfer de índices para cada proporción de simplificación mantiene los cambios rápidos y evita los daños acumulativos de simplificaciones repetidas. Ejecutar el proceso por cada sección de geometry.groups y reconstruir los grupos conserva las asignaciones de varios materiales. Consulta LOD y meshoptimizer para ver cómo se integra esto en el LOD basado en la distancia.
Parte 17 de 29. Anterior: Parte 16 - Estructura para un mundo que no deja de crecer Siguiente: Parte 18 - Un pincel de dispersión que parece guiado por IA Guía de la serie: /es/blog/2026-02-25-open-world-browser-series-guide