वर्चुअल ऑब्जेक्ट को असल दुनिया के व्यू में दिखाना

Hit Test API की मदद से, वर्चुअल आइटम को असल दुनिया के व्यू में रखा जा सकता है.

Joe Medley
Joe Medley

WebXR Device API, पिछले साल की फ़ॉल सीज़न में Chrome 79 के साथ लॉन्च हुआ था. जैसा कि हमने बताया था कि Chrome में एपीआई को लागू करने का काम जारी है. Chrome को यह बताते हुए खुशी हो रही है कि कुछ काम पूरे हो चुके हैं. Chrome 81 में दो नई सुविधाएं जोड़ी गई हैं:

इस लेख में WebXR हिट टेस्ट एपीआई के बारे में बताया गया है. इसका मतलब है कि वर्चुअल ऑब्जेक्ट को असल दुनिया के कैमरे के व्यू में रखा जाता है.

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

इमर्सिव एआर सेशन का सैंपल

इस लेख में दिया गया कोड, इमर्सिव वेब वर्किंग ग्रुप के हिट टेस्ट सैंपल (डेमो, सोर्स) पर आधारित है. हालांकि, यह कोड उससे पूरी तरह मेल नहीं खाता. इस उदाहरण में, असल दुनिया में किसी सतह पर वर्चुअल सूरजमुखी लगाने का तरीका बताया गया है.

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

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

दीवार पर रेंडर किया गया रेटिकल, जिसका कॉन्टेक्स्ट के हिसाब से लेक्स या स्ट्रिक्ट हो सकता है
रेटिकल कुछ समय के लिए दिखाई जाने वाली इमेज होती है. यह किसी वस्तु को ऑगमेंटेड रिएलिटी (एआर) में रखने में मदद करती है.

रेटिकल बनाना

आपको खुद ही रेटिकल इमेज बनानी होगी, क्योंकि इसे ब्राउज़र या एपीआई से नहीं दिया जाता. इसे लोड करने और ड्रॉ करने का तरीका, फ़्रेमवर्क के हिसाब से अलग-अलग होता है. अगर सीधे WebGL या WebGL2 का इस्तेमाल करके ड्रॉ नहीं किया जा रहा है, तो अपने फ़्रेमवर्क के दस्तावेज़ देखें. इस वजह से, हम सैंपल में रेटिकल को कैसे खींचा गया है, इस बारे में ज़्यादा जानकारी नहीं देंगे. मैंने यहां सिर्फ़ एक वजह से इसकी एक लाइन दिखाई है: ताकि बाद में दिए जाने वाले कोड के सैंपल में, आपको पता चल सके कि reticle वैरिएबल का इस्तेमाल करते समय, मेरा क्या मतलब है.

let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});

सेशन का अनुरोध करना

सेशन का अनुरोध करते समय, आपको requiredFeatures कलेक्शन में 'hit-test' का अनुरोध करना होगा, जैसा कि यहां दिखाया गया है.

navigator.xr.requestSession('immersive-ar', {
  requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
  // Do something with the session
});

सेशन में शामिल होना

पिछले लेखों में, मैंने XR सेशन में शामिल होने के लिए कोड दिया है. मैंने कुछ बदलावों के साथ, इसका एक वर्शन यहां दिखाया है. सबसे पहले, मैंने select इवेंट Listener जोड़ा है. जब उपयोगकर्ता स्क्रीन पर टैप करता है, तो कैमरे के व्यू में एक फूल दिखेगा. यह फूल, रेटिकल के पोज़ के आधार पर दिखेगा. हम उस इवेंट लिसनर के बारे में बाद में बताएंगे.

function onSessionStarted(xrSession) {
  xrSession.addEventListener('end', onSessionEnded);
  xrSession.addEventListener('select', onSelect);

  let canvas = document.createElement('canvas');
  gl = canvas.getContext('webgl', { xrCompatible: true });

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(session, gl)
  });

  xrSession.requestReferenceSpace('viewer').then((refSpace) => {
    xrViewerSpace = refSpace;
    xrSession.requestHitTestSource({ space: xrViewerSpace })
    .then((hitTestSource) => {
      xrHitTestSource = hitTestSource;
    });
  });

  xrSession.requestReferenceSpace('local').then((refSpace) => {
    xrRefSpace = refSpace;
    xrSession.requestAnimationFrame(onXRFrame);
  });
}

एक से ज़्यादा रेफ़रंस स्पेस

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

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

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

इमेज ड्रॉ करने के लिए, मैं local रेफ़रंस स्पेस का इस्तेमाल करता हूं. इससे मुझे आस-पास के माहौल के हिसाब से स्थिरता मिलती है. यह जानकारी मिलने के बाद, मैंने requestAnimationFrame() को कॉल करके फ़्रेम लूप शुरू किया.

हिट टेस्टिंग के लिए, हम viewer रेफ़रंस स्पेस का इस्तेमाल करते हैं. यह हिट टेस्ट के समय डिवाइस के पोज़ पर आधारित होता है. इस संदर्भ में, 'दर्शक' लेबल थोड़ा भ्रमित करने वाला है, क्योंकि हम कंट्रोलर के बारे में बात कर रहे हैं. अगर कंट्रोलर को इलेक्ट्रॉनिक व्यूअर के तौर पर देखा जाए, तो यह समझ आता है. यह जानकारी मिलने के बाद, xrSession.requestHitTestSource() को कॉल किया जाता है. इससे हिट टेस्ट के डेटा का सोर्स बनता है, जिसका इस्तेमाल ड्रॉइंग करते समय किया जाएगा.

फ़्रेम लूप चलाना

हिट टेस्टिंग को हैंडल करने के लिए, requestAnimationFrame() कॉलबैक को नया कोड भी मिलता है.

डिवाइस को हिलाने पर, रेटिकल को भी उसके साथ हिलना चाहिए, ताकि वह सतहों को ढूंढ सके. किसी ऑब्जेक्ट के हिलने का भ्रम पैदा करने के लिए, हर फ़्रेम में रेटिकल को फिर से बनाएं. हालांकि, अगर हिट टेस्ट पूरा नहीं होता है, तो रेटिकल न दिखाएं. इसलिए, मैंने पहले बनाए गए रेटिकल के लिए, उसकी visible प्रॉपर्टी को false पर सेट किया.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

एआर में कुछ भी बनाने के लिए, मुझे यह जानना होगा कि दर्शक कहां है और वह कहां देख रहा है. इसलिए, मैं यह जांच करता/करती हूं कि hitTestSource और xrViewerPose अब भी मान्य हैं या नहीं.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

अब मैं getHitTestResults() को कॉल करूँ. यह hitTestSource को आर्ग्युमेंट के तौर पर लेता है और HitTestResult के इंस्टेंस का कलेक्शन दिखाता है. हिट टेस्ट में कई प्लैटफ़ॉर्म मौजूद हो सकते हैं. कलेक्शन में मौजूद पहला एलिमेंट, कैमरे के सबसे नज़दीक होता है. ज़्यादातर मामलों में आपको इसका इस्तेमाल करना होगा, लेकिन इस्तेमाल के बेहतर उदाहरणों के लिए, एक कलेक्शन दिखाया जाता है. उदाहरण के लिए, मान लें कि आपका कैमरा फ़्लोर पर मौजूद टेबल पर रखे बॉक्स पर फ़ोकस कर रहा है. ऐसा भी हो सकता है कि हिट टेस्ट, कलेक्शन में तीनों प्लैटफ़ॉर्म दिखाएगा. ज़्यादातर मामलों में, यह वह बॉक्स होगा जिस पर मुझे ध्यान देना है. अगर दिखाए गए कलेक्शन की लंबाई 0 है, यानी कोई हिट टेस्ट नहीं दिखाया गया है, तो आगे बढ़ें. अगले फ़्रेम में फिर से कोशिश करें.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.visible = true;
      reticle.matrix = pose.transform.matrix;
    }
  }

  // Draw to the screen
}

आखिर में, मुझे हिट टेस्ट के नतीजों को प्रोसेस करना होगा. बुनियादी प्रोसेस यह है. हिट टेस्ट के नतीजे से कोई पोज़ पाएं, रीटिकल इमेज को हिट टेस्ट की पोज़िशन पर ट्रांसफ़ॉर्म (मूव) करें, फिर उसकी visible प्रॉपर्टी को 'सही' पर सेट करें. पोज़ से किसी सतह पर मौजूद बिंदु के पोज़ के बारे में पता चलता है.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);

  reticle.visible = false;

  // Reminder: the hitTestSource was acquired during onSessionStart()
  if (xrHitTestSource && xrViewerPose) {
    let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
    if (hitTestResults.length > 0) {
      let pose = hitTestResults[0].getPose(xrRefSpace);
      reticle.matrix = pose.transform.matrix;
      reticle.visible = true;

    }
  }

  // Draw to the screen
}

कोई ऑब्जेक्ट डालना

जब उपयोगकर्ता स्क्रीन पर टैप करता है, तो किसी ऑब्जेक्ट को एआर में रखा जाता है. मैंने सेशन में पहले से ही एक select इवेंट हैंडलर जोड़ दिया है. (ऊपर देखें.)

इस चरण में, यह जानना ज़रूरी है कि इसे कहां रखना है. हिट टेस्ट के लिए, चलती हुई रेटिकल का इस्तेमाल किया जा सकता है. इसलिए, किसी ऑब्जेक्ट को आसानी से डालने के लिए, उसे आखिरी हिट टेस्ट के दौरान रेटिकल की जगह पर ड्रॉ करें.

function onSelect(event) {
  if (reticle.visible) {
    // The reticle should already be positioned at the latest hit point,
    // so we can just use its matrix to save an unnecessary call to
    // event.frame.getHitTestResults.
    addARObjectAt(reticle.matrix);
  }
}

नतीजा

इस बारे में जानने का सबसे अच्छा तरीका यह है कि सैंपल कोड को देखें या कोडलैब आज़माएं. उम्मीद है कि मैंने आपको दोनों को समझाने के लिए ज़रूरी जानकारी मिल गई है.

हमने इमर्सिव वेब एपीआई बनाने का काम नहीं किया है. हम लंबी अवधि के वीडियो में ऐसा नहीं कर रहे हैं. जैसे-जैसे हम इस सुविधा को बेहतर बनाते रहेंगे, हम यहां नए लेख पब्लिश करते रहेंगे.

Unsplash पर डैनियल फ़्रैंक की ओर से अपलोड की गई फ़ोटो