تعیین موقعیت اشیاء مجازی در نماهای دنیای واقعی

Hit Test API به شما امکان می دهد آیتم های مجازی را در یک نمای دنیای واقعی قرار دهید.

جو مدلی
Joe Medley

WebXR Device API پاییز گذشته در Chrome 79 ارسال شد. همانطور که قبلاً گفته شد، اجرای Chrome از API در حال انجام است. کروم با خوشحالی اعلام می کند که برخی از کارها به پایان رسیده است. در کروم 81، دو ویژگی جدید وارد شده است:

این مقاله WebXR Hit Test API را پوشش می‌دهد، وسیله‌ای برای قرار دادن اشیاء مجازی در نمای دوربین دنیای واقعی.

در این مقاله فرض می‌کنم که شما قبلاً می‌دانید چگونه یک جلسه واقعیت افزوده ایجاد کنید و می‌دانید که چگونه یک حلقه فریم را اجرا کنید. اگر با این مفاهیم آشنا نیستید، باید مقالات قبلی این مجموعه را بخوانید.

نمونه جلسه AR همهجانبه

کد موجود در این مقاله بر اساس آن است، اما با کدی که در نمونه تست ضربه گروه کاری Immersive Web ( دمو ، منبع ) یافت شده است، یکسان نیست. این مثال به شما امکان می دهد گل آفتابگردان مجازی را روی سطوح در دنیای واقعی قرار دهید.

هنگامی که برای اولین بار برنامه را باز می کنید، یک دایره آبی با یک نقطه در وسط می بینید. نقطه تقاطع بین یک خط خیالی از دستگاه شما تا نقطه ای در محیط است. با حرکت دستگاه حرکت می کند. همانطور که نقاط تقاطع را پیدا می کند، به نظر می رسد که به سطوحی مانند کف، میز و دیوارها می چسبد. این کار را به این دلیل انجام می دهد که تست ضربه موقعیت و جهت نقطه تقاطع را ارائه می دهد، اما چیزی در مورد خود سطوح ارائه نمی دهد.

این دایره رتیکل نامیده می شود که تصویری موقت است که به قرار دادن یک شی در واقعیت افزوده کمک می کند. اگر روی صفحه ضربه بزنید، یک گل آفتابگردان بر روی سطح در محل شبکه و جهت نقطه مشبک قرار می گیرد، صرف نظر از اینکه کجا روی صفحه ضربه زده اید. شبکه به حرکت با دستگاه شما ادامه می دهد.

مشبکی که بر روی دیوار، سست، یا سخت بسته به بافت آنها ارائه شده است
رتیکل تصویری موقت است که به قرار دادن یک شی در واقعیت افزوده کمک می کند.

مشبک را ایجاد کنید

شما باید تصویر رتیکل را خودتان ایجاد کنید زیرا توسط مرورگر یا API ارائه نشده است. روش بارگذاری و ترسیم آن چارچوب خاص است. اگر آن را مستقیماً با استفاده از WebGL یا WebGL2 نمی‌کشید، با مستندات چارچوب خود مشورت کنید. به همین دلیل، من به جزئیات در مورد نحوه رسم رتیکل در نمونه نمی پردازم. در زیر یک خط از آن را فقط به یک دلیل نشان می‌دهم: به طوری که در نمونه‌های کد بعدی، وقتی از متغیر reticle استفاده می‌کنم، بدانید به چه چیزی اشاره می‌کنم.

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

درخواست یک جلسه

هنگام درخواست جلسه، باید '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
}

برای ترسیم هر چیزی در 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);
  }
}

نتیجه

بهترین راه برای دستیابی به این موضوع این است که از طریق کد نمونه گام بردارید یا نرم افزار Codelab را امتحان کنید. امیدوارم پیش زمینه کافی برای درک هر دوی آنها به شما داده باشم.

ما ساختن APIهای وب همهجانبه را به پایان نرسانده ایم. با پیشرفت، مقالات جدیدی را در اینجا منتشر خواهیم کرد.

عکس از دانیل فرانک در Unsplash