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

Ilmari Heikkinen

World Wonders 3D ग्लोब का परिचय

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

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

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

WebGL Globe सेट करना

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

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

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

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

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

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

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

एक गोलाकार लैंडमास मॉडल के साथ, मैं इसे ग्लोब की सतह से थोड़ा सा ऑफ़सेट रख सका. इसके बाद, एक तरह की परछाई के लिए उनके नीचे काले रंग की 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 में मार्कर इस्तेमाल करना एक बेहतर विकल्प होता. फिर, सीएसएस मार्कर ने डेव के समय का अच्छा-खासा समय बचाया.

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

मार्कर का 3D दृश्य के साथ सिंक्रोनाइज़ करने का तरीका बहुत जटिल नहीं है. थ्री.js सीन में हर मार्कर का एक संबंधित Object3D होता है, जिसका इस्तेमाल मार्कर को ट्रैक करने के लिए किया जाता है. स्क्रीन स्पेस निर्देशांक पाने के लिए, मैं ग्लोब और मार्कर के लिए तीन.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;
}

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

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

फ़ाइल का साइज़ छोटा करें

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

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

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

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

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

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

खास जानकारी

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

रेफ़रंस