Making of the World Wonders 3D Globe

Ilmari Heikkinen

Introduzione al globo 3D World Wonders

Se hai visitato il sito Google World Wonders lanciato di recente su un browser compatibile con WebGL, potresti aver notato un fantastico globo che gira nella parte inferiore dello schermo. Questo articolo spiega come funziona il globo e in che modo è stato costruito.

Per darti una rapida panoramica, il globo World Wonders è una versione molto modificata di WebGL Globe del team Data Arts di Google. Abbiamo preso il globo originale, rimosso i bit dei grafici a barre, cambiato gli ombreggiatori, aggiunto indicatori HTML cliccabili fantasiosi e la geometria del continente naturale di Earth dalla demo GlobeTweeter di Mozilla (un grande grazie a Cedric Pinson!) Il tutto per creare un bel globo animato che si abbina alla combinazione di colori del sito e aggiunge un ulteriore livello di sofisticazione al sito.

Lo scopo del progetto per il globo terrestre era la creazione di una bella mappa animata con indicatori cliccabili posizionati in cima ai siti del Patrimonio mondiale. Tenendo conto di questo, ho iniziato a cercare qualcosa di adatto. La prima cosa che mi è venuta in mente è stata la WebGL Globe, realizzata dal team di Google Data Arts. È un globo e ha un bell'aspetto. Cos'altro ti serve, eh?

Configurazione di WebGL Globe

Il primo passo per creare il widget globo è stato scaricare l'app WebGL Globe e renderla operativa. WebGL Globe è disponibile su Google Code ed è semplice da scaricare ed eseguire. Scarica ed estrai il file zip, il CD al suo interno ed esegui un server web di base: python -m SimpleHTTPServer. Tieni presente che la codifica UTF-8 non è attiva per impostazione predefinita; puoi utilizzarla. Ora, se andrai su http://localhost:8000/globe/globe.html dovresti vedere il globo WebGL.

Con WebGL Globe in funzione, era ora di tagliare tutte le parti non necessarie. Ho modificato il codice HTML per eliminare i bit dell'interfaccia utente e ho rimosso i componenti di configurazione del grafico a barre del globo dalla funzione di inizializzazione del globo. Alla fine del processo, sullo schermo c'era un elemento WebGL Globe essenziale. Puoi farla ruotare e sembra interessante, ma questo è tutto.

Per eliminare gli elementi non necessari, ho eliminato tutti gli elementi dell'interfaccia utente dal sito index.html del globo e modificato lo script di inizializzazione in questo modo:

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

Aggiungere la geometria del continente

Volevamo che la fotocamera fosse vicina alla superficie del globo, ma quando abbiamo testato il globo abbiamo ingrandito la mancanza di risoluzione della texture. Aumentata lo zoom, la texture di WebGL Globe diventa quadrata e sfocata. Avremmo potuto utilizzare un'immagine più grande, ma che avrebbe rallentato il download e l'esecuzione del globo, quindi abbiamo optato per una rappresentazione vettoriale delle masse continentali e dei confini.

Per quanto riguarda la geometria di continenti, mi sono rivolto alla demo open source di GlobeTweeter e ho caricato il modello 3D al suo interno in Three.js. Dopo il caricamento e il rendering del modello, era giunto il momento di iniziare a perfezionare il look per il globo. Il primo problema è stato che il modello di continente non era abbastanza sferico da essere allineato con WebGL Globe, quindi ho finito per scrivere un rapido algoritmo di suddivisione della rete che rendesse il modello più sferico.

Con un modello sferico di massa continentale, sono stato in grado di posizionarlo leggermente sfalsato dalla superficie del globo, creando continenti galleggianti con una linea nera di 2 pixel sotto per creare una sorta di ombra. Ho anche sperimentato con i contorni neon per creare un look simile a quello di Tron.

Con il rendering del globo e delle masse continentali, ho iniziato a sperimentare diversi aspetti del globo. Poiché volevamo optare per un sobrio look monocromatico, ho bloccato un globo in scala di grigi e masse continentali. Oltre ai contorni al neon citati sopra, ho provato un globo scuro con masse continentali scure su uno sfondo chiaro, che in realtà ha un aspetto davvero interessante. Ma il contrasto era troppo basso per essere facilmente leggibile e non si adattava all'aspetto del progetto, quindi l'ho scartato.

Un'altra cosa che ho pensato prima di creare il globo era fare sembrare la porcellana smaltata. Quella che non sono riuscita a provare perché non sono riuscita a scrivere uno Shaker per un aspetto grigio creta (un editor di materiale visivo sarebbe bello). La cosa più vicina che ho provato è stato questo globo luminoso bianco con masse continentali nere. È un po' ordinato ma ha un contrasto troppo elevato. E non sembra molto bello. Eccone un'altra per lo scrapheap.

Gli analisti dei globi in bianco e nero utilizzano una sorta di illuminazione diffusa fitta e retroilluminata. La luminosità del globo dipende dalla distanza della superficie normale dal piano dello schermo. Di conseguenza, i pixel al centro del globo che puntano sullo schermo sono scuri, mentre i pixel ai bordi del globo sono chiari. Se abbinato a uno sfondo chiaro, puoi ottenere un'immagine del globo in cui il globo è riflesso sullo sfondo luminoso e diffuso, creando un look da showroom di classe. Il globo nero utilizza anche la texture WebGL Globe come mappa lucida, in modo che le piattaforme continentali (aree con acque poco profonde) appaiano brillanti rispetto ad altre parti del globo.

Ecco l'aspetto dello strato oceanico per il globo nero. Un Vertex Shader molto basilare e un trucchetto "Oh, che sembra un bel momento di modifica" per i frammenti.

    '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')
    }

Alla fine siamo andati con un globo scuro con masse continentali grigie illuminate dall'alto. Era più vicino al brief di progettazione e aveva un aspetto piacevole e leggibile. Inoltre, un globo con un basso contrasto fa risaltare maggiormente gli indicatori e il resto dei contenuti. La versione seguente utilizza oceani completamente neri, mentre la versione di produzione ha oceani grigio scuro e indicatori leggermente diversi.

Creazione degli indicatori con CSS

A proposito di indicatori, con il globo e le masse continentali attive, ho iniziato a lavorare sui segnaposto. Ho deciso di optare per gli elementi HTML in stile CSS per gli indicatori, per semplificare la creazione e lo stile degli indicatori e per riutilizzare gli indicatori nella mappa 2D a cui il team stava lavorando. A quei tempi non sapevo nemmeno un modo semplice per rendere cliccabili gli indicatori WebGL e non volevo scrivere codice aggiuntivo per caricare / creare modelli di indicatori. Con il passare del tempo, i marcatori CSS funzionavano bene, ma avevano la tendenza a riscontrare occasionalmente problemi di prestazioni quando i compositori e i renderer del browser si trovavano in periodi di cambiamento. Dal punto di vista delle prestazioni, l'applicazione degli indicatori in WebGL sarebbe stata un'opzione migliore. Di nuovo, gli indicatori CSS hanno consentito di risparmiare una buona quantità di tempo di sviluppo.

Gli indicatori CSS sono costituiti da un paio di div posizionati in modo assoluto con la proprietà CSS transform. Lo sfondo degli indicatori è un gradiente CSS e la parte triangolare dell'indicatore è un div ruotato. Gli indicatori hanno una piccola ombra che li mette in risalto dallo sfondo. Il problema maggiore degli indicatori era che avessero un buon rendimento. Per quanto possa sembrare triste, disegnare qualche decina di div che si muovono e modificano il loro z-index su ogni frame è un ottimo modo per attivare tutti i tipi di insidie di rendering del browser.

Il modo in cui gli indicatori sono sincronizzati con la scena 3D non è troppo complicato. A ogni indicatore corrisponde un Object3D nella scena Three.js, che viene utilizzato per tracciare gli indicatori. Per ottenere le coordinate dello spazio sullo schermo, prendo le matrici Three.js per il globo e l'indicatore e moltiplichiamo per queste le matrici di un vettore zero. Da qui ottengo la posizione dell'indicatore nella scena. Per ottenere la posizione dell'indicatore sullo schermo, proietto la posizione della scena attraverso la fotocamera. Il vettore proiettato risultante ha le coordinate dello spazio sullo schermo per l'indicatore, pronto per l'uso in 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;
}

Alla fine, l'approccio più rapido è stato utilizzare le trasformazioni CSS per spostare gli indicatori, non utilizzare la dissolvenza dell'opacità perché attivava un percorso lento su Firefox e mantenere tutti gli indicatori nel DOM, senza rimuoverli quando si trovavano dietro il globo. Abbiamo anche sperimentato l'utilizzo di trasformazioni 3D invece di z-index, ma per qualche motivo non ha funzionato direttamente nell'app (ma ha funzionato in uno scenario di test ridotto, vai al calcolo) e a pochi giorni dal lancio abbiamo dovuto lasciare quella parte alla manutenzione dopo il lancio.

Quando fai clic su un indicatore, questo si espande nell'elenco di nomi dei luoghi cliccabili. Si tratta di normali elementi DOM HTML, quindi è semplicissimo da scrivere. Tutti i link e il rendering del testo funzionano senza alcun intervento aggiuntivo da parte nostra.

Riduzione delle dimensioni del file

Con la demo funzionante e collegata al resto del sito di World Wonders, c'era ancora un grosso problema da risolvere. La mesh in formato JSON per le masse continentali del globo aveva una dimensione di circa 3 megapixel. Non adatto per la prima pagina di un sito vetrina. L'aspetto positivo è stato che la compressione del mesh con gzip ha portato le dimensioni a 350 kB. Ma 350 kB è comunque un po' grande. Un paio di email più tardi siamo riusciti a reclutare Won Chun, che si è occupato della compressione degli enormi mesh di corpo di Google, per darci una mano a comprimerli. Ha compresso la mesh da un grande elenco piatto di triangoli dati come coordinate JSON a coorde compresse a 11 bit con triangoli indicizzati e ha ridotto le dimensioni del file a 95 kB in formato gzip.

L'utilizzo di mesh compressi non comporta solo un risparmio di larghezza di banda, ma anche un'analisi più rapida dei mesh. La trasformazione di 3 mega di numeri sotto forma di stringa in numeri nativi richiede molto più lavoro rispetto all'analisi di un centinaio di kB di dati binari. Inoltre, la conseguente riduzione delle dimensioni di 250 kB per la pagina è molto utile e il tempo di caricamento iniziale è inferiore al secondo con una connessione a 2 Mbps. Più veloce e più piccolo, incredibile!

Allo stesso tempo, stavo scarabocchiando per caricare i file di forma della Terra naturale originali da cui deriva il mesh GlobeTweeter. Sono riuscito a caricare i shapefile, ma per convertirli in masse continentali piatte è necessario triangolarli (con fori per laghi e natch). Ho triangolato le forme usando gli util THREE.js, ma non i fori. E le mesh risultanti avevano bordi molto lunghi, il che richiedeva di dividere la rete in tris più piccoli. Per farla breve, non sono riuscito a farlo in tempo, ma la cosa interessante era che il formato Shapefile ulteriormente compresso avrebbe prodotto un modello landmass da 8 kB. Oh beh, forse la prossima volta.

Lavori futuri

Una cosa che potrebbe richiedere un po' di lavoro in più è migliorare le animazioni degli indicatori. Ora, quando oltrepassano l'orizzonte, l'effetto è un po' appiccicoso. Inoltre, sarebbe utile creare un'animazione accattivante per l'apertura dell'indicatore.

Per quanto riguarda le prestazioni, gli aspetti mancanti sono l'ottimizzazione dell'algoritmo di suddivisione del mesh e la velocità degli indicatori. A parte questo, sono pazzeschi. Evviva!

Riepilogo

In questo articolo ho descritto come abbiamo realizzato il globo in 3D per il progetto World Wonders di Google. Spero ti siano piaciuti gli esempi e che proverai a creare il tuo widget globo personalizzato.

Riferimenti