Cómo dar vida a la Tierra Media con WebGL para dispositivos móviles
Históricamente, llevar experiencias interactivas, basadas en la Web y con mucho contenido multimedia a los dispositivos móviles y las tablets ha sido un desafío. Las principales limitaciones fueron el rendimiento, la disponibilidad de la API, las limitaciones del audio HTML5 en los dispositivos y la falta de una reproducción de video intercalada sin problemas.
A principios de este año, comenzamos un proyecto con amigos de Google y Warner Bros. para crear una experiencia web centrada en dispositivos móviles para la nueva película de Hobbit, The Hobbit: The Desolation of Smaug. Crear un Experimento de Chrome para dispositivos móviles con mucho contenido multimedia fue una tarea realmente inspiradora y desafiante.
La experiencia está optimizada para Chrome para Android en los nuevos dispositivos Nexus, en los que ahora tenemos acceso a WebGL y Web Audio. Sin embargo, se puede acceder a una gran parte de la experiencia en dispositivos y navegadores que no son WebGL gracias a la composición acelerada por hardware y las animaciones CSS.
Toda la experiencia se basa en un mapa de la Tierra Media y en las ubicaciones y los personajes de las películas de El Hobbit. El uso de WebGL nos permitió dramatizar y explorar el rico mundo de la trilogía de El Hobbit y permitir que los usuarios controlen la experiencia.
Desafíos de WebGL en dispositivos móviles
En primer lugar, el término "dispositivos móviles" es muy amplio. Las especificaciones de los dispositivos varían mucho. Por lo tanto, como desarrollador, debes decidir si quieres admitir más dispositivos con una experiencia menos compleja o, como hicimos en este caso, limitar los dispositivos compatibles a aquellos que puedan mostrar un mundo 3D más realista. Para "Viaje por la Tierra Media", nos enfocamos en dispositivos Nexus y cinco smartphones Android populares.
En el experimento, usamos three.js, como lo hicimos en algunos de nuestros proyectos anteriores de WebGL. Para comenzar la implementación, compilamos una versión inicial del juego Trollshaw que se ejecutara bien en la tablet Nexus 10. Después de algunas pruebas iniciales en el dispositivo, teníamos en mente una lista de optimizaciones que se parecía mucho a lo que normalmente usábamos para una laptop de baja especificación:
- Usa modelos de baja poligonización
- Usa texturas de baja resolución
- Combina la geometría para reducir la cantidad de llamadas de dibujo tanto como sea posible.
- Simplifica los materiales y la iluminación
- Quita los efectos posteriores y desactiva el suavizado
- Optimiza el rendimiento de JavaScript
- Renderiza el lienzo de WebGL a la mitad del tamaño y escala con CSS
Después de aplicar estas optimizaciones a nuestra primera versión preliminar del juego, obtuvimos una velocidad de fotogramas estable de 30 FPS con la que estábamos conformes. En ese momento, nuestro objetivo era mejorar los elementos visuales sin afectar negativamente la velocidad de fotogramas. Probamos muchos trucos: algunos tuvieron un impacto real en el rendimiento, mientras que otros no tuvieron el efecto esperado.
Usa modelos de baja poligonización
Comencemos con los modelos. El uso de modelos de baja poligonización ayuda a reducir el tiempo de descarga y el tiempo que se tarda en inicializar la escena. Descubrimos que podíamos aumentar mucho la complejidad sin afectar mucho el rendimiento. Los modelos de troll que usamos en este juego son de alrededor de 5,000 caras y la escena tiene alrededor de 40,000 caras, y eso funciona bien.

En otra ubicación de la experiencia (aún no lanzada), observamos un mayor impacto en el rendimiento debido a la reducción de polígonos. En ese caso, cargamos objetos con menos polígonos para dispositivos móviles que los que cargamos para computadoras de escritorio. Crear diferentes conjuntos de modelos 3D requiere trabajo adicional y no siempre es necesario. En realidad, depende de qué tan complejos sean tus modelos.
Cuando trabajamos en escenas grandes con muchos objetos, tratamos de ser estratégicos en la forma en que dividimos la geometría. Esto nos permitió activar y desactivar rápidamente las mallas menos importantes para encontrar una configuración que funcionara en todos los dispositivos móviles. Luego, podríamos elegir combinar la geometría en JavaScript durante el tiempo de ejecución para la optimización dinámica o combinarla en la etapa de preproducción para guardar solicitudes.
Usa texturas de baja resolución
Para reducir el tiempo de carga en dispositivos móviles, decidimos cargar diferentes texturas que tenían la mitad del tamaño de las texturas de computadoras de escritorio. Resulta que todos los dispositivos pueden controlar tamaños de textura de hasta 2048 x 2048 px, y la mayoría puede controlar 4096 x 4096 px. La búsqueda de texturas en las texturas individuales no parece ser un problema una vez que se suben a la GPU. El tamaño total de las texturas debe caber en la memoria de la GPU para evitar que se suban y descarguen constantemente, pero es probable que esto no sea un gran problema para la mayoría de las experiencias web. Sin embargo, es importante combinar las texturas en la menor cantidad posible de hojas de sprites para reducir la cantidad de llamadas de dibujo, lo que tiene un gran impacto en el rendimiento en dispositivos móviles.

(tamaño original: 512 x 512 px)
Simplifica los materiales y la iluminación
La elección de los materiales también puede afectar en gran medida el rendimiento y debe administrarse con prudencia en los dispositivos móviles. Usamos MeshLambertMaterial
(cálculo de luz por vértice) en three.js en lugar de MeshPhongMaterial
(cálculo de luz por texel) para optimizar el rendimiento. Básicamente, intentamos usar sombreadores lo más simples posible con la menor cantidad de cálculos de iluminación.
Para ver cómo los materiales que usas afectan el rendimiento de una escena, puedes anular los materiales de la escena con un MeshBasicMaterial
. Esto te permitirá hacer una buena comparación.
scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});
Optimiza el rendimiento de JavaScript
Cuando se compilan juegos para dispositivos móviles, la GPU no siempre es el mayor obstáculo. Se dedica mucho tiempo a la CPU, en especial a la física y las animaciones esqueléticas. A veces, un truco que ayuda, según la simulación, es ejecutar estos cálculos costosos cada dos fotogramas. También puedes usar las técnicas de optimización de JavaScript disponibles en lo que respecta al grupo de objetos, la recopilación de elementos no utilizados y la creación de objetos.
Actualizar objetos preasignados en bucles en lugar de crear objetos nuevos es un paso importante para evitar "problemas" de recolección de elementos no utilizados durante el juego.
Por ejemplo, considera un código como el siguiente:
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos = new THREE.Vector3(0+offsetX,100,0);
}
Una versión mejorada de este bucle evita la creación de objetos nuevos que deben eliminarse:
var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos.copy(originPos).x += offsetX;
//or
currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}
En la medida de lo posible, los controladores de eventos solo deben actualizar las propiedades y permitir que el bucle de renderización requestAnimationFrame
controle la actualización del escenario.
Otra sugerencia es optimizar o precalcular las operaciones de lanzamiento de rayos. Por ejemplo, si necesitas conectar un objeto a una malla durante un movimiento de ruta estática, puedes "registrar" las posiciones durante un bucle y, luego, leer estos datos en lugar de realizar un ray-casting contra la malla. O bien, como hacemos en la experiencia de Rivendell, puedes usar el ray-casting para buscar interacciones del mouse con una malla invisible más simple de baja poligonización. La búsqueda de colisiones en una malla de alta poligonización es muy lenta y se debe evitar en un bucle de juego en general.
Renderiza el lienzo de WebGL a la mitad del tamaño y escala con CSS
El tamaño del lienzo WebGL es probablemente el parámetro más eficaz que puedes ajustar para optimizar el rendimiento. Cuanto más grande sea el lienzo que uses para dibujar tu escena en 3D, más píxeles se deberán dibujar en cada fotograma. Esto, por supuesto, afecta el rendimiento.El Nexus 10, con su pantalla de alta densidad de 2560 x 1600 píxeles, tiene que enviar 4 veces la cantidad de píxeles que una tablet de baja densidad. Para optimizar esto para dispositivos móviles, usamos un truco en el que configuramos el lienzo a la mitad del tamaño (50%) y, luego, lo aumentamos hasta su tamaño previsto (100%) con transformaciones 3D de CSS aceleradas por hardware. La desventaja de esto es que se obtiene una imagen pixelada en la que las líneas finas pueden convertirse en un problema, pero en una pantalla de alta resolución, el efecto no es tan malo. Vale la pena el rendimiento adicional.

Objetos como componentes básicos
Para poder crear el gran laberinto del castillo de Dol Guldur y el valle sin fin de Rivendell, creamos un conjunto de modelos 3D de bloques de construcción que reutilizamos. La reutilización de objetos nos permite asegurarnos de que se creen instancias de los objetos y se carguen al comienzo de la experiencia, y no en medio de ella.

En Rivendell, tenemos varias secciones de suelo que reposicionamos constantemente en la profundidad en Z a medida que avanza el recorrido del usuario. A medida que el usuario pasa por las secciones, estas se vuelven a posicionar en la distancia lejana.
En el caso del castillo de Dol Guldur, queríamos que el laberinto se regenerara para cada partida. Para ello, creamos una secuencia de comandos que regenera el laberinto.
Si combinas toda la estructura en una malla grande desde el principio, se genera una escena muy grande y un rendimiento bajo. Para abordar este problema, decidimos ocultar y mostrar los componentes básicos según si están a la vista. Desde el principio, teníamos la idea de usar una secuencia de comandos de raycaster en 2D, pero al final usamos la eliminación de frustrum integrada de three.js. Volvimos a usar la secuencia de comandos del raycaster para acercar el "peligro" al que se enfrenta el jugador.
El siguiente gran tema que debes controlar es la interacción del usuario. En computadoras, tienes la entrada del mouse y el teclado. En dispositivos móviles, los usuarios interactúan con el tacto, el deslizamiento, el pellizco, la orientación del dispositivo, etcétera.
Cómo usar la interacción táctil en experiencias web para dispositivos móviles
Agregar compatibilidad táctil no es difícil. Hay artículos excelentes para leer sobre el tema. Sin embargo, hay algunos detalles que pueden complicarlo.
Puedes usar ambos: el mouse y la pantalla táctil. La Chromebook Pixel y otras laptops compatibles con la función táctil son compatibles con el mouse y la función táctil. Un error común es verificar si el dispositivo está habilitado para la función táctil y, luego, solo agregar objetos de escucha de eventos táctiles y ninguno para el mouse.
No actualices la renderización en los objetos de escucha de eventos. En su lugar, guarda los eventos táctiles en variables y reacciona a ellos en el bucle de renderización de requestAnimationFrame. Esto mejora el rendimiento y también une eventos en conflicto. Asegúrate de volver a usar objetos en lugar de crear objetos nuevos en los objetos de escucha de eventos.
Recuerda que es multitáctil: event.touches es un array de todos los toques. En algunos casos, es más interesante observar event.targetTouches o event.changedTouches y solo reaccionar a los toques que te interesan. Para separar los toques de los deslizamientos, usamos un retraso antes de verificar si el toque se movió (deslizamiento) o si está quieto (toque). Para obtener un pellizco, medimos la distancia entre los dos toques iniciales y cómo cambia con el tiempo.
En un mundo 3D, debes decidir cómo reacciona la cámara a las acciones del mouse en comparación con las de deslizamiento. Una forma común de agregar movimiento de cámara es seguir el movimiento del mouse. Esto se puede hacer con un control directo mediante la posición del mouse o con un movimiento delta (cambio de posición). No siempre quieres que el comportamiento de un dispositivo móvil sea el mismo que el de un navegador para computadoras de escritorio. Realizamos pruebas exhaustivas para decidir qué era lo adecuado para cada versión.
Cuando trabajes con pantallas más pequeñas y pantallas táctiles, verás que los dedos del usuario y los gráficos de interacción de la IU suelen interferir en lo que quieres mostrar. Esto es algo a lo que estamos acostumbrados cuando diseñamos apps nativas, pero que no tuvimos que tener en cuenta antes con las experiencias web. Este es un verdadero desafío para los diseñadores y diseñadores de UX.
Resumen
Nuestra experiencia general con este proyecto es que WebGL en dispositivos móviles funciona muy bien, especialmente en dispositivos más nuevos y de alta gama. En lo que respecta al rendimiento, parece que el recuento de polígonos y el tamaño de la textura afectan principalmente los tiempos de descarga y de inicialización, y que los materiales, los sombreadores y el tamaño del lienzo de WebGL son las partes más importantes para optimizar el rendimiento en dispositivos móviles. Sin embargo, es la suma de las partes lo que afecta el rendimiento, por lo que todo lo que puedas hacer para optimizarlo cuenta.
Orientar tu contenido a dispositivos móviles también significa que debes acostumbrarte a pensar en las interacciones táctiles y que no solo se trata del tamaño de los píxeles, sino también del tamaño físico de la pantalla. En algunos casos, tuvimos que acercar la cámara 3D para ver qué estaba pasando.
Se lanzó el experimento y ha sido un recorrido fantástico. Esperamos que lo disfrutes.
¿Quieres probarlo? Haz tu propio viaje a la Tierra Media.