Dünya Harikaları 3D yerküresine giriş
Yakın zamanda kullanıma sunulan Google Dünya Harikaları sitesini WebGL uyumlu bir tarayıcıda görüntülediyseniz ekranın alt kısmında dönen şık bir yerküre görmüş olabilirsiniz. Bu makalede, kürenin işleyiş şekli ve küreyi oluşturmak için kullandığımız araçlar hakkında bilgi verilmektedir.
Dünya Harikaları küresi, Google Veri Sanatları Ekibi tarafından WebGL kürenin büyük ölçüde değiştirilmiş bir sürümüdür. Orijinal yerküreyi aldık, çubuk grafik parçalarını çıkardık, gölgelendiricileri değiştirdik, tıklanabilir HTML işaretçileri ve Mozilla'nın GlobeTweeter demosundan Natural Earth kıta geometrisini ekledik (Cedric Pinson'a çok teşekkürler!) Tüm bunları, sitenin renk düzeniyle eşleşen ve siteye ek bir sofistike katman ekleyen güzel bir animasyonlu küre oluşturmak için yaparsınız.
Küre için tasarım brief'inde, Dünya Mirası alanlarının üzerine yerleştirilmiş tıklanabilir işaretçiler içeren güzel görünümlü bir animasyonlu harita olması isteniyordu. Bu nedenle, uygun bir şey aramaya başladım. Aklıma ilk gelen, Google Veri Sanatları Ekibi tarafından oluşturulan WebGL Küre oldu. Dünya küresi ve havalı görünüyor. Başka ne gerekiyor?
WebGL küre ayarlama
Yerküre widget'ını oluşturmanın ilk adımı, WebGL yerküresini indirip çalıştırmaktır. WebGL küre, Google Code'da online olarak bulunur ve indirip çalıştırması kolaydır. Zip dosyasını indirip ayıklayın, içine gidin ve temel bir web sunucusu çalıştırın: python -m SimpleHTTPServer
. (Bu sürümde UTF-8'in varsayılan olarak etkin olmadığını unutmayın. Bu sürümden yararlanabilirsiniz.) Artık http://localhost:8000/globe/globe.html
bölümüne gittiğinizde WebGL küresini görebilirsiniz.
WebGL küre hazır ve çalışır durumdayken artık gereksiz tüm parçaları kaldırma zamanı gelmişti. HTML'yi, kullanıcı arayüzü parçalarını çıkarmak için düzenledim ve yerküre çubuk grafiği kurulum öğelerini yerküre başlatma işlevinden kaldırdım. Bu sürecin sonunda ekranımda çok basit bir WebGL küre vardı. Döndürüp bakabilirsiniz, güzel görünür ama bu kadar.
Gereksiz öğeleri çıkarmak için kürenin index.html dosyasından tüm kullanıcı arayüzü öğelerini sildim ve başlatma komut dosyasını şu şekilde düzenledim:
if(!Detector.webgl){
Detector.addGetWebGLMessage();
} else {
var container = document.getElementById('container');
var globe = new DAT.Globe(container);
globe.animate();
}
Kıta geometrisini ekleme
Kameranın yerküre yüzeyine yakın olmasını istedik ancak yerküreyi yakınlaştırarak test ettiğimizde doku çözünürlüğünün yetersiz olduğu ortaya çıktı. WebGL kürenin yakınlaştırılmış hâlinde dokusu bloklu ve bulanık görünür. Daha büyük bir resim kullanabilirdik ancak bu durumda kürenin indirilmesi ve çalıştırılması daha yavaş olurdu. Bu nedenle, kara kütlelerinin ve sınırların vektörel bir temsilini kullanmayı tercih ettik.
Kara kütlesi geometrisi için açık kaynak GlobeTweeter demosuna başvurdum ve bu demodaki 3D modeli Three.js'e yükledim. Model yüklenip oluşturulduktan sonra yerkürenin görünümünü iyileştirmeye başladık. İlk sorun, yerküre kara kütlesi modelinin WebGL yerküresiyle aynı hizada olacak kadar küresel olmamasıydı. Bu nedenle, kara kütlesi modelini daha küresel hale getiren hızlı bir ağ bölme algoritması yazdım.
Küre şeklinde bir kara kütlesi modeli kullanarak, küre yüzeyinden biraz uzakta olacak şekilde yerleştirdim. Böylece, bir tür gölge oluşturmak için altlarında 2 piksel genişliğinde siyah bir çizgiyle ana hatları çizilmiş yüzen kıtalar oluşturdum. Ayrıca Tron'a benzer bir görünüm elde etmek için neon renkli dış çizgilerle de denemeler yaptım.
Yerküre ve kara kütleleri oluşturma işlemiyle birlikte, yerküre için farklı görünümler denemeye başladım. Sade bir tek renkli görünüm tercih ettiğimiz için gri tonlamalı bir yerküre ve kara kütleleri kullandım. Yukarıda bahsedilen neon ana hatlara ek olarak, açık arka planda koyu renkli kara parçaları olan koyu renkli bir küre de denedim. Bu küre gerçekten çok havalı görünüyor. Ancak kolay okunabilmesi için kontrastı çok düşüktü ve projenin havasına uymadığı için bu fikri rafa kaldırdım.
Küre görünümü için ilk aklıma gelen fikirlerden biri, yerküreyi sırlı porselen gibi yapmaktı. Bu yöntemi, porselen görünümü oluşturacak bir gölgelendirici yazamadığım için denemeyi başaramadım (görsel materyal düzenleyici iyi olurdu). En yakınını denediğim şey, siyah kara parçaları olan bu beyaz parlayan küre oldu. Güzel bir resim ama çok yüksek kontrastlı. Ayrıca çok da güzel görünmüyor. Birini daha hurdaya gönderiyoruz.
Siyah beyaz kürelerdeki gölgelendiriciler, sahte dağınık arka ışıklı aydınlatma kullanıyor. Kürenin parlaklığı, yüzeyin ekran düzlemindeki normal mesafesine bağlıdır. Bu nedenle, kürenin ortasında ekrana doğru işaret eden pikseller koyu, kürenin kenarlarındaki pikseller ise açık renktedir. Işıklı bir arka planla birlikte kullanıldığında, kürenin dağınık parlak arka planı yansıttığı ve şık bir showroom görünümü oluşturduğu bir görünüm elde edersiniz. Siyah küre, parlaklık haritası olarak WebGL küre dokusu da kullanır. Böylece kıta rafları (sığ su alanları), kürenin diğer bölgelerine kıyasla parlak görünür.
Siyah küre için okyanus gölgelendiricisinin görünümü aşağıdaki gibidir. Çok basit bir köşe birimi gölgelendirici ve "bu biraz hoş görünüyor tweak tweak" şeklindeki hileli bir parçacık gölgelendirici.
'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')
}
Sonunda, açık gri kara kütlelerinin yukarıdan aydınlatıldığı koyu renkli bir küre kullanmaya karar verdik. Tasarım brief'ine en yakın olan, hoş ve okunaklı görünen bu tasarımı seçtik. Ayrıca, yerkürenin kontrastının biraz düşük olması, işaretçilerin ve içeriğin geri kalanının kıyasla daha belirgin olmasını sağlar. Aşağıdaki sürümde tamamen siyah okyanuslar kullanılırken üretim sürümünde koyu gri okyanuslar ve biraz farklı işaretçiler bulunur.
İşaretleri CSS ile oluşturma
İşaretçilerden bahsetmişken, yerküre ve kara kütleleri çalışırken yer işaretçileri üzerinde çalışmaya başladım. İşaretçileri oluşturup stillendirmeyi kolaylaştırmak ve işaretçileri ekibin üzerinde çalıştığı 2D haritada yeniden kullanmak için işaretçilerde CSS stilinde HTML öğeleri kullanmaya karar verdim. O zamanlar WebGL işaretçilerini tıklanabilir hale getirmenin kolay bir yolunu bilmiyordum ve işaretçi modellerini yüklemek / oluşturmak için ek kod yazmak istemiyordum. CSS işaretçileri iyi çalışıyordu ancak tarayıcı derleyicileri ve oluşturma araçları değişkenlik gösterdiğinde zaman zaman performans sorunlarıyla karşılaşıyordu. Performans açısından, işaretçileri WebGL'de yapmak daha iyi bir seçenek olurdu. Ancak CSS işaretçileri, geliştirici zamanı açısından da oldukça faydalı oldu.
CSS işaretçileri, CSS dönüşüm mülkü ile mutlak konumlandırılmış birkaç div'den oluşur. İşaretçinin arka planı CSS gradyanı, üçgen kısmı ise döndürülmüş bir div'dir. İşaretçilerin arka plandan öne çıkmasını sağlayan küçük bir gölgesi vardır. İşaretçilerle ilgili en büyük sorun, bunların yeterince iyi performans göstermesini sağlamaktı. Ne kadar üzücü görünse de her karede hareket eden ve z-dizinini değiştiren birkaç düzine div çizmek, her türlü tarayıcı oluşturma sorununu tetiklemenin oldukça iyi bir yoludur.
İşaretçilerin 3D sahneyle senkronize edilme şekli çok karmaşık değildir. Her işaretçi, Three.js sahnesinde işaretçileri izlemek için kullanılan bir Object3D'ye sahiptir. Ekran alanı koordinatlarını almak için küre ve işaretçi için Three.js matrislerini alır ve sıfır vektörünü bunlarla çarparım. Bu sayede işaretçinin sahnedeki konumunu öğrenebilirim. İşaretçinin ekrandaki konumunu almak için kamerayı kullanarak sahnenin konumunu yansıtıyorum. Sonuç olarak elde edilen yansıtılan vektör, CSS'de kullanılmaya hazır işaretçi ekran alanı koordinatlarını içerir.
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;
}
Sonuç olarak, en hızlı yaklaşım, işaretçileri taşımak için CSS dönüşümleri kullanmak oldu. Firefox'ta yavaş bir yol tetiklediği için opaklığın soluklaşması kullanılmadı ve tüm işaretçiler, kürenin arkasına geçtiklerinde kaldırılmadan DOM'da tutuldu. Z-dizini yerine 3D dönüştürme kullanmayı da denedik ancak bu yöntem uygulamada bir şekilde düzgün çalışmadı (ancak basitleştirilmiş bir test durumunda çalıştı). Bu sırada lansmana birkaç gün kalmıştı ve bu kısmı lansman sonrası bakıma bırakmamız gerekiyordu.
Bir işaretçiyi tıkladığınızda işaretçi, tıklanabilir yer adlarının listesine dönüşür. Bunların hepsi normal HTML DOM öğeleri olduğu için yazmak çok kolaydı. Tüm bağlantılar ve metin oluşturma işlemleri, bizim herhangi bir işlem yapmamıza gerek kalmadan çalışır.
Dosya boyutunu küçültme
Demo çalışır durumdaydı ve Dünya Harikaları sitesinin geri kalanına bağlanmıştı ancak çözülmesi gereken önemli bir sorun daha vardı. Dünyadaki kara kütleleri için JSON biçimindeki ağ yaklaşık 3 MB boyutundaydı. Bir vitrin sitesinin ön sayfası için uygun değildir. İyi bir haber de, örgünün gzip ile sıkıştırılmasıyla boyutunun 350 KB'a düşürülmüş olmasıydı. Ancak 350 KB yine de biraz büyük. Birkaç e-posta alışverişinden sonra, büyük Google Body ağlarını sıkıştırma konusunda çalışan Won Chun'u ağ sıkıştırma konusunda bize yardımcı olması için işe aldık. Ağı, JSON koordinatları olarak verilen büyük bir düz üçgen listesinden dizine eklenen üçgenler içeren sıkıştırılmış 11 bit koordinatlara sıkıştırdı ve dosya boyutunu sıkıştırılmış halde 95 KB'ya düşürdü.
Sıkıştırılmış ağlar kullanmak, bant genişliğinden tasarruf etmenizi sağlamanın yanı sıra ağların ayrıştırılmasını da hızlandırır. 3 megabaytlık dize halindeki sayıları yerel sayılara dönüştürmek, yüz kilobaytlık ikili veriyi ayrıştırmaktan çok daha fazla iş gerektirir. Sonuç olarak sayfanın boyutu 250 KB azaltılmış ve 2 Mb/sn. bağlantıda ilk yükleme süresi bir saniyenin altına indirilmiştir. Daha hızlı ve daha küçük, harika!
Aynı zamanda, GlobeTweeter örgüsünün türetildiği orijinal Natural Earth Shapefile'leri yüklemeyle ilgili deneme yapıyordum. Şekil dosyalarını yüklemeyi başardım ancak bunları düz kara parçaları olarak oluşturmak için üçgenleme yapmam gerekiyor (elbette göller için deliklerle). Üçgenler halindeki şekilleri THREE.js yardımcı programlarını kullanarak elde ettim ancak delikler oluşturulmadı. Elde edilen ağların çok uzun kenarları vardı. Bu nedenle, ağın daha küçük üçgenlere bölünmesi gerekiyordu. Uzun lafın kısası, bunu zamanında çalıştırmayı başaramadım ancak daha fazla sıkıştırılmış Shapefile biçimi size 8 KB'lık bir kara kütlesi modeli verirdi. Öyleyse bir dahaki sefere.
Gelecekte yapılacak çalışmalar
İşaretçi animasyonları daha güzel hale getirilebilir. Uçaklar ufkun üzerine çıktığında efekt biraz saçma görünüyor. Ayrıca, işaretçi açılırken güzel bir animasyon gösterilebilir.
Performans açısından, ağ bölme algoritmasının optimize edilmesi ve işaretçilerin daha hızlı hale getirilmesi gerekiyor. Bunun dışında her şey yolunda. Yaşasın!
Özet
Bu makalede, Google Dünya Harikaları projesi için 3D küremizi nasıl oluşturduğumuzu anlattık. Örnekleri beğendiğinizi ve kendi özel dünya widget'ınızı oluşturmayı deneyeceğinizi umuyoruz.