Introduzione al globo 3D delle meraviglie del mondo
Se hai visitato il sito Meraviglie del mondo di Google lanciato di recente su un browser compatibile con WebGL, potresti aver notato un globo in rotazione nella parte inferiore dello schermo. Questo articolo ti spiega come funziona il globo e quali strumenti abbiamo utilizzato per realizzarlo.
Per darti una rapida panoramica, il globo delle meraviglie del mondo è una versione molto modificata del globo WebGL del team di Google Data Arts. Abbiamo preso il globo originale, rimosso i componenti del grafico a barre, modificato gli shader, aggiunto indicatori HTML cliccabili e la geometria dei continenti di Natural Earth dalla demo di GlobeTweeter di Mozilla (un grande grazie a Cedric Pinson!). Il tutto per creare un bel globo animato che si abbini alla combinazione di colori del sito e ne accompagni la raffinatezza.
Il brief per il design del globo era creare una mappa animata di bell'aspetto con indicatori cliccabili posizionati sopra i siti Patrimonio dell'Umanità. Tenendo presente questo, ho iniziato a cercare qualcosa di adatto. La prima cosa che mi è venuta in mente è stata la sfera WebGL creata dal team di Data Arts di Google. È un globo ed è bello. Cos'altro ti serve?
Configurazione del globo WebGL
Il primo passaggio per creare il widget del globo è stato scaricare il globo WebGL e farlo funzionare. Il globo WebGL è disponibile online su Google Code ed è facile da scaricare ed eseguire. Scarica ed estrai il file ZIP, vai alla relativa cartella ed esegui un web server di base: python -m SimpleHTTPServer
. Tieni presente che UTF-8 non è attivo per impostazione predefinita, ma puoi utilizzarlo. Ora, se vai a http://localhost:8000/globe/globe.html
, dovresti vedere il globo WebGL.
Una volta che il globo WebGL era attivo e funzionante, era giunto il momento di eliminare tutte le parti non necessarie. Ho modificato il codice HTML per eliminare i componenti dell'interfaccia utente e ho rimosso la configurazione del grafico a barre del globo dalla funzione di inizializzazione del globo. Al termine di questo processo, sullo schermo era presente un globo WebGL molto essenziale. Puoi farlo girare ed è bello da vedere, ma non c'è altro.
Per eliminare gli elementi non necessari, ho eliminato tutti gli elementi dell'interfaccia utente da index.html del globo e ho 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();
}
Aggiunta della geometria del continente
Volevamo avvicinare la fotocamera alla superficie del globo, ma quando abbiamo provato a eseguire lo zoom sul globo, è emersa la mancanza di risoluzione delle texture. Se aumenti lo zoom, la trama della sfera WebGL diventa a blocchi e sfocata. Avremmo potuto utilizzare un'immagine più grande, ma il download e l'esecuzione del globo sarebbero stati più lenti, quindi abbiamo optato per una rappresentazione vettoriale delle masse continentali e dei confini.
Per la geometria delle masse continentali, ho utilizzato la demo open source GlobeTweeter e ho caricato il modello 3D in Three.js. Una volta caricato e visualizzato il modello, era giunto il momento di perfezionare l'aspetto del globo. Il primo problema era che il modello delle masse continentali del globo non era abbastanza sferico da essere a filo con il globo WebGL, quindi ho finito per scrivere un rapido algoritmo di suddivisione della mesh che ha reso il modello delle masse continentali più sferico.
Con un modello sferico delle masse continentali, ho potuto posizionarlo leggermente fuori dalla superficie del globo, creando continenti fluttuanti delineati da una linea nera di 2 pixel sottostante per creare una sorta di ombra. Ho anche sperimentato con contorni colorati fluorescenti per creare una sorta di look alla Tron.
Con il rendering del globo e delle masse continentali, ho iniziato a sperimentare diversi look per il globo. Dato che volevamo optare per un look monocromatico sobrio, ho scelto un mappamondo e masse continentali in scala di grigi. Oltre ai contorni al neon sopra menzionati, ho provato un globo scuro con masse continentali scure su uno sfondo chiaro, che in realtà è molto bello. Tuttavia, il contrasto era troppo basso per essere facilmente leggibile e non si adattava all'atmosfera del progetto, quindi l'ho eliminato.
Un'altra idea che avevo per l'aspetto del globo era renderlo simile alla porcellana smaltata. Non sono riuscito a provarlo perché non sono riuscito a scrivere uno shader per ottenere l'effetto porcellana (sarebbe bello avere un editor di materiali visivi). L'immagine più simile che ho provato è un globo bianco incandescente con masse continentali nere. È carina, ma ha un contrasto troppo elevato. E non sembra molto bello. Un altro da buttare.
Gli shader nei globi in bianco e nero utilizzano una sorta di illuminazione retroilluminata diffusa non reale. La luminosità del globo dipende dalla distanza della superficie normale al piano dello schermo. Di conseguenza, i pixel al centro del globo che puntano verso lo schermo sono scuri e i pixel ai bordi del globo sono chiari. Se abbinato a uno sfondo chiaro, il globo riflette lo sfondo luminoso e diffuso, creando un look elegante da showroom. Il globo nero utilizza anche la texture del globo WebGL come mappa di lucentezza, in modo che le piattaforme continentali (aree con acque poco profonde) appaiano lucide rispetto alle altre parti del globo.
Ecco l'aspetto dello shader dell'oceano per il globo nero. Un vertex shader molto semplice e un frammento shader "oh, sembra carino aggiusta aggiusta".
'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 abbiamo scelto un globo scuro con masse continentali grigio chiaro illuminate dall'alto. Era il più simile al brief di progettazione ed era bello e leggibile. Inoltre, il globo con un contrasto leggermente basso fa risaltare maggiormente gli indicatori e il resto dei contenuti. La versione riportata di seguito 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, dopo aver creato il globo e le masse continentali, ho iniziato a lavorare agli indicatori di luogo. Ho deciso di utilizzare elementi HTML in stile CSS per gli indicatori, per semplificarne la creazione e la definizione dello stile e per poterli potenzialmente riutilizzare nella mappa 2D su cui il team stava lavorando. All'epoca non conoscevo un modo semplice per rendere cliccabili gli indicatori WebGL e non volevo scrivere codice aggiuntivo per il caricamento / la creazione dei modelli di indicatori. A posteriori, gli indicatori CSS hanno funzionato bene, ma tendevano a riscontrare occasionalmente problemi di prestazioni quando i compositori e i renderer del browser erano in periodi di cambiamento. Dal punto di vista del rendimento, sarebbe stata un'opzione migliore utilizzare gli indicatori in WebGL. D'altra parte, gli indicatori CSS hanno risparmiato molto tempo di sviluppo.
Gli indicatori CSS sono costituiti da un paio di elementi div con posizionamento 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 ombreggiatura per distinguersi dallo sfondo. Il problema più grande con gli indicatori era farli funzionare abbastanza bene. Per quanto possa sembrare triste, disegnare alcune dozzine di div che si spostano e cambiano il loro indice z in ogni frame è un ottimo modo per attivare tutti i tipi di problemi di rendering del browser.
Il modo in cui gli indicatori vengono sincronizzati con la scena 3D non è troppo complicato. Ogni indicatore ha un Object3D corrispondente nella scena Three.js, che viene utilizzato per monitorare gli indicatori. Per ottenere le coordinate nello spazio dello schermo, prendo le matrici Three.js per il globo e l'indicatore e moltiplico un vettore zero con queste. Da qui ottengo la posizione nella scena dell'indicatore. Per ottenere la posizione sullo schermo dell'indicatore, proietto la posizione della scena tramite la fotocamera. Il vettore proiettato risultante ha le coordinate dello spazio sullo schermo per l'indicatore, pronte per essere utilizzate 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 l'effetto di svanimento dell'opacità perché attivava un percorso lento su Firefox e mantenere tutti gli indicatori nel DOM, senza rimuoverli quando passavano dietro il globo. Abbiamo anche sperimentato l'utilizzo di trasformazioni 3D anziché di indici z, ma per qualche motivo non funzionavano correttamente nell'app (funzionavano però in un caso di test ridotto, chi lo sa), e a quel punto mancavano solo pochi giorni al lancio, quindi abbiamo dovuto rimandare questa parte alla manutenzione post-lancio.
Quando fai clic su un indicatore, questo si espande in un elenco di nomi di località cliccabili. Si tratta di normale codice DOM HTML, quindi è stato molto facile da scrivere. Tutti i link e il rendering del testo funzionano senza alcun intervento da parte nostra.
Riduci le dimensioni del file
Una volta che la demo era funzionante e collegata al resto del sito delle Meraviglie del mondo, rimaneva ancora un grosso problema da risolvere. La mesh in formato JSON per le masse continentali del globo aveva una dimensione di circa 3 MB. Non adatto alla home page di un sito di vetrina. La buona notizia è che la compressione della mesh con gzip ha ridotto le dimensioni a 350 kB. Ma 350 kB sono comunque un po' troppo. Dopo un paio di email, siamo riusciti a reclutare Won Chun, che si è occupato di comprimere gli enormi mesh di Google Body, per aiutarci a comprimere il mesh. Ha compresso la mesh da un grande elenco piatto di triangoli specificati come coordinate JSON a coordinate compresse a 11 bit con triangoli indicizzati e ha ridotto le dimensioni del file a 95 kB con compressione GZIP.
L'utilizzo di mesh compressi non solo consente di risparmiare larghezza di banda, ma anche di analizzarli più velocemente. Trasformare 3 MB di numeri con stringa in numeri nativi richiede molto più lavoro rispetto all'analisi di 100 KB di dati binari. La riduzione delle dimensioni della pagina di 250 kB risultante è molto utile, oltre a ridurre il tempo di caricamento iniziale a meno di un secondo su una connessione di 2 Mbps. Più veloce e più piccolo, fantastico!
Allo stesso tempo, stavo provando a caricare i shapefile originali di Natural Earth da cui è stato dedotto il mesh di GlobeTweeter. Ho caricato i shapefile, ma per visualizzarli come masse continentali piane è necessario triangularla (ovviamente con i buchi per i laghi). Ho ottenuto le forme triangolate utilizzando gli strumenti di THREE.js, ma non i fori. I mesh risultanti avevano bordi molto lunghi, che richiedevano la suddivisione della mesh in triangoli più piccoli. Per farla breve, non sono riuscito a farlo funzionare in tempo, ma la cosa interessante è che il formato Shapefile compresso ulteriormente avrebbe generato un modello di masse terrestri di 8 kB. Pazienza, magari la prossima volta.
Lavoro futuro
Un aspetto che potrebbe richiedere un po' di lavoro in più è rendere più belle le animazioni degli indicatori. Ora, quando passano sopra l'orizzonte, l'effetto è un po' pacchiano. Inoltre, sarebbe bello avere un'animazione carina per l'apertura dell'indicatore.
Per quanto riguarda le prestazioni, mancano l'ottimizzazione dell'algoritmo di suddivisione della mesh e l'aumento della velocità degli indicatori. A parte questo, va tutto bene. Evviva!
Riepilogo
In questo articolo ho descritto come abbiamo creato il globo 3D per il progetto Mirabilia di Google. Spero che gli esempi ti siano piaciuti e che tu provi a creare il tuo widget personalizzato del globo.