বাস্তব জগতের দৃশ্যে ভার্চুয়াল অবজেক্টের পজিশন ঠিক করা

হিট টেস্ট API আপনাকে বাস্তব-জগতের ভিউতে ভার্চুয়াল আইটেম পজিশন করতে দেয়।

Joe Medley
Joe Medley

গত শরতে Chrome 79-এ WebXR ডিভাইস API শিপ করা হয়েছে। তখন যেমন বলা হয়েছিল, Chrome-এ API প্রয়োগ করার কাজ এখনও চলছে। Chrome আনন্দের সাথে জানাচ্ছে যে কিছু কাজ সম্পূর্ণ হয়েছে। Chrome 81-এ দুটি নতুন ফিচার এসেছে:

এই নিবন্ধে WebXR হিট টেস্ট API সম্পর্কে আলোচনা করা হয়েছে, যা বাস্তব-জগতের ক্যামেরা ভিউতে ভার্চুয়াল অবজেক্ট প্লেস করার একটি উপায়।

এই নিবন্ধে আমি ধরে নিচ্ছি যে আপনি ইতিমধ্যেই জানেন কীভাবে একটি অগমেন্টেড রিয়েলিটি সেশন তৈরি করতে হয় এবং আপনি জানেন কীভাবে একটি ফ্রেম লুপ চালাতে হয়। আপনি যদি এইসব ধারণার সাথে পরিচিত না হন, তাহলে এই সিরিজের আগের নিবন্ধগুলি আপনার পড়া উচিত।

ইমারসিভ AR সেশন স্যাম্পেল

এই নিবন্ধের কোডটি ইমারসিভ ওয়েব ওয়ার্কিং গ্রুপের হিট টেস্ট স্যাম্পেলে (ডেমো, সোর্স) পাওয়া কোডের উপর ভিত্তি করে তৈরি করা হয়েছে, তবে এটি হুবহু এক নয়। এই উদাহরণ আপনাকে বাস্তব জগতের সারফেসে ভার্চুয়াল সূর্যমুখী ফুল প্লেস করতে দেয়।

আপনি প্রথমবার অ্যাপ খুললে, মাঝখানে একটি ডট সহ নীল রঙের বৃত্ত দেখতে পাবেন। ডটটি হল আপনার ডিভাইস থেকে পরিবেশের পয়েন্ট পর্যন্ত একটি কাল্পনিক রেখার ছেদবিন্দু। আপনি ডিভাইসটি সরালে এটিও সরে যায়। এটি ইন্টারসেকশন পয়েন্ট খুঁজে পেলে, মেঝে, টেবিল টপ ও দেওয়ালের মতো সারফেসে স্ন্যাপ করে। এটি এমন করে কারণ হিট টেস্টিং ইন্টারসেকশন পয়েন্টের পজিশন ও ওরিয়েন্টেশন প্রদান করে, কিন্তু সারফেস সম্পর্কে কিছুই জানায় না।

এই বৃত্তটিকে রেটিকল বলা হয়, যা একটি সাময়িক ছবি এবং অগমেন্টেড রিয়েলিটিতে কোনও অবজেক্টকে প্লেস করতে সাহায্য করে। আপনি স্ক্রিনে ট্যাপ করলে, রেটিকল লোকেশন এবং রেটিকল পয়েন্টের ওরিয়েন্টেশন অনুযায়ী সারফেসে একটি সূর্যমুখী ফুল প্লেস করা হয়, আপনি স্ক্রিনের যেখানেই ট্যাপ করুন না কেন। আপনার ডিভাইসের সাথে রেটিকল মুভ করতে থাকে।

দেওয়ালে রেটিকল রেন্ডার করা হয়েছে, প্রসঙ্গ অনুযায়ী শিথিল বা কঠোর
রেটিকল হল একটি সাময়িক ছবি যা অগমেন্টেড রিয়েলিটিতে কোনও অবজেক্টকে প্লেস করতে সাহায্য করে।

রেটিকল তৈরি করুন

আপনাকে অবশ্যই রেটিকল ইমেজ তৈরি করতে হবে কারণ এটি ব্রাউজার বা API দ্বারা প্রদান করা হয় না। এটি লোড ও ড্র করার পদ্ধতি ফ্রেমওয়ার্কের উপর নির্ভর করে। আপনি 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 ইভেন্ট লিসেনার যোগ করেছি। ব্যবহারকারী স্ক্রিনে ট্যাপ করলে, রেটিকলের পোজের উপর ভিত্তি করে ক্যামেরার ভিউতে একটি ফুল দেখানো হবে। আমি পরে সেই ইভেন্ট লিসনারের বর্ণনা দেব।

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
}

AR-তে কিছু আঁকতে হলে, দর্শক কোথায় আছেন এবং কোথায় দেখছেন তা আমাকে জানতে হবে। তাই আমি পরীক্ষা করে দেখি যে 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
}

অবজেক্ট রাখা

ব্যবহারকারী স্ক্রিনে ট্যাপ করলে AR-তে অবজেক্ট দেখানো হয়। আমি আগেই সেশনে 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);
  }
}

উপসংহার

এই বিষয়ে আরও ভালভাবে বুঝতে স্যাম্পেল কোড ধাপে ধাপে দেখুন অথবা কোডল্যাব ব্যবহার করে দেখুন। আমি আশা করি আমি আপনাকে দুটি বোঝার জন্য যথেষ্ট ব্যাকগ্রাউন্ড দিয়েছি।

আমরা ইমারসিভ ওয়েব API তৈরি করার কাজ এখনও শেষ করিনি, এখনও অনেক কাজ বাকি। আমরা অগ্রগতি করার সাথে সাথে এখানে নতুন নিবন্ধ প্রকাশ করব।