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

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

Joe Medley
Joe Medley

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

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

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

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

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

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

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

संदर्भ के आधार पर, दीवार, Lax या Strict पर रेंडर किया गया रेटिकल
रेटिकल एक अस्थायी इमेज होती है. यह ऑगमेंटेड रिएलिटी में किसी ऑब्जेक्ट को रखने में मदद करती है.

रेटिकल बनाना

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

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

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

सेशन का अनुरोध करते समय, आपको 'hit-test' ऐरे में 'hit-test' का अनुरोध करना होगा. यहां दिए गए उदाहरण में देखें.requiredFeatures

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

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

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

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 पर Daniel Frank की फ़ोटो