Dünya Harikaları 3D Küre'nin Yapımı

Ilmari Heikkinen

World Wonders 3D yerküresine giriş

Kısa süre önce başlatılan Google World Wonders sitesini WebGL özellikli bir tarayıcıda görüntülediyseniz, ekranın altında dönen bir yerküre fark etmiş olabilirsiniz. Bu makalede yerkürenin nasıl çalıştığı ve onu oluşturmak için neler kullandığımız konusunda bilgi verilmektedir.

Size hızlı bir genel bakış sunmak amacıyla, World Wonders yerküresi, Google Data Arts Ekibi tarafından WebGL Globe'un büyük ölçüde değiştirilmiş bir sürümüdür. Mozilla'nın GlobeTweeter demosunda orijinal yerküreyi aldık, çubuk grafik bitlerini kaldırdık, gölgelendiricileri değiştirdik, süslü tıklanabilir HTML işaretçileri ve Natural Earth kıta geometrisini ekledik (Cedric Pinson'ın sayesinde!) Bunların tümü, sitenin renk şemasıyla eşleşen hoş bir animasyonlu yerküre oluşturmak ve siteye fazladan bir gelişmişlik katmanı eklemek için kullanılır.

Dünya için tasarım planı, Dünya Mirası Alanları'nın üzerine tıklanabilir işaretçilerin yerleştirildiği, hoş görünümlü ve animasyonlu bir haritaya sahip olmaktı. Bunu düşünerek uygun bir şeyler aramaya başladım. Aklınıza ilk gelen, Google Veri Sanatları Ekibi tarafından geliştirilen WebGL Globe oldu. O bir yerküre ve havalı. Başka neye ihtiyacın var, değil mi?

WebGL Globe'u kurma

Yerküre widget'ını yapmanın ilk adımı WebGL Globe'u indirmek ve çalışır duruma getirmekti. WebGL Globe, Google Code'da çevrimiçi durumdadır ve kolayca indirilip çalıştırılabilir. Zip dosyasını indirip çıkarın, cd'den çıkarın ve temel web sunucusunu çalıştırın: python -m SimpleHTTPServer. (Bunda varsayılan olarak UTF-8 olmadığını unutmayın; bunu kullanabilirsiniz.) Şimdi http://localhost:8000/globe/globe.html sitesine giderseniz WebGL Globe'u görürsünüz.

WebGL Globe çalışır duruma geldikten sonra, gereksiz tüm parçaları kesmenin zamanı gelmişti. HTML'yi, kullanıcı arayüzü bitlerini çıkaracak şekilde düzenledim ve yerküre çubuk grafiği kurulum öğelerini yerküre başlatma işlevinden kaldırdım. Bu işlemin sonunda ekranımda sadece çıplak bir WebGL Yerküre'si vardı. Etrafı döndürerek güzel görünmesini sağlayabilirsiniz, ama hepsi bu.

Gereksiz öğeleri kesmek için yerkürenin index.html dosyasındaki tüm kullanıcı arayüzü öğelerini sildim ve başlatma komut dosyasını aşağıdaki gibi düzenledim:

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

Kıta geometrisi ekleme

Kamerayı yerküre yüzeyine yakın tutmak istedik, ancak yerküreyi yakınlaştırarak test ettiğimizde doku çözünürlüğünün eksik olduğunu fark ettik. Yakınlaştırıldığında, WebGL Globe'un dokusu bloklu ve bulanık hale gelir. Daha büyük bir resim kullanabilirdik ancak bu, yerkürenin indirilip yürütülmesini yavaşlatacağından kara parçalarının ve sınırların vektörel temsilini kullanmayı tercih ettik.

Kara parçası geometrisi için, açık kaynak GlobeTweeter demosunu kullandım ve 3D modeli Three.js'ye yükledim. Model yüklendikten ve oluşturulduktan sonra yerküre görünümünü iyileştirmeye başlamanın zamanı gelmişti. İlk sorun, yerkürenin kara parçası modelinin WebGL Yerküre ile aynı hizada olacak kadar küre şeklinde olmamasıydı. Bu yüzden, kara parçası modelini daha küresel hale getiren hızlı bir örgü bölme algoritması yazdım.

Küresel bir kara kütlesi modeliyle, yerküre yüzeyinden biraz uzaktaki bir konuma yerleştirmeyi başardım. Böylece, bir çeşit gölge için altlarında 2 piksellik bir çizgiyle çevrelenmiş yüzen kıtalar oluşturdum. Ayrıca, Tron benzeri bir görünüm elde etmek için neon renkli dış çizgilerle de denemeler yaptım.

Yerkürenin ve kara parçalarının oluşturulmasıyla birlikte yerküre için farklı görünümler denemeye başladım. Sade, tek renkli bir görünüm kullanmak istediğimizden, gri tonlamalı yerkürede ve kara parçalarında takılı kaldım. Yukarıda belirtilen neon dış çizgilere ek olarak, açık arka plan üzerinde koyu renkli kara kütleleri bulunan ve oldukça havalı görünen karanlık bir küre denedim. Ancak kolayca okunamayacak kadar düşük kontrastlı olduğu için projenin verdiği hissi pek iyi yansıtamayıp bu fikri not ettim.

İlk aklıma gelen diğer bir fikrim de yerkürenin sırlı porselen gibi görünmesini sağlamaktı. Porselen görünümlü bir gölgelendirici yazamadığım için bunu deneyemedim (görsel malzeme editörü iyi olurdu). Denediğim en yakın şey, siyah kara parçaları olan bu beyaz parlayan yerküreydi. Şık ama kontrastı fazla. Ayrıca çok da hoş görünmüyor. Hurdalık için bir soru daha.

Siyah ve beyaz yerkürelerdeki gölgelendiriciler, arkadan aydınlatmalı sahte bir ışık kullanırlar. Yerkürenin ışığı, yüzeyin ekran düzlemi arasındaki mesafeye göre normaldir. Yani yerkürenin ortasındaki ekranı gösteren pikseller koyu, yerkürenin kenarlarındaki pikseller ise açık renklidir. Açık renkli bir arka planla bir araya geldiğinde, dünyanın dağılmış parlak arka planı yansıttığında kaliteli bir galeri görünümü elde edebilirsiniz. Siyah yerküre, aynı zamanda WebGL Yerküre dokusunu parlak harita olarak da kullanmaktadır. Böylece, kıta rafları (sığ su alanları) yerkürenin diğer bölümlerine kıyasla daha parlak görünür.

Siyah yerkürenin okyanus gölgelendiricisinin görünümü aşağıda verilmiştir. Çok basit bir köşe gölgelendirici ve düzgün görünen bir ince ayar" parçası gölgelendiricidir.

    '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, yukarıdan aydınlatılan açık gri kara kütlelerinin olduğu karanlık bir küreye gittik. Tasarım özetine çok yakındı, güzel ve okunabilir görünüyordu. Ayrıca, yerkürenin kontrastının biraz düşük olması, işaretçilerin ve içeriğin geri kalanının karşılaştırıldığında daha fazla ön plana çıkmasına neden olur. Aşağıdaki sürüm tamamen siyah okyanusları kullanırken üretim sürümünde koyu gri okyanuslar ve biraz farklı işaretçiler vardır.

İşaretçileri CSS ile oluşturma

İşaretçilerden bahsetmişken, yerkürenin ve kara parçalarının çalıştığı yer işaretleri üzerinde çalışmaya başladım. İşaretçiler için CSS stili HTML öğeleri kullanmaya karar verdim. Böylece işaretçileri daha kolay hale getirip stillerini değiştirebilir ve ekibin üzerinde çalıştığı 2D haritada işaretçileri potansiyel olarak yeniden kullanabilirsiniz. O zamanlar WebGL işaretçilerini tıklanabilir yapmanın kolay bir yolunu da bilmiyordum ve işaretçi modellerini yüklemek / oluşturmak için fazladan kod yazmak istemiyordum. Geriye baktığımızda, CSS işaretçileri iyi çalışıyordu, ancak tarayıcı birleştiricileri ve oluşturucuları değişim dönemlerindeyken zaman zaman performans sorunlarıyla karşılaşma eğilimindeydi. Performans açısından, işaretçileri WebGL'de kullanmak daha iyi bir seçenek olurdu. Ardından, CSS işaretçileri geliştirme süresinden epey tasarruf sağladı.

CSS işaretçileri, CSS dönüşüm özelliğiyle mutlak konumlandırılmış birkaç div öğesinden oluşur. İşaretçilerin arka planı CSS gradyanı, üçgen kısmı ise döndürülmüş bir div öğesidir. İşaretçilerin arka planda belirmesi için küçük bir gölge vardır. İşaretçilerle ilgili en büyük sorun, yeterince iyi performans göstermelerini sağlamaktı. Kulağa çok üzülür gibi görünse de, her karede hareket eden ve Z-endeklerini değiştiren birkaç düzine div çizmek, tarayıcıda görüntülemeyle ilgili her tür sorunu tetiklemenin oldukça iyi bir yoludur.

İşaretçilerin 3D sahneyle senkronize edilme şekli çok karmaşık değildir. Three.js düzeninde her bir işaretçiye karşılık gelen bir Object3D vardır ve bunlar işaretçileri izlemek için kullanılır. Ekran alanı koordinatlarını elde etmek için yerkürenin ve işaretçinin Three.js matrislerini alıp sıfır vektörü bunlarla çarpıyorum. Buradan, işaretçinin sahne konumunu bulurum. İşaretçinin ekrandaki konumunu elde etmek için, sahne konumunu kamera aracılığıyla yansıtırım. Ortaya çıkan öngörülen vektör, CSS'de kullanıma hazır işaretçi için ekran alanı koordinatlarına sahiptir.

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;
}

Sonunda en hızlı yaklaşım, işaretçileri taşımak için CSS dönüştürmelerini kullanmak, Firefox'ta yavaş bir yolu tetiklediği ve yerkürenin arkasına geçtiklerinde tüm işaretçileri DOM'da tuttuğu için opaklık solmasını kullanmamaktı. Ayrıca, Z-endeksleri yerine 3D dönüşümleri kullanmayı da denedik, ancak bir nedenden dolayı uygulamada doğru şekilde çalışmadı (ancak daha az test durumunda işe yaradı, bakın) ve o noktada lansmandan birkaç gün sonra bu kısmı lansman sonrası bakıma bırakmak zorunda kaldık.

Bir işaretçiyi tıkladığınızda, işaretçi tıklanabilir yer adları listesine genişler. Bunların tümü normal HTML DOM öğeleri olduğu için yazmak çok kolaydı. Tüm bağlantılar ve metin oluşturma işlemleri, herhangi bir ekstra işlem yapmanıza gerek kalmadan çalışmaktadır.

Dosya boyutunu sıkma

Demo çalışır ve World Wonders sitesinin geri kalanına bağlanırken hâlâ çözülecek bir büyük sorun vardı. Yerküre kara parçaları için JSON biçimli ağ yaklaşık 3 meg büyüklüğündeydi. Bir vitrin sitesinin ön sayfası için uygun değildir. İşin iyi tarafı, ağın gzip ile sıkıştırılmasıyla 350 kB'a düşürülmesiydi. Ancak 350 KB hâlâ biraz büyük. Birkaç e-posta sonra, büyük Google Gövde örgülerini sıkıştırmak için çalışan Won Chun'ı, ağı sıkıştırma konusunda bize yardımcı olması için işe aldık. JSON koordinatları olarak verilen üçgenlerden oluşan büyük bir listeden ağı sıkıştırarak, dizine eklenmiş üçgenlerin kullanıldığı sıkıştırılmış 11 bitlik koordinatlara dönüştürdü ve dosya boyutunu gzip olarak 95 kB'a düşürdü.

Sıkıştırılmış örgülerin kullanılması yalnızca bant genişliğinden tasarruf etmekle kalmaz, aynı zamanda örgülerin ayrıştırılmasını da daha hızlı sağlar. 3 meg'lik dizeli sayıları yerel sayılara dönüştürmek, yüz kB'lık ikili veriyi ayrıştırmaktan çok daha fazla iş gerektirir. Sayfa için ortaya çıkan 250 kB boyut küçülmesi oldukça şık ve 2 Mb/sn bağlantıda ilk yükleme süresini bir saniyenin altına indiriyor. Daha hızlı ve daha küçük, harika sos!

Aynı zamanda, GlobeTweeter ağının türetildiği orijinal Doğal Dünya Şekil dosyalarını yükleyerek çizim yapıyordum. Şekil dosyalarını yüklemeyi başardım ancak bunları düz kara kütleleri olarak oluşturmak için üçgenleme yapılması gerekiyor (göller için delikler, çukurlar ve). Delikleri değil, ÜÇ.js yardımcı programlarını kullanarak şekilleri üçgenleme işlemi yaptım. Ortaya çıkan örgülerin çok uzun kenarları vardı ve bu da örgünün daha küçük üçgenlere bölünmesi gerektiriyordu. Özetle, bunu zamanında çalıştıramadım. İşin iyi tarafı, daha fazla sıkıştırılan Shapefile biçiminin size 8 kB'lık bir kara parçası modeli alması gerekmesiydi. Ah, o zaman belki bir dahakine.

Gelecekteki çalışmalar

İşaretçi animasyonlarını daha iyi hale getirmek, ekstra iş yapmak işe yarayabilir. Şimdi, izleyiciler ufuk üzerinden ilerlediklerinde bu işlemin etkisi biraz görünmeyen hale geliyor. Buna ek olarak, işaretçi açılışı için havalı bir animasyonun olması iyi olur.

Performans açısından eksik olan iki şey, örgü bölme algoritmasını optimize etmek ve işaretçileri daha hızlı hale getirmektir. Bunun dışında, işler belirsizdir. Yaşasın!

Özet

Bu makalede, Google World Wonders projesi için 3D dünyayı nasıl oluşturduğumuzu açıkladım. Örneklerden memnun kaldığınızı ve kendi özel yerküre widget'ınızı oluşturmayı deneyeceğinizi umuyorum.

Referanslar