दुनिया के अजूबों का 3D ग्लोब बनाना

Ilmari Heikkinen

दुनिया के अजूबों के 3D ग्लोब के बारे में जानकारी

अगर आपने हाल ही में लॉन्च की गई Google World Wonders साइट को वेबजीएल की सुविधा वाले ब्राउज़र पर देखा है, तो हो सकता है कि आपको स्क्रीन पर सबसे नीचे एक शानदार ग्लोब दिखे. इस लेख में बताया गया है कि ग्लोब कैसे काम करता है और इसे बनाने के लिए हमने किन चीज़ों का इस्तेमाल किया है.

आपको कम शब्दों में खास जानकारी देने के लिए, बता दें कि 'दुनिया के अजूबे' ग्लोब, Google की डेटा आर्ट टीम की ओर से WebGL ग्लोब में काफ़ी बदलाव करके बनाया गया है. हमने ओरिजनल ग्लोब लिया, बार ग्राफ़ के हिस्सों को हटा दिया, शेडर बदल दिए, क्लिक किए जा सकने वाले शानदार एचटीएमएल मार्कर जोड़े, और Mozilla के GlobeTweeter डेमो से नेचुरल अर्थ कॉन्टिनेंट की ज्यामिति जोड़ी (सेड्रिक पिंसन का बहुत-बहुत धन्यवाद!) ऐसा करने से, साइट के रंगों के स्कीम से मैच करने वाला एक अच्छा ऐनिमेट किया गया ग्लोब बनता है. साथ ही, साइट को ज़्यादा बेहतर बनाता है.

ग्लोब के डिज़ाइन के लिए, एक ऐसा ऐनिमेशन वाला मैप तैयार करना था जो देखने में अच्छा लगे. साथ ही, इसमें विश्व धरोहर स्थलों के ऊपर क्लिक किए जा सकने वाले मार्कर भी हों. इस बात को ध्यान में रखते हुए, मैंने अपने लिए सही विकल्प ढूंढना शुरू किया. सबसे पहले, हमें Google Data Arts टीम का बनाया गया वेबजीएल ग्लोब याद आया. यह एक ग्लोब है और यह बहुत अच्छा दिखता है. आपको और क्या चाहिए?

WebGL ग्लोब सेट अप करना

ग्लोब विजेट बनाने के लिए, सबसे पहले WebGL ग्लोब डाउनलोड करना और उसे सेट अप करना ज़रूरी था. WebGL ग्लोब, Google Code पर ऑनलाइन उपलब्ध है. इसे डाउनलोड और चलाना आसान है. ज़िप डाउनलोड और निकालें, उसमें cd करें, और एक बुनियादी वेब सर्वर चलाएं: python -m SimpleHTTPServer. (ध्यान दें, इसमें UTF-8 डिफ़ॉल्ट रूप से चालू नहीं होता; इसका इस्तेमाल किया जा सकता है.) अब http://localhost:8000/globe/globe.html पर जाने पर, आपको WebGL ग्लोब दिखेगा.

WebGL ग्लोब के चालू होने के बाद, अब उन सभी हिस्सों को हटाने का समय आ गया था जो ज़रूरी नहीं थे. मैंने यूज़र इंटरफ़ेस (यूआई) के हिस्सों को हटाने के लिए, एचटीएमएल में बदलाव किया है. साथ ही, ग्लोब को शुरू करने वाले फ़ंक्शन से ग्लोब के बार ग्राफ़ सेटअप करने की सुविधा को हटा दिया है. इस प्रोसेस के आखिर में, मेरी स्क्रीन पर एक बहुत ही बुनियादी WebGL ग्लोब था. इसे घुमाया जा सकता है और यह मज़ेदार दिखता है. हालांकि, इसके अलावा इसमें और कुछ नहीं किया जा सकता.

ज़रूरत न पड़ने वाले कॉन्टेंट को हटाने के लिए, मैंने ग्लोब के index.html से सभी यूज़र इंटरफ़ेस (यूआई) एलिमेंट मिटा दिए. साथ ही, शुरुआती स्क्रिप्ट में बदलाव करके इसे इस तरह से दिखाया:

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

महाद्वीप की ज्यामिति जोड़ना

हम चाहते थे कि कैमरा ग्लोब की सतह के करीब हो, लेकिन ग्लोब को ज़ूम इन करके देखने पर, हमें पता चला कि टेक्स्चर का रिज़ॉल्यूशन कम है. ज़ूम इन करने पर, WebGL ग्लोब का टेक्स्चर ब्लॉकी और धुंधला हो जाता है. हम बड़ी इमेज का इस्तेमाल कर सकते थे, लेकिन इससे ग्लोब को डाउनलोड और चलाने में ज़्यादा समय लगता. इसलिए, हमने देशों और सीमाओं को वेक्टर के तौर पर दिखाने का विकल्प चुना.

लैंडमास की ज्यामिति के लिए, मैंने ओपन सोर्स GlobeTweeter डेमो का इस्तेमाल किया. साथ ही, उसमें Three.js में 3D मॉडल लोड किया. मॉडल लोड होने और रेंडर होने के बाद, ग्लोब को बेहतर बनाने का समय आ गया था. पहली समस्या यह थी कि ग्लोब का लैंडमास मॉडल, वेबजीएल ग्लोब के साथ फ़्लश करने के लिए ज़रूरत के मुताबिक गोलाकार नहीं था. इसलिए, मैंने एक ऐसा मेश स्प्लिटिंग एल्गोरिदम लिखा जिससे लैंडमास मॉडल को ज़्यादा गोलाकार बनाया जा सके.

गोलाकार लैंडमास मॉडल की मदद से, मैंने इसे ग्लोब की सतह से थोड़ा ऑफ़सेट किया. साथ ही, 2 पिक्सल की काली लाइन के साथ, फ़्लोटिंग महाद्वीपों की आउटलाइन बनाई, ताकि उन्हें एक तरह की परछाई मिल सके. मैंने ट्रॉन जैसा लुक देने के लिए, नेॉन रंग की आउटलाइन भी आज़माईं.

ग्लोब और लैंडमास रेंडरिंग के साथ, मैंने ग्लोब के अलग-अलग लुक के साथ प्रयोग करना शुरू किया. हम एक साधारण मोनोक्रोम लुक चाहते थे, इसलिए मैंने ग्रेस्केल ग्लोब और लैंडमास का इस्तेमाल किया. ऊपर बताई गई नीयन आउटलाइन के अलावा, मैंने हल्के बैकग्राउंड पर गहरे रंग के लैंडमास के साथ एक गहरे रंग का ग्लोब आज़माया. यह वाकई में बहुत अच्छा लग रहा है. हालांकि, यह आसानी से पढ़ने लायक नहीं था, क्योंकि इसका कंट्रास्ट बहुत कम था. साथ ही, यह प्रोजेक्ट के हिसाब से भी सही नहीं था. इसलिए, मैंने इसे हटा दिया.

ग्लोब के लुक के लिए, मेरे पास एक और आइडिया था. मुझे इसे ग्लेज़्ड पॉर्सिलीन जैसा बनाना था. मैंने इस विकल्प को आज़माने की कोशिश नहीं की, क्योंकि मुझे चीनी मिट्टी की तरह दिखने वाला शेडर लिखने में समस्या आ रही थी. विज़ुअल मटीरियल एडिटर का विकल्प अच्छा होगा. मैंने ज़्यादा से ज़्यादा इस तरह का ग्लोब बनाने की कोशिश की है. इसमें ग्लोब का रंग सफ़ेद है और लैंडमास का रंग काला है. यह काफ़ी अच्छा है, लेकिन इसमें कंट्रास्ट ज़्यादा है. साथ ही, यह बहुत अच्छा नहीं दिखता. इसलिए, एक और वीडियो को स्क्रैप किया जा रहा है.

काले और सफ़ेद रंग के ग्लोब में मौजूद शेडर, एक तरह की बोगस डिफ़्यूज़ बैकलिट लाइटिंग का इस्तेमाल कर रहे हैं. ग्लोब की रोशनी, स्क्रीन प्लेन से सतह के सामान्य हिस्से की दूरी पर निर्भर करती है. इसलिए, ग्लोब के बीच में मौजूद पिक्सल, स्क्रीन की ओर इशारा करते हैं और ग्लोब के किनारों पर मौजूद पिक्सल हल्के होते हैं. हल्के बैकग्राउंड के साथ, आपको ऐसा लुक मिलता है जिसमें ग्लोब, फ़ैल गई चमकदार रोशनी को दिखा रहा है. इससे शोरूम का क्लासी लुक मिलता है. ब्लैक ग्लोब में भी WebGL ग्लोब टेक्सचर का इस्तेमाल, ग्लॉस मैप के तौर पर किया गया है. इससे ग्लोब के दूसरे हिस्सों की तुलना में, महाद्वीप के शेल्फ़ (छोटे पानी वाले इलाके) चमकदार दिखते हैं.

यहां ब्लैक ग्लोब के लिए, ओशन शेडर कैसा दिखता है. बहुत ही बुनियादी वर्टिक्स शेडर और "यह थोड़ा अच्छा लग रहा है बदलाव करें" फ़्रैगमेंट शेडर.

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

आखिर में, हमने गहरे रंग के ग्लोब का इस्तेमाल किया, जिसमें ऊपर से रोशन की गई हल्के स्लेटी रंग की लैंडमास दिखाई गई हैं. यह डिज़ाइन ब्रीफ़ के सबसे करीब था. साथ ही, यह अच्छा और पढ़ने लायक था. इसके अलावा, ग्लोब का कंट्रास्ट कम होने पर, मार्कर और बाकी कॉन्टेंट ज़्यादा बेहतर तरीके से दिखता है. यहां दिए गए वर्शन में, पूरी तरह से काले रंग के महासागरों का इस्तेमाल किया गया है. वहीं, प्रोडक्शन वर्शन में गहरे स्लेटी रंग के महासागर और थोड़े अलग मार्कर हैं.

सीएसएस की मदद से मार्कर बनाना

मार्कर की बात करें, तो ग्लोब और लैंडमास के काम करने के बाद, मैंने प्लेसमार्क पर काम शुरू किया. मैंने मार्कर के लिए सीएसएस स्टाइल वाले एचटीएमएल एलिमेंट का इस्तेमाल करने का फ़ैसला लिया, ताकि मार्कर बनाने और उन्हें स्टाइल करने में आसानी हो. साथ ही, टीम जिस 2D मैप पर काम कर रही थी उसमें मार्कर का फिर से इस्तेमाल किया जा सके. उस समय, मुझे WebGL मार्कर को क्लिक करने लायक बनाने का आसान तरीका भी नहीं पता था. साथ ही, मुझे मार्कर मॉडल लोड करने या बनाने के लिए अतिरिक्त कोड नहीं लिखना था. अब हम यह समझते हैं कि सीएसएस मार्कर अच्छे से काम करते थे. हालांकि, ब्राउज़र कंपोजर और रेंडरर में बदलाव होने पर, कभी-कभी परफ़ॉर्मेंस से जुड़ी समस्याएं आती थीं. परफ़ॉर्मेंस के लिहाज़ से, मार्कर को WebGL में बनाना बेहतर विकल्प होता. इसके अलावा, सीएसएस मार्कर की मदद से डेवलपर को काफ़ी समय की बचत हुई.

सीएसएस मार्कर में, सीएसएस ट्रांसफ़ॉर्म प्रॉपर्टी के साथ एब्सोल्यूट-पोज़िशन वाले कुछ डिव शामिल होते हैं. मार्कर का बैकग्राउंड, सीएसएस ग्रेडिएंट है और मार्कर का त्रिकोण वाला हिस्सा, घुमाया गया div है. मार्कर को बैकग्राउंड से अलग दिखाने के लिए, उनमें एक छोटा ड्रॉप शैडो है. मार्कर की सबसे बड़ी समस्या यह थी कि उन्हें अच्छी तरह से काम करने के लिए तैयार करना. भले ही, यह सुनने में अफ़सोसजनक लगे, लेकिन हर फ़्रेम पर अपने z-index को बदलने और इधर-उधर घूमने वाले कुछ दर्जन डिव का इस्तेमाल करना, ब्राउज़र रेंडरिंग से जुड़ी सभी तरह की समस्याओं को ट्रिगर करने का एक अच्छा तरीका है.

मार्कर को 3D सीन के साथ सिंक करने का तरीका बहुत मुश्किल नहीं है. हर मार्कर के लिए, Three.js सीन में एक Object3D होता है. इसका इस्तेमाल मार्कर को ट्रैक करने के लिए किया जाता है. स्क्रीन स्पेस के निर्देशांक पाने के लिए, मैं ग्लोब और मार्कर के लिए Three.js मैट्रिक्स लेता हूं और उनमें शून्य वेक्टर को गुणा करता हूं. इससे मुझे मार्कर की सीन पोज़िशन मिलती है. मार्कर की स्क्रीन पोज़िशन जानने के लिए, मैं कैमरे की मदद से सीन की पोज़िशन को प्रोजेक्ट करता/करती हूं. प्रोजेक्ट किए गए वेक्टर में मार्कर के लिए स्क्रीन स्पेस के निर्देशांक होते हैं, जो सीएसएस में इस्तेमाल के लिए तैयार होते हैं.

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

आखिर में, मार्कर को एक जगह से दूसरी जगह ले जाने के लिए सीएसएस ट्रांसफ़ॉर्म का इस्तेमाल करना सबसे तेज़ तरीका था. ओपैसिटी फ़ेडिंग का इस्तेमाल नहीं किया गया, क्योंकि यह फ़ायरफ़ॉक्स पर धीमे पाथ को ट्रिगर कर रहा था. साथ ही, सभी मार्कर को DOM में रख रहा था. जब वे ग्लोब के पीछे चले जाते थे, तब उन्हें हटाया नहीं जाता था. हमने z-index के बजाय 3D ट्रांसफ़ॉर्म का इस्तेमाल करने की कोशिश भी की, लेकिन किसी वजह से यह ऐप्लिकेशन में ठीक से काम नहीं किया. हालांकि, यह कम किए गए टेस्ट केस में काम किया. लॉन्च में कुछ ही दिन बचे थे, इसलिए हमें इस हिस्से को लॉन्च के बाद के रखरखाव के लिए छोड़ना पड़ा.

किसी मार्कर पर क्लिक करने पर, वह क्लिक की जा सकने वाली जगहों की सूची में बड़ा हो जाता है. यह सामान्य एचटीएमएल DOM का कॉन्टेंट है. इसलिए, इसे लिखना बहुत आसान था. सभी लिंक और टेक्स्ट रेंडरिंग, बिना किसी अतिरिक्त काम के अपने-आप काम करती है.

फ़ाइल का साइज़ कम करना

डेमो काम कर रहा था और 'दुनिया के अजूबे' साइट के बाकी हिस्सों से कनेक्ट हो गया था. हालांकि, अब भी एक बड़ी समस्या हल करनी थी. ग्लोब के लैंडमास के लिए, JSON फ़ॉर्मैट में मेश का साइज़ करीब तीन एमबी था. शोकेस साइट के होम पेज के लिए सही नहीं है. अच्छी बात यह थी कि gzip की मदद से मेश को कंप्रेस करने पर, इसका साइज़ 350 केबी तक कम हो गया. हालांकि, 350 केबी अब भी थोड़ा ज़्यादा है. कुछ ईमेल के बाद, हमने Won Chun को भर्ती किया. उन्होंने Google Body के बड़े मेश को कंप्रेस करने पर काम किया था. हमने उनसे मेश को कंप्रेस करने में मदद मांगी. उन्होंने मेश को JSON निर्देशांक के तौर पर दिए गए त्रिभुजों की बड़ी फ़्लैट सूची से, इंडेक्स किए गए त्रिभुजों के साथ संपीड़ित 11-बिट निर्देशांक में कम कर दिया. साथ ही, फ़ाइल का साइज़ 95 केबी तक कम कर दिया.

कंप्रेस किए गए मेश का इस्तेमाल करने से, बैंडविड्थ की बचत होती है. साथ ही, मेश को पार्स करने में भी कम समय लगता है. स्ट्रिंग में बदली गई तीन मेगाबाइट की संख्याओं को नेटिव संख्याओं में बदलने में, सौ केबी के बाइनरी डेटा को पार्स करने से ज़्यादा समय लगता है. इससे पेज का साइज़ 250 केबी कम हो गया, जो बहुत अच्छा है. साथ ही, 2 एमबीपीएस के कनेक्शन पर, पेज को लोड होने में एक सेकंड से भी कम समय लगता है. तेज़ और छोटा, शानदार!

साथ ही, मैं Natural Earth की ओरिजनल शेपफ़ाइलें लोड करने की कोशिश कर रहा था. इन शेपफ़ाइलों से GlobeTweeter का मेश बनाया जाता है. मैंने शेपफ़ाइल लोड कर ली हैं, लेकिन उन्हें समतल इलाकों के तौर पर रेंडर करने के लिए, उन्हें ट्राइऐंगल (झीलों के लिए छेदों के साथ) में बांटना ज़रूरी है. मुझे THREE.js की सुविधाओं का इस्तेमाल करके, आकृतियों को त्रिकोणीय आकार में बांटा गया है, लेकिन छिद्रों को नहीं. इससे बनने वाले मेश के किनारे बहुत लंबे थे. इसलिए, मेश को छोटे-छोटे ट्राइऐंगल में बांटना पड़ा. कम शब्दों में बताऊं, तो मुझे समय पर इसे काम नहीं कराने में सफलता नहीं मिली. हालांकि, अच्छी बात यह थी कि ज़्यादा कंप्रेस किए गए शेपफ़ाइल फ़ॉर्मैट से, आपको 8 केबी का लैंडमास मॉडल मिलता. शायद अगली बार.

आने वाले समय में किया जाने वाला काम

मार्कर ऐनिमेशन को बेहतर बनाने के लिए, थोड़ा और काम किया जा सकता है. अब जब वे क्षितिज के ऊपर जाते हैं, तो असर थोड़ा अजीब लगता है. इसके अलावा, मार्कर के खुलने के लिए कोई अच्छा ऐनिमेशन होना चाहिए.

परफ़ॉर्मेंस के हिसाब से, मेश को बांटने वाले एल्गोरिदम को ऑप्टिमाइज़ करना और मार्कर को तेज़ करना, दो ऐसी चीज़ें हैं जिनमें सुधार की ज़रूरत है. इसके अलावा, सब कुछ ठीक है. हुर्रे!

खास जानकारी

इस लेख में मैंने बताया है कि हमने Google के 'दुनिया के अजूबे' प्रोजेक्ट के लिए, 3D ग्लोब कैसे बनाया. हमें उम्मीद है कि आपको ये उदाहरण पसंद आए होंगे और आपने अपना कस्टम ग्लोब विजेट बनाने की कोशिश की होगी.

रेफ़रंस