বাস্তব-বিশ্বের দৃশ্যে ভার্চুয়াল বস্তুর অবস্থান

হিট টেস্ট এপিআই আপনাকে ভার্চুয়াল আইটেমগুলিকে বাস্তব-বিশ্বের দৃশ্যে অবস্থান করতে দেয়।

জো মেডলি
Joe Medley

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

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

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

ইমারসিভ এআর সেশনের নমুনা

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

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

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

প্রেক্ষাপটের উপর নির্ভর করে দেয়ালে রেন্ডার করা একটি রেটিকেল, ল্যাক্স, অথবা স্ট্রিক
রেটিকেল হল একটি অস্থায়ী চিত্র যা কোনও বস্তুকে অগমেন্টেড রিয়েলিটিতে স্থাপন করতে সহায়তা করে।

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

রেটিকেল ইমেজটি আপনাকে নিজেই তৈরি করতে হবে কারণ এটি ব্রাউজার বা 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);
  }
}

উপসংহার

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

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

ছবি: ড্যানিয়েল ফ্রাঙ্ক, আনস্প্ল্যাশে