WebGL ओवरले व्यू की मदद से, 3D मैप का अनुभव तैयार करना

1. शुरू करने से पहले

इस कोडलैब में, आपको Maps JavaScript API की WebGL की मदद से काम करने वाली सुविधाओं का इस्तेमाल करने का तरीका सिखाया जाएगा. इनकी मदद से, वेक्टर मैप को तीन डाइमेंशन में कंट्रोल और रेंडर किया जा सकता है.

फ़ाइनल 3D पिन

ज़रूरी शर्तें

इस कोडलैब में यह माना गया है कि आपको JavaScript और Maps JavaScript API की सामान्य जानकारी है. Maps JS API इस्तेमाल करने के बारे में बुनियादी जानकारी पाने के लिए, अपनी वेबसाइट में मैप जोड़ें (JavaScript) कोडलैब आज़माएं.

आपको क्या सीखने को मिलेगा

  • JavaScript के लिए वेक्टर मैप की सुविधा चालू करके, मैप आईडी जनरेट करना.
  • प्रोग्राम के हिसाब से मैप को झुकाने और घुमाने की सुविधा.
  • WebGLOverlayView और Three.js की मदद से, मैप पर 3D ऑब्जेक्ट रेंडर किए जा रहे हैं.
  • moveCamera की मदद से, कैमरे के मूवमेंट को ऐनिमेट किया जा रहा है.

आपको किन चीज़ों की ज़रूरत होगी

  • बिलिंग की सुविधा वाला Google Cloud Platform खाता
  • Maps JavaScript API की सुविधा वाला Google Maps Platform API पासकोड
  • JavaScript, एचटीएमएल, और सीएसएस की सामान्य जानकारी
  • अपनी पसंद का टेक्स्ट एडिटर या आईडीई
  • Node.js

2. सेट अप करें

नीचे दिए गए चरण को पूरा करने के लिए, आपको Maps JavaScript API को चालू करना होगा.

Google Maps Platform सेट अप करना

अगर आपके पास Google Cloud Platform खाता और बिलिंग की सुविधा वाला प्रोजेक्ट नहीं है, तो बिलिंग की सुविधा वाला खाता और प्रोजेक्ट बनाएं. ऐसा करने का तरीका जानने के लिए, कृपया Google Maps Platform का इस्तेमाल शुरू करना देखें.

  1. Cloud Console में, प्रोजेक्ट वाले ड्रॉप-डाउन मेन्यू पर क्लिक करें. इसके बाद, उस प्रोजेक्ट को चुनें जिसे इस कोडलैब के लिए इस्तेमाल करना है.

  1. इस कोडलैब के लिए ज़रूरी Google Maps Platform API और एसडीके को Google Cloud Marketplace में जाकर चालू करें. ऐसा करने के लिए, इस वीडियो या दस्तावेज़ में बताया गया तरीका अपनाएं.
  2. Cloud Console के क्रेडेंशियल पेज पर जाकर, एक एपीआई पासकोड जनरेट करें. ऐसा करने के लिए, इस वीडियो या दस्तावेज़ में बताया गया तरीका अपनाएं. Google Maps Platform का इस्तेमाल करने के लिए, एपीआई पासकोड ज़रूरी है.

Node.js सेटअप करना

अगर आपके पास पहले से यह नहीं है, तो अपने कंप्यूटर पर Node.js रनटाइम डाउनलोड और इंस्टॉल करने के लिए, https://nodejs.org/ पर जाएं.

Node.js में npm पैकेज मैनेजर होता है. इस कोडलैब के लिए, आपको डिपेंडेंसी इंस्टॉल करनी होंगी.

प्रोजेक्ट स्टार्टर टेंप्लेट डाउनलोड करना

इस कोडलैब को शुरू करने से पहले, स्टार्टर प्रोजेक्ट के टेंप्लेट और पूरे सलूशन कोड को डाउनलोड करने के लिए, यह तरीका अपनाएं:

  1. इस कोडलैब के लिए, GitHub repo को https://github.com/googlecodelabs/maps-platform-101-webgl/ पर डाउनलोड या फ़ोर्क करें. स्टार्टर प्रोजेक्ट, /starter डायरेक्ट्री में मौजूद है. साथ ही, इसमें वह बेसिक फ़ाइल स्ट्रक्चर भी दिया गया है जो कोडलैब को पूरा करने के लिए ज़रूरी है. काम करने के लिए आपको जो भी चीज़ें चाहिए वे सब /starter/src डायरेक्ट्री में मौजूद हैं.
  2. स्टार्टर प्रोजेक्ट डाउनलोड करने के बाद, /starter डायरेक्ट्री में npm install चलाएं. इससे package.json में दी गई सभी ज़रूरी डिपेंडेंसी इंस्टॉल हो जाती हैं.
  3. डिपेंडेंसी इंस्टॉल हो जाने के बाद, डायरेक्ट्री में npm start चलाएं.

आपके लिए स्टार्टर प्रोजेक्ट सेट अप हो जाएगा, ताकि आप webpack-dev-server का इस्तेमाल कर सकें. यह लोकल तौर पर लिखे गए आपके कोड को कंपाइल करता है और चलाता है. इसके साथ-साथ, जब भी कोड में बदलाव किया जाता है, तब webpack-dev-server अपने-आप ब्राउज़र में आपके ऐप्लिकेशन को फिर से लोड कर देता है.

अगर आपको पूरा सलूशन कोड चलता हुआ देखना है, तो /solution डायरेक्ट्री में जाकर, ऊपर दिया गया सेटअप का तरीका अपनाएं.

अपनी एपीआई कुंजी जोड़ना

स्टार्टर ऐप्लिकेशन में, JS API Loader की मदद से मैप लोड करने के लिए ज़रूरी सभी कोड शामिल होते हैं. इसलिए, आपको सिर्फ़ अपना एपीआई पासकोड और मैप आईडी देना होता है. JS API Loader एक सामान्य लाइब्रेरी है. यह script टैग की मदद से, HTML टेंप्लेट में Maps JS API को इनलाइन लोड करने के पारंपरिक तरीके को ऐब्स्ट्रैक्ट करती है. इससे आपको JavaScript कोड में सब कुछ मैनेज करने की सुविधा मिलती है.

अपनी एपीआई कुंजी जोड़ने के लिए, स्टार्टर प्रोजेक्ट में यह तरीका अपनाएं:

  1. app.js खोलें.
  2. apiOptions ऑब्जेक्ट में, अपनी एपीआई पासकोड को apiOptions.apiKey की वैल्यू के तौर पर सेट करें.

3. मैप आईडी जनरेट करना और उसका इस्तेमाल करना

Maps JavaScript API की WebGL पर आधारित सुविधाओं का इस्तेमाल करने के लिए, आपको वेक्टर मैप की सुविधा वाले मैप आईडी की ज़रूरत होगी.

मैप आईडी जनरेट करना

मैप आईडी जनरेट करना

  1. Google Cloud Console में, ‘Google Maps Platform' > ‘मैप मैनेजमेंट' पर जाएं.
  2. ‘नया मैप आईडी बनाएं' पर क्लिक करें.
  3. ‘मैप का नाम' फ़ील्ड में, अपने मैप आईडी के लिए कोई नाम डालें.
  4. ‘मैप टाइप' ड्रॉपडाउन में, ‘JavaScript' चुनें. ‘JavaScript के विकल्प' दिखेंगे.
  5. ‘JavaScript के विकल्प' में जाकर, ‘वेक्टर' रेडियो बटन, ‘झुकाव' चेकबॉक्स, और ‘घुमाव' चेकबॉक्स को चुनें.
  6. Optional. ‘ब्यौरा' फ़ील्ड में, अपनी एपीआई कुंजी के लिए ब्यौरा डालें.
  7. 'आगे बढ़ें' बटन पर क्लिक करें. 'मैप आईडी की जानकारी' पेज दिखेगा.

    मैप की जानकारी वाला पेज
  8. मैप आईडी को कॉपी करें. इसका इस्तेमाल अगले चरण में मैप को लोड करने के लिए किया जाएगा.

मैप आईडी का इस्तेमाल करना

वेक्टर मैप लोड करने के लिए, आपको मैप को इंस्टैंशिएट करते समय, विकल्पों में प्रॉपर्टी के तौर पर मैप आईडी देना होगा. अगर चाहें, तो Maps JavaScript API लोड करते समय भी एक ही मैप आईडी दिया जा सकता है.

मैप आईडी का इस्तेमाल करके मैप लोड करने के लिए, यह तरीका अपनाएं:

  1. mapOptions.mapId की वैल्यू के तौर पर अपना मैप आईडी सेट करें.

    मैप को इंस्टैंशिएट करते समय मैप आईडी देने से, Google Maps Platform को यह पता चलता है कि किसी खास इंस्टेंस के लिए, आपके कौनसे मैप लोड करने हैं. एक ही मैप आईडी का इस्तेमाल, कई ऐप्लिकेशन या एक ही ऐप्लिकेशन में कई व्यू के लिए किया जा सकता है.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

अपने ब्राउज़र में चल रहे ऐप्लिकेशन की जांच करें. झुकाव और रोटेशन की सुविधा वाला वेक्टर मैप लोड होना चाहिए. यह देखने के लिए कि झुकाने और घुमाने की सुविधा चालू है या नहीं, Shift बटन को दबाकर रखें. इसके बाद, माउस से खींचें या कीबोर्ड पर मौजूद ऐरो बटन का इस्तेमाल करें.

अगर मैप लोड नहीं होता है, तो देखें कि आपने apiOptions में मान्य एपीआई पासकोड दिया हो. अगर मैप को झुकाया और घुमाया नहीं जा सकता, तो पक्का करें कि आपने ऐसा मैप आईडी दिया हो जिसमें apiOptions और mapOptions में झुकाने और घुमाने की सुविधा चालू हो.

झुका हुआ मैप

आपकी app.js फ़ाइल अब ऐसी दिखनी चाहिए:

    import { Loader } from '@googlemaps/js-api-loader';

    const apiOptions = {
      "apiKey": 'YOUR_API_KEY',
    };

    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    }

    async function initMap() {
      const mapDiv = document.getElementById("map");
      const apiLoader = new Loader(apiOptions);
      await apiLoader.load();
      return new google.maps.Map(mapDiv, mapOptions);
    }

    function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      // WebGLOverlayView code goes here
    }

    (async () => {
      const map = await initMap();
    })();

4. WebGLOverlayView लागू करना

WebGLOverlayView आपको उसी WebGL रेंडरिंग कॉन्टेक्स्ट का ऐक्सेस देता है जिसका इस्तेमाल वेक्टर बेस मैप को रेंडर करने के लिए किया जाता है. इसका मतलब है कि WebGL के साथ-साथ WebGL पर आधारित लोकप्रिय ग्राफ़िक्स लाइब्रेरी का इस्तेमाल करके, मैप पर सीधे तौर पर 2D और 3D ऑब्जेक्ट रेंडर किए जा सकते हैं.

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

  • onAdd(): इस फ़ंक्शन को तब कॉल किया जाता है, जब WebGLOverlayView इंस्टेंस पर setMap को कॉल करके, मैप में ओवरले जोड़ा जाता है. आपको WebGL से जुड़ा कोई भी ऐसा काम यहीं करना चाहिए जिसके लिए WebGL कॉन्टेक्स्ट का सीधे तौर पर ऐक्सेस ज़रूरी नहीं है.
  • onContextRestored(): इस फ़ंक्शन को तब कॉल किया जाता है, जब WebGL कॉन्टेक्स्ट उपलब्ध हो जाता है, लेकिन इससे पहले कि कुछ भी रेंडर किया जाए. यहां आपको ऑब्जेक्ट शुरू करने, स्थिति को बाइंड करने, और अन्य काम करने चाहिए. इन कामों के लिए, WebGL कॉन्टेक्स्ट का ऐक्सेस ज़रूरी होता है. हालांकि, इन्हें onDraw() कॉल के बाहर किया जा सकता है. इससे, मैप को रेंडर करने के लिए ज़रूरी सभी चीज़ें सेट अप की जा सकती हैं. साथ ही, मैप को रेंडर करने में ज़्यादा समय नहीं लगता. मैप को रेंडर करने में पहले से ही GPU का ज़्यादा इस्तेमाल होता है.
  • onDraw(): इसे हर फ़्रेम में एक बार कॉल किया जाता है. ऐसा तब होता है, जब WebGL मैप और आपके अनुरोध की गई किसी भी अन्य चीज़ को रेंडर करना शुरू कर देता है. आपको onDraw() में कम से कम काम करना चाहिए, ताकि मैप रेंडर करने में परफ़ॉर्मेंस से जुड़ी समस्या न हो.
  • onContextLost(): इस फ़ंक्शन को तब कॉल किया जाता है, जब किसी वजह से WebGL रेंडरिंग कॉन्टेक्स्ट उपलब्ध न हो.
  • onRemove(): इस फ़ंक्शन को तब कॉल किया जाता है, जब WebGLOverlayView इंस्टेंस पर setMap(null) को कॉल करके, मैप से ओवरले हटा दिया जाता है.

इस चरण में, आपको WebGLOverlayView का एक इंस्टेंस बनाना होगा. साथ ही, इसके तीन लाइफ़साइकल हुक लागू करने होंगे: onAdd, onContextRestored, और onDraw. इस कोडलैब के स्टार्टर टेंप्लेट में दिए गए initWebGLOverlayView() फ़ंक्शन में, ओवरले के लिए सभी कोड मैनेज किए जाएंगे. इससे कोड को समझना और उसे फ़ॉलो करना आसान हो जाएगा.

  1. WebGLOverlayView() इंस्टेंस बनाएं.

    ओवरले, Maps JS API के google.maps.WebGLOverlayView में उपलब्ध है. शुरू करने के लिए, initWebGLOverlayView() में यह जोड़कर एक इंस्टेंस बनाएं:
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. लाइफ़साइकल हुक लागू करें.

    लाइफ़साइकल हुक लागू करने के लिए, initWebGLOverlayView() में यह जोड़ें:
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, transformer}) => {};
    
  3. ओवरले इंस्टेंस को मैप में जोड़ें.

    अब ओवरले इंस्टेंस पर setMap() को कॉल करें और मैप को पास करें. इसके लिए, initWebGLOverlayView() में यह कोड जोड़ें:
    webGLOverlayView.setMap(map)
    
  4. initWebGLOverlayView पर कॉल करें.

    आखिरी चरण में, initWebGLOverlayView() को लागू करना होता है. इसके लिए, app.js के सबसे नीचे मौजूद, तुरंत लागू होने वाले फ़ंक्शन में यह कोड जोड़ें:
    initWebGLOverlayView(map);
    

आपका initWebGLOverlayView और तुरंत लागू किया गया फ़ंक्शन अब ऐसा दिखना चाहिए:

    async function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      const webGLOverlayView = new google.maps.WebGLOverlayView();

      webGLOverlayView.onAdd = () => {}
      webGLOverlayView.onContextRestored = ({gl}) => {}
      webGLOverlayView.onDraw = ({gl, transformer}) => {}
      webGLOverlayView.setMap(map);
    }

    (async () => {
      const map = await initMap();
      initWebGLOverlayView(map);
    })();

WebGLOverlayView को लागू करने के लिए, आपको बस इतना ही करना है. इसके बाद, Three.js का इस्तेमाल करके मैप पर 3D ऑब्जेक्ट रेंडर करने के लिए, ज़रूरी सभी चीज़ें सेट अप करें.

5. three.js सीन सेट अप करना

WebGL का इस्तेमाल करना बहुत मुश्किल हो सकता है, क्योंकि इसमें आपको हर ऑब्जेक्ट के सभी पहलुओं को मैन्युअल तरीके से तय करना होता है. इस कोडलैब में, Three.js का इस्तेमाल किया जाएगा. यह एक लोकप्रिय ग्राफ़िक्स लाइब्रेरी है, जो WebGL के ऊपर एक आसान ऐब्स्ट्रैक्शन लेयर उपलब्ध कराती है. Three.js में कई तरह के सुविधा फ़ंक्शन होते हैं. ये WebGL रेंडरर बनाने से लेकर, सामान्य 2D और 3D ऑब्जेक्ट शेप बनाने, कैमरे कंट्रोल करने, ऑब्जेक्ट ट्रांसफ़ॉर्मेशन करने जैसे कई काम करते हैं.

Three.js में तीन बुनियादी ऑब्जेक्ट टाइप होते हैं. इनकी मदद से ही कुछ भी दिखाया जा सकता है:

  • सीन: यह एक "कंटेनर" होता है, जिसमें सभी ऑब्जेक्ट, रोशनी के सोर्स, टेक्सचर वगैरह रेंडर किए जाते हैं और दिखाए जाते हैं.
  • कैमरा: ऐसा कैमरा जो सीन के व्यूपॉइंट को दिखाता है. इसमें कई तरह के कैमरे उपलब्ध हैं. साथ ही, एक सीन में एक या उससे ज़्यादा कैमरे जोड़े जा सकते हैं.
  • रेंडरर: यह एक ऐसा रेंडरर होता है जो सीन में मौजूद सभी ऑब्जेक्ट को प्रोसेस करता है और उन्हें दिखाता है. Three.js में, WebGLRenderer का इस्तेमाल सबसे ज़्यादा किया जाता है. हालांकि, अगर क्लाइंट WebGL के साथ काम नहीं करता है, तो कुछ अन्य फ़ॉलबैक उपलब्ध होते हैं.

इस चरण में, आपको Three.js के लिए ज़रूरी सभी डिपेंडेंसी लोड करनी होंगी. साथ ही, एक बेसिक सीन सेट अप करना होगा.

  1. three.js लोड करें

    इस कोडलैब के लिए, आपको दो डिपेंडेंसी की ज़रूरत होगी: Three.js लाइब्रेरी और GLTF Loader. यह एक ऐसी क्लास है जिसकी मदद से, GL ट्रांसमिशन फ़ॉर्मैट (gLTF) में 3D ऑब्जेक्ट लोड किए जा सकते हैं. Three.js, कई अलग-अलग 3D ऑब्जेक्ट फ़ॉर्मैट के लिए खास लोडर उपलब्ध कराता है. हालांकि, gLTF का इस्तेमाल करने का सुझाव दिया जाता है.

    नीचे दिए गए कोड में, पूरी Three.js लाइब्रेरी इंपोर्ट की गई है. प्रोडक्शन ऐप्लिकेशन में, आपको सिर्फ़ उन क्लास को इंपोर्ट करना चाहिए जिनकी ज़रूरत है. हालांकि, इस कोडलैब के लिए, पूरी लाइब्रेरी इंपोर्ट करें, ताकि चीज़ें आसान रहें. यह भी ध्यान दें कि GLTF लोडर, डिफ़ॉल्ट लाइब्रेरी में शामिल नहीं है. इसे डिपेंडेंसी में अलग पाथ से इंपोर्ट करना होता है. यह वह पाथ है जहां Three.js के सभी लोडर ऐक्सेस किए जा सकते हैं.

    Three.js और GLTF Loader को इंपोर्ट करने के लिए, app.js के सबसे ऊपर यह कोड जोड़ें:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. three.js सीन बनाएं.

    सीन बनाने के लिए, onAdd हुक में यह कोड जोड़कर, Three.js Scene क्लास को इंस्टैंशिएट करें:
    scene = new THREE.Scene();
    
  3. सीन में कैमरा जोड़ें.

    जैसा कि हमने पहले बताया था, कैमरा सीन को देखने के नज़रिए को दिखाता है. साथ ही, यह तय करता है कि Three.js, सीन में मौजूद ऑब्जेक्ट की विज़ुअल रेंडरिंग को कैसे मैनेज करता है. कैमरे के बिना, सीन को "देखा" नहीं जा सकता. इसका मतलब है कि ऑब्जेक्ट नहीं दिखेंगे, क्योंकि उन्हें रेंडर नहीं किया जाएगा.

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

    सीन में पर्सपेक्टिव कैमरा जोड़ने के लिए, onAdd हुक में यह जोड़ें:
    camera = new THREE.PerspectiveCamera();
    
    PerspectiveCamera की मदद से, व्यूपॉइंट बनाने वाले एट्रिब्यूट भी कॉन्फ़िगर किए जा सकते हैं. इनमें नज़दीक और दूर के प्लेन, आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात), और फ़ील्ड ऑफ़ विज़न (एफ़ओवी) शामिल हैं. ये सभी एट्रिब्यूट मिलकर, व्यूइंग फ़्रस्टम बनाते हैं. 3D में काम करते समय, इस कॉन्सेप्ट को समझना ज़रूरी है. हालांकि, यह इस कोडलैब के दायरे से बाहर है. डिफ़ॉल्ट PerspectiveCamera कॉन्फ़िगरेशन का इस्तेमाल किया जा सकता है.
  4. सीन में रोशनी के सोर्स जोड़ें.

    डिफ़ॉल्ट रूप से, Three.js सीन में रेंडर किए गए ऑब्जेक्ट काले रंग के दिखेंगे. भले ही, उन पर कोई भी टेक्सचर लागू किया गया हो. ऐसा इसलिए होता है, क्योंकि Three.js सीन में ऑब्जेक्ट, असल दुनिया में ऑब्जेक्ट के काम करने के तरीके की नकल करते हैं. असल दुनिया में, किसी ऑब्जेक्ट का रंग इस बात पर निर्भर करता है कि उस पर पड़ने वाली रोशनी कितनी रिफ़्लेक्ट हो रही है. इसका मतलब है कि कोई रोशनी नहीं, कोई रंग नहीं.

    Three.js, अलग-अलग तरह की लाइट उपलब्ध कराता है. इनमें से दो लाइट का इस्तेमाल किया जाएगा:

  5. AmbientLight: यह एक ऐसा लाइट सोर्स है जो सभी ऑब्जेक्ट को हर ऐंगल से एक जैसा दिखाता है. इससे सीन में रोशनी की एक तय मात्रा मिल जाएगी, ताकि सभी ऑब्जेक्ट पर मौजूद टेक्सचर दिखें.
  6. DirectionalLight: सीन में किसी दिशा से आने वाली रोशनी दिखाता है. असल दुनिया में, किसी जगह पर रखी गई लाइट से निकलने वाली रोशनी की किरणें फैलती हैं. हालांकि, DirectionalLight से निकलने वाली रोशनी की सभी किरणें एक-दूसरे के समानांतर होती हैं. साथ ही, ये लाइट सोर्स से दूर जाने पर फैलती और बिखरती नहीं हैं.

    रोशनी के इफ़ेक्ट बनाने के लिए, हर लाइट के रंग और उसकी चमक को कॉन्फ़िगर किया जा सकता है. उदाहरण के लिए, नीचे दिए गए कोड में, ऐंबियंट लाइट पूरे सीन के लिए हल्की सफ़ेद रोशनी देती है. वहीं, डायरेक्शनल लाइट एक सेकंडरी लाइट है, जो ऑब्जेक्ट पर नीचे की ओर से पड़ती है. डायरेक्शनल लाइट के मामले में, position.set(x, y ,z) का इस्तेमाल करके ऐंगल सेट किया जाता है. इसमें हर वैल्यू, संबंधित ऐक्सिस के हिसाब से होती है. इसलिए, उदाहरण के लिए, position.set(0,1,0) लाइट को सीधे तौर पर सीन के ऊपर y-ऐक्सिस पर रखेगा, जो सीधे नीचे की ओर पॉइंट करेगा.

    सीन में लाइट सोर्स जोड़ने के लिए, onAdd हुक में यह कोड जोड़ें:
    const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);
    

आपका onAdd हुक अब ऐसा दिखना चाहिए:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
      scene.add(ambientLight);
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);
    }

आपका सीन अब सेट अप हो गया है और रेंडर करने के लिए तैयार है. इसके बाद, WebGL रेंडरर को कॉन्फ़िगर करें और सीन को रेंडर करें.

6. सीन को रेंडर करना

अब सीन को रेंडर करने का समय आ गया है. अब तक, आपने Three.js की मदद से जो भी बनाया है उसे कोड में शुरू किया गया है. हालांकि, यह अभी तक मौजूद नहीं है, क्योंकि इसे WebGL रेंडरिंग कॉन्टेक्स्ट में रेंडर नहीं किया गया है. WebGL, कैनवस एपीआई का इस्तेमाल करके, ब्राउज़र में 2D और 3D कॉन्टेंट रेंडर करता है. अगर आपने पहले Canvas API का इस्तेमाल किया है, तो आपको शायद एचटीएमएल कैनवस के context के बारे में पता होगा. इसी में सब कुछ रेंडर किया जाता है. आपको शायद यह नहीं पता होगा कि यह एक ऐसा इंटरफ़ेस है जो ब्राउज़र में WebGLRenderingContext API के ज़रिए OpenGL ग्राफ़िक्स रेंडरिंग कॉन्टेक्स्ट को दिखाता है.

WebGL रेंडरर को मैनेज करना आसान बनाने के लिए, Three.js WebGLRenderer उपलब्ध कराता है. यह एक रैपर है. इससे WebGL रेंडरिंग कॉन्टेक्स्ट को कॉन्फ़िगर करना आसान हो जाता है, ताकि Three.js ब्राउज़र में सीन रेंडर कर सके. हालांकि, मैप के मामले में, सिर्फ़ मैप के साथ ब्राउज़र में Three.js सीन को रेंडर करना काफ़ी नहीं है. Three.js को मैप के रेंडरिंग कॉन्टेक्स्ट में ही रेंडर करना होगा, ताकि मैप और Three.js सीन के सभी ऑब्जेक्ट एक ही वर्ल्ड स्पेस में रेंडर किए जा सकें. इससे रेंडरर, मैप पर मौजूद ऑब्जेक्ट और सीन में मौजूद ऑब्जेक्ट के बीच इंटरैक्शन को मैनेज कर पाता है. जैसे, ऑक्लूज़न. ऑक्लूज़न का मतलब है कि कोई ऑब्जेक्ट, अपने पीछे मौजूद ऑब्जेक्ट को छिपा देगा.

यह बहुत मुश्किल लगता है, है न? खुशी की बात है कि Three.js एक बार फिर हमारी मदद के लिए तैयार है.

  1. WebGL रेंडरर सेट अप करें.

    Three.js WebGLRenderer का नया इंस्टेंस बनाते समय, उसे वह WebGL रेंडरिंग कॉन्टेक्स्ट दिया जा सकता है जिसमें आपको अपनी सीन रेंडर करनी है. क्या आपको gl आर्ग्युमेंट याद है जिसे onContextRestored हुक में पास किया जाता है? वह gl ऑब्जेक्ट, मैप का WebGL रेंडरिंग कॉन्टेक्स्ट है. आपको सिर्फ़ WebGLRenderer इंस्टेंस को कॉन्टेक्स्ट, कैनवस, और एट्रिब्यूट देने होंगे. ये सभी gl ऑब्जेक्ट के ज़रिए उपलब्ध होते हैं. इस कोड में, रेंडरर की autoClear प्रॉपर्टी को भी false पर सेट किया गया है, ताकि रेंडरर हर फ़्रेम में अपना आउटपुट न मिटाए.

    रेंडरर को कॉन्फ़िगर करने के लिए, onContextRestored हुक में यह जोड़ें:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. सीन को रेंडर करें.

    रेंडरर कॉन्फ़िगर होने के बाद, WebGLOverlayView इंस्टेंस पर requestRedraw को कॉल करें. इससे ओवरले को यह पता चलेगा कि अगला फ़्रेम रेंडर होने पर, फिर से ड्रॉ करने की ज़रूरत है. इसके बाद, रेंडरर पर render को कॉल करें और उसे रेंडर करने के लिए Three.js सीन और कैमरा पास करें. आखिर में, WebGL रेंडरिंग कॉन्टेक्स्ट की स्थिति मिटाएं. जीएल स्टेट के टकराव से बचने के लिए, यह एक ज़रूरी चरण है. ऐसा इसलिए, क्योंकि WebGL ओवरले व्यू का इस्तेमाल, शेयर की गई जीएल स्टेट पर निर्भर करता है. अगर हर ड्रॉ कॉल के आखिर में स्थिति रीसेट नहीं की जाती है, तो GL की स्थिति में टकराव की वजह से रेंडरर काम नहीं कर सकता.

    इसके लिए, onDraw हुक में यह जोड़ें, ताकि इसे हर फ़्रेम में लागू किया जा सके:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

अब आपके onContextRestored और onDraw हुक, कुछ ऐसे दिखने चाहिए:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

7. मैप पर 3D मॉडल रेंडर करना

ठीक है, आपने सभी ज़रूरी जानकारी दे दी है. आपने WebGl Overlay View सेट अप कर लिया है और Three.js सीन बना लिया है. हालांकि, इसमें एक समस्या है: इसमें कुछ भी नहीं है. इसके बाद, सीन में 3D ऑब्जेक्ट को रेंडर करने का समय आता है. इसके लिए, आपको पहले इंपोर्ट किए गए GLTF Loader का इस्तेमाल करना होगा.

3D मॉडल कई अलग-अलग फ़ॉर्मैट में उपलब्ध होते हैं. हालांकि, Three.js के लिए gLTF फ़ॉर्मैट को प्राथमिकता दी जाती है. इसकी वजह यह है कि यह फ़ॉर्मैट, साइज़ और रनटाइम परफ़ॉर्मेंस के हिसाब से बेहतर होता है. इस कोडलैब में, सीन में रेंडर करने के लिए एक मॉडल पहले से ही /src/pin.gltf में दिया गया है.

  1. मॉडल लोडर इंस्टेंस बनाएं.

    onAdd में यह जोड़ें:
    loader = new GLTFLoader();
    
  2. कोई 3D मॉडल लोड करें.

    मॉडल लोडर एसिंक्रोनस होते हैं. ये मॉडल पूरी तरह से लोड होने के बाद, कॉलबैक को एक्ज़ीक्यूट करते हैं. pin.gltf को लोड करने के लिए, onAdd में यह जोड़ें:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. मॉडल को सीन में जोड़ें.

    अब मॉडल को सीन में जोड़ा जा सकता है. इसके लिए, loader कॉलबैक में यह कोड जोड़ें. ध्यान दें कि gltf नहीं, बल्कि gltf.scene जोड़ा जा रहा है:
    scene.add(gltf.scene);
    
  4. कैमरे के प्रोजेक्शन मैट्रिक्स को कॉन्फ़िगर करें.

    मैप पर मॉडल को सही तरीके से रेंडर करने के लिए, आपको Three.js सीन में कैमरे की प्रोजेक्शन मैट्रिक्स सेट करनी होगी. प्रोजेक्शन मैट्रिक्स को Three.js Matrix4 ऐरे के तौर पर तय किया जाता है. यह तीन डाइमेंशन वाले स्पेस में किसी पॉइंट को ट्रांसफ़ॉर्मेशन के साथ तय करता है. जैसे, रोटेशन, शियर, स्केल वगैरह.

    WebGLOverlayView के मामले में, प्रोजेक्शन मैट्रिक्स का इस्तेमाल रेंडरर को यह बताने के लिए किया जाता है कि बेस मैप के हिसाब से, Three.js सीन को कहां और कैसे रेंडर करना है. हालांकि, इसमें एक समस्या है. मैप पर मौजूद जगहों को अक्षांश और देशांतर के कोऑर्डिनेट पेयर के तौर पर दिखाया जाता है. वहीं, Three.js सीन में मौजूद जगहों को Vector3 कोऑर्डिनेट के तौर पर दिखाया जाता है. जैसा कि आपने अनुमान लगाया होगा, इन दोनों सिस्टम के बीच कन्वर्ज़न का हिसाब लगाना आसान नहीं है. इस समस्या को हल करने के लिए, WebGLOverlayView, OnDraw लाइफ़साइकल हुक को coordinateTransformer ऑब्जेक्ट पास करता है. इसमें fromLatLngAltitude नाम का फ़ंक्शन होता है. fromLatLngAltitude, LatLngAltitude या LatLngAltitudeLiteral ऑब्जेक्ट लेता है. साथ ही, यह सीन के लिए ट्रांसफ़ॉर्मेशन तय करने वाले आर्ग्युमेंट का एक सेट भी लेता है. इसके बाद, यह उन्हें आपके लिए मॉडल व्यू प्रोजेक्शन (एमवीपी) मैट्रिक्स में बदल देता है. आपको बस यह बताना होगा कि मैप पर Three.js सीन को कहां रेंडर करना है. साथ ही, यह भी बताना होगा कि आपको इसे कैसे बदलना है. इसके बाद, WebGLOverlayView बाकी काम अपने-आप कर देगा. इसके बाद, MVP मैट्रिक्स को Three.js Matrix4 ऐरे में बदला जा सकता है. साथ ही, कैमरा प्रोजेक्शन मैट्रिक्स को इस पर सेट किया जा सकता है.

    नीचे दिए गए कोड में, दूसरा आर्ग्युमेंट WebGl Overlay View को बताता है कि Three.js सीन की ऊंचाई को ज़मीन से 120 मीटर ऊपर सेट करना है. इससे मॉडल तैरता हुआ दिखेगा.

    कैमरा प्रोजेक्शन मैट्रिक्स सेट करने के लिए, onDraw हुक में यह जोड़ें:
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 120
    }
    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
  5. मॉडल को ट्रांसफ़ॉर्म करना.

    आपको दिखेगा कि पिन, मैप के लंबवत नहीं है. 3D ग्राफ़िक में, वर्ल्ड स्पेस के अपने x, y, और z ऐक्सिस होते हैं. ये ऐक्सिस, ओरिएंटेशन तय करते हैं. इसके अलावा, हर ऑब्जेक्ट का अपना ऑब्जेक्ट स्पेस भी होता है, जिसमें ऐक्सिस का एक अलग सेट होता है.

    इस मॉडल के मामले में, इसे इस तरह से नहीं बनाया गया है कि पिन का ‘ऊपरी' हिस्सा y-ऐक्सिस की ओर हो. इसलिए, आपको ऑब्जेक्ट को ट्रांसफ़ॉर्म करना होगा, ताकि इसे दुनिया के स्पेस के हिसाब से सही तरीके से ओरिएंट किया जा सके. इसके लिए, इस पर rotation.set कॉल करें. ध्यान दें कि Three.js में रोटेशन को डिग्री में नहीं, बल्कि रेडियन में बताया जाता है. आम तौर पर, डिग्री में सोचना आसान होता है. इसलिए, degrees * Math.PI/180 फ़ॉर्मूले का इस्तेमाल करके सही कन्वर्ज़न करना ज़रूरी है.

    इसके अलावा, मॉडल थोड़ा छोटा है. इसलिए, scale.set(x, y ,z) को कॉल करके, इसे सभी ऐक्सिस पर एक जैसा स्केल करें.

    मॉडल को घुमाने और उसका साइज़ बदलने के लिए, onAdd before scene.add(gltf.scene) के loader कॉलबैक में यह जोड़ें. इससे gLTF को सीन में जोड़ा जाता है:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

अब पिन, मैप के हिसाब से सीधा दिखता है.

अपराइट पिन

अब आपके onAdd और onDraw हुक, कुछ ऐसे दिखने चाहिए:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light
      scene.add( ambientLight );
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);

      loader = new GLTFLoader();
      const source = 'pin.gltf';
      loader.load(
        source,
        gltf => {
          gltf.scene.scale.set(25,25,25);
          gltf.scene.rotation.x = 180 * Math.PI/180;
          scene.add(gltf.scene);
        }
      );
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 100
      }

      const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
      camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);

      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

अब बात करते हैं कैमरे के ऐनिमेशन की!

8. कैमरे को ऐनिमेट करना

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

  1. मॉडल के लोड होने का इंतज़ार करें.

    उपयोगकर्ताओं को बेहतर अनुभव देने के लिए, gLTF मॉडल लोड होने के बाद ही कैमरे को मूव करना शुरू करें. इसके लिए, लोडर के onLoad इवेंट हैंडलर को onContextRestored हुक में जोड़ें:
    loader.manager.onLoad = () => {}
    
  2. ऐनिमेशन लूप बनाएं.

    ऐनिमेशन लूप बनाने के एक से ज़्यादा तरीके हैं. जैसे, setInterval या requestAnimationFrame का इस्तेमाल करना. इस मामले में, आपको Three.js रेंडरर के setAnimationLoop फ़ंक्शन का इस्तेमाल करना होगा. यह फ़ंक्शन, Three.js के हर बार नया फ़्रेम रेंडर करने पर, कॉलबैक में एलान किए गए किसी भी कोड को अपने-आप कॉल करेगा. ऐनिमेशन लूप बनाने के लिए, पिछले चरण में onLoad इवेंट हैंडलर में यह कोड जोड़ें:
    renderer.setAnimationLoop(() => {});
    
  3. ऐनिमेशन लूप में कैमरे की पोज़िशन सेट करें.

    इसके बाद, मैप को अपडेट करने के लिए moveCamera पर कॉल करें. यहां, मैप को लोड करने के लिए इस्तेमाल किए गए mapOptions ऑब्जेक्ट की प्रॉपर्टी का इस्तेमाल, कैमरे की पोज़िशन तय करने के लिए किया जाता है:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. हर फ़्रेम के लिए कैमरे को अपडेट करें.

    आखिरी चरण! हर फ़्रेम के आखिर में mapOptions ऑब्जेक्ट को अपडेट करें, ताकि अगले फ़्रेम के लिए कैमरे की पोज़िशन सेट की जा सके. इस कोड में, if स्टेटमेंट का इस्तेमाल करके, कैमरे को तब तक झुकाया जाता है, जब तक कि वह 67.5 डिग्री तक न पहुंच जाए. इसके बाद, हर फ़्रेम में हेडिंग को थोड़ा-थोड़ा बदला जाता है. ऐसा तब तक किया जाता है, जब तक कैमरा पूरा 360 डिग्री घूम न जाए. जब ऐनिमेशन पूरा हो जाता है, तब null को setAnimationLoop पास किया जाता है, ताकि ऐनिमेशन को रद्द किया जा सके. इससे ऐनिमेशन हमेशा नहीं चलता रहता.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

आपका onContextRestored हुक अब ऐसा दिखना चाहिए:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;

      loader.manager.onLoad = () => {
        renderer.setAnimationLoop(() => {
           map.moveCamera({
            "tilt": mapOptions.tilt,
            "heading": mapOptions.heading,
            "zoom": mapOptions.zoom
          });

          if (mapOptions.tilt < 67.5) {
            mapOptions.tilt += 0.5
          } else if (mapOptions.heading <= 360) {
            mapOptions.heading += 0.2;
          } else {
            renderer.setAnimationLoop(null)
          }
        });
      }
    }

9. बधाई हो

अगर सब कुछ प्लान के मुताबिक हुआ, तो अब आपके पास एक ऐसा मैप होना चाहिए जिसमें बड़ा 3D पिन हो. यह पिन कुछ इस तरह दिखेगा:

फ़ाइनल 3D पिन

आपने क्या सीखा

इस कोडलैब में, आपने कई चीज़ों के बारे में जाना. यहां उनकी खास बातें दी गई हैं:

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

आगे क्या करना है?

WebGL और कंप्यूटर ग्राफ़िक्स, एक मुश्किल विषय है. इसलिए, इसमें हमेशा कुछ न कुछ नया सीखने को मिलता है. यहां कुछ संसाधन दिए गए हैं जिनकी मदद से, आप माइग्रेशन की प्रोसेस शुरू कर सकते हैं: