Creación del globo terráqueo 3D de las maravillas del mundo

Ilmari Heikkinen

Introducción al globo terráqueo en 3D de las Maravillas del mundo

Si viste el sitio Google World Wonders lanzado recientemente en un navegador compatible con WebGL, es posible que hayas visto un elegante globo terráqueo giratorio en la parte inferior de la pantalla. Este artículo le permitirá conocer cómo funciona el globo terráqueo y qué utilizamos para crearlo.

Para ofrecer una descripción general rápida, el globo terráqueo de las Maravillas del mundo es una versión modificada del globo WebGL que creó el equipo de Data Arts de Google. Tomamos el globo terráqueo original, quitamos los bits del gráfico de barras, cambiamos los sombreadores, agregamos elegantes marcadores HTML en los que se puede hacer clic y la geometría del continente de la Tierra natural desde la demostración GlobeTweeter de Mozilla (gracias a Cedric Pinson). todo para crear un bonito globo terráqueo animado que coincida con el esquema de colores del sitio y agregue una capa extra de sofisticación al sitio.

El resumen de diseño del globo terráqueo consistía en tener un mapa animado de aspecto atractivo con marcadores en los que se podía hacer clic encima de los lugares de Patrimonio de la Humanidad. Con eso en mente, empecé a buscar algo adecuado. Lo primero que se les vino a la mente fue el WebGL Globe creado por el equipo de Data Arts de Google. Es un globo terráqueo y se ve genial. ¿Qué más necesitas?

Cómo configurar el Globo con WebGL

El primer paso para crear el widget de globo terráqueo fue descargar WebGL Globe y ponerlo en funcionamiento. WebGL Globe está en línea en Google Code y es fácil de descargar y ejecutar. Descarga y extrae el archivo zip, ábrelo y ejecuta un servidor web básico: python -m SimpleHTTPServer. (Ten en cuenta que UTF-8 no está activado de forma predeterminada; puedes usarlo). Ahora, si navegas a http://localhost:8000/globe/globe.html, deberías ver WebGL Globe.

Con el Globe WebGL en funcionamiento, llegó el momento de cortar todas las partes innecesarias. Edité el código HTML para excluir los bits de la IU y quité los elementos de configuración del gráfico de barras del globo terráqueo de la función de inicialización del globo terráqueo. Al final de ese proceso, tenía un Globe WebGL muy simple en mi pantalla. Puedes darle vuelta y se ve genial, pero eso es todo.

Para eliminar lo innecesario, borré todos los elementos de la IU del index.html del globo terráqueo y edité la secuencia de comandos de inicialización para que se vea de la siguiente manera:

if(!Detector.webgl){
  Detector.addGetWebGLMessage();
} else {
  var container = document.getElementById('container');
  var globe = new DAT.Globe(container);
  globe.animate();
}

Cómo agregar la geometría del continente

Queríamos que la cámara estuviera cerca de la superficie del globo terráqueo, pero cuando probamos el globo, se hizo zoom y se hizo evidente la falta de resolución de la textura. Cuando se acerca, la textura del Globo con WebGL se vuelve borrosa y borrosa. Podríamos haber usado una imagen más grande, pero eso haría que la descarga y ejecución del globo fuera más lenta, por lo que decidimos optar por una representación vectorial de las masas de tierra y los bordes.

Para la geometría de la masa terrestre, utilicé la demostración de código abierto de GlobeTweeter y cargué el modelo 3D en Three.js. Con el modelo cargado y renderizado, llegó el momento de comenzar a pulir el aspecto del globo terráqueo. El primer problema fue que el modelo de masa terrestre no era lo suficientemente esférico como para quedar alineado con el WebGL Globe, así que escribí un algoritmo rápido de división de malla que hizo que el modelo de masa terrestre fuera más esférico.

Con un modelo de masa terrestre esférica, pude colocarlo apenas desplaza la superficie del globo terráqueo, creando continentes flotantes delineados con una línea negra de 2 px debajo de ellos como una sombra. También experimenté con contornos de colores neón para crear una especie de apariencia similar a Tron.

Con el renderizado del globo terráqueo y la masa terrestre, comencé a experimentar con diferentes aspectos del globo terráqueo. Como queríamos elegir un aspecto monocromático discreto, me quedé con un globo terráqueo en escala de grises y masas terrestres. Además de los contornos de neón mencionados anteriormente, probé un globo oscuro con masas de tierra oscuras sobre un fondo claro que, en realidad, se ve genial. Pero tenía un contraste demasiado bajo para ser legible y no se adaptaba al aspecto del proyecto, así que lo descargué.

Otro aspecto que antes se le ocurrió fue que se veía como porcelana vidriada. Esa es que no pude probar porque no pude escribir un sombreador para el aspecto de porcelana (sería lindo usar un editor de material visual). Lo más cercano que intenté fue este globo blanco brillante con masas de tierra negras. Es prolijo, pero tiene un contraste demasiado alto. Y no se ve muy bien. Así que otra para el montón de basura.

Los sombreadores en los globos blancos y negros usan una especie de iluminación difusa a contraluz falsa. La luminosidad del globo terráqueo depende de la distancia de la superficie normal al plano de la pantalla. Por lo tanto, los píxeles del centro del globo terráqueo que apuntan a la pantalla son oscuros y los píxeles en los bordes del globo son claros. Cuando se combina con un fondo claro, se consigue una imagen en la que el globo terráqueo refleja el fondo brillante difuso, lo que crea un estilo con clase en una sala de exposición. El globo negro también usa la textura WebGL Globe como mapa brillante para que las plataformas continentales (áreas de aguas poco profundas) se vean brillantes en comparación con las demás partes del mundo.

Así se ve el sombreador del océano para el globo terráqueo negro. Un sombreador de vértices muy básico y un sombreador de fragmentos retoque de retoque".

    'ocean' : {
      uniforms: {
        'texture': { type: 't', value: 0, texture: null }
      },
      vertexShader: [
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
          'vNormal = normalize( normalMatrix * normal );',
          'vUv = uv;',
        '}'
      ].join('\n'),
      fragmentShader: [
        'uniform sampler2D texture;',
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'vec3 diffuse = texture2D( texture, vUv ).xyz;',
          'float intensity = pow(1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0);',
          'float i = 0.8-pow(clamp(dot( vNormal, vec3( 0, 0, 1.0 )), 0.0, 1.0), 1.5);',
          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * intensity;',
          'float d = clamp(pow(max(0.0,(diffuse.r-0.062)*10.0), 2.0)*5.0, 0.0, 1.0);',
          'gl_FragColor = vec4( (d*vec3(i)) + ((1.0-d)*diffuse) + atmosphere, 1.0 );',
        '}'
      ].join('\n')
    }

Al final, fuimos con un globo terráqueo oscuro con masas de tierra gris claro iluminadas desde arriba. Estaba más cerca del resumen de diseño y se veía agradable y legible. Además, que el globo terráqueo tenga un contraste un poco bajo hace que los marcadores y el resto del contenido se destaquen más en comparación. La siguiente versión usa océanos totalmente negros, mientras que la versión de producción tiene océanos de color gris oscuro y marcadores ligeramente diferentes.

Cómo crear los marcadores con CSS

Hablando de marcadores, con el globo terráqueo y las masas de tierra funcionando, comencé a trabajar en los marcadores. Decidí usar elementos HTML de estilo CSS para los marcadores para que fuera más fácil crearlos y darles estilo, y para poder reutilizarlos en el mapa 2D en el que estaba trabajando el equipo. En ese momento, tampoco sabía que existía una manera fácil de hacer que se pudiera hacer clic en los marcadores de WebGL y no quería escribir código adicional para cargar o crear los modelos de marcadores. En retrospectiva, los marcadores CSS funcionaban bien, pero tenían la tendencia a tener, a veces, problemas de rendimiento cuando los compositores y los procesadores del navegador se encontraban en períodos de cambio. Desde el punto de vista del rendimiento, hacer los marcadores en WebGL habría sido una mejor opción. Por otro lado, los marcadores CSS ahorraron mucho tiempo de desarrollo.

Los marcadores CSS consisten en un par de div posicionados de manera absoluta con la propiedad de transformación CSS. El fondo de los marcadores es un gradiente de CSS, y la parte triangular del marcador es un elemento div rotado. Los marcadores tienen una pequeña sombra paralela para destacarlos del fondo. El mayor problema que tenían los marcadores era que mejoraran su rendimiento. Por tristeza que parezca, dibujar unas docenas de elementos div que se muevan y cambien su índice z en cada fotograma es una forma bastante buena de activar todo tipo de errores de renderización del navegador.

La forma en que los marcadores se sincronizan con la escena 3D no es demasiado complicada. Cada marcador tiene un Object3D correspondiente en la escena de Three.js, que se usa para hacer un seguimiento de los marcadores. Para obtener las coordenadas del espacio en pantalla, tomo las matrices de Three.js para el globo y el marcador y multiplico un vector cero por ellas. A partir de ahí, obtengo la posición de la escena del marcador. Para obtener la posición de la pantalla del marcador, proyecto la posición de la escena a través de la cámara. El vector proyectado resultante tiene las coordenadas del espacio de pantalla para el marcador, listas para usarse en CSS.

var mat = new THREE.Matrix4();
var v = new THREE.Vector3();

for (var i=0; i<locations.length; i++) {
  mat.copy(scene.matrix);
  mat.multiplySelf(locations[i].point.matrix);
  v.set(0,0,0);
  mat.multiplyVector3(v);
  projector.projectVector(v, camera);
  var x = w * (v.x + 1) / 2; // Screen coords are between -1 .. 1, so we transform them to pixels.
  var y = h - h * (v.y + 1) / 2; // The y coordinate is flipped in WebGL.
  var z = v.z;
}

Al final, el enfoque más rápido fue usar transformaciones CSS para mover los marcadores, no usar atenuación de opacidad, ya que activaba una ruta lenta en Firefox y mantenía todos los marcadores en el DOM, sin eliminarlos cuando estaban detrás del globo terráqueo. También experimentamos con el uso de transformaciones 3D en lugar de índices z, pero, por algún motivo, no funcionaba bien en la app (pero funcionó en un caso de prueba reducido, ve la cifra). Estuvimos a unos días del lanzamiento, así que tuvimos que dejar esa parte para el mantenimiento posterior.

Cuando haces clic en un marcador, este se expande en una lista de nombres de lugares en los que se puede hacer clic. Todo esto es algo normal de HTML DOM, por lo que fue muy fácil de escribir. Todos los vínculos y la representación de texto funcionan sin ningún esfuerzo de nuestra parte.

Comprimir el tamaño del archivo

Con la demostración en funcionamiento y conectada al resto del sitio de las Maravillas del mundo, todavía quedaba un gran problema por resolver. La malla en formato JSON para las masas continentales del globo tenía un tamaño de aproximadamente 3 Mb. No es recomendable para la primera página de un sitio de presentación. Lo bueno es que comprimir la malla con gzip la redujo a 350 KB. Pero bueno, 350 KB sigue siendo un poco grande. Después de un par de correos electrónicos, logramos reclutar a Won Chun, que trabajó en la compresión de las enormes mallas de cuerpo de Google, para que nos ayudara a comprimir la malla. Comprimió la malla desde una gran lista plana de triángulos proporcionados como coordenadas JSON a coordenadas comprimidas de 11 bits con triángulos indexados y logró que el tamaño del archivo se comprimiera hasta 95 KB en gzip.

El uso de mallas comprimidas no solo ahorra ancho de banda, sino que también se analizan con más rapidez. Convertir 3 megas de números en string en números nativos requiere mucho más trabajo que analizar cien kB de datos binarios. Además, la reducción del tamaño resultante de 250 KB para la página es muy útil y permite que el tiempo de carga inicial sea inferior a un segundo en una conexión de 2 Mbps. ¡Es increíble, más rápido y pequeño!

Al mismo tiempo, dibujaba archivos con los archivos Shapefile de Natural Earth originales del que se derivaba la malla GlobeTweeter. Logré cargar los archivos Shapefile, pero renderizarlos como masas de tierra planas requiere triangulaciones (con agujeros para lagos, natch). Se triangularon las formas con THREE.js, pero no los agujeros. Además, las mallas resultantes tenían bordes muy largos, por lo que se requería dividirlas en tris más pequeñas. En pocas palabras, no logré que funcionara a tiempo, pero lo bueno es que el formato Shapefile más comprimido te habría conseguido un modelo de masa terrestre de 8 KB. Bueno, quizás la próxima.

Trabajo futuro

Algo que requeriría un poco más de trabajo es hacer que las animaciones de los marcadores sean más atractivas. Ahora, cuando sobrepasan el horizonte, el efecto es un poco sórdido. Además, sería bueno tener una animación genial para la apertura del marcador.

En cuanto al rendimiento, los dos aspectos que faltan son optimizar el algoritmo de división de malla y acelerar los marcadores. Aparte de eso, todo es genial. ¡Hurra!

Resumen

En este artículo, describí cómo construimos el globo terráqueo en 3D para el proyecto Google World Wonders. Espero que hayas disfrutado los ejemplos y que intentes crear tu propio widget de globo terráqueo personalizado.

Referencias