واقعیت مجازی به وب می آید، قسمت دوم

همه چیز در مورد حلقه قاب

جو مدلی
Joe Medley

اخیراً، من واقعیت مجازی به وب می‌آید منتشر کردم، مقاله‌ای که مفاهیم اساسی پشت WebXR Device API را معرفی می‌کند. من همچنین دستورالعمل هایی را برای درخواست، ورود و پایان یک جلسه XR ارائه کردم.

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

WebGL و WebGL2 تنها ابزار ارائه محتوا در طول یک حلقه فریم در برنامه WebXR هستند. خوشبختانه بسیاری از فریم ورک ها لایه ای از انتزاع را در بالای WebGL و WebGL2 ارائه می دهند. چنین چارچوب‌هایی عبارتند از three.js ، babylonjs و PlayCanvas ، در حالی که A-Frame و React 360 برای تعامل با WebXR طراحی شده‌اند.

این مقاله نه WebGL است و نه یک چارچوب آموزشی. اصول اولیه یک حلقه فریم را با استفاده از نمونه جلسه VR Immersive گروه کاری وب Immersive ( دمو ، منبع ) توضیح می دهد. اگر می خواهید در WebGL یا یکی از چارچوب ها غوطه ور شوید، اینترنت فهرست رو به رشدی از مقالات را ارائه می دهد.

بازیکنان و بازی

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

بازیکنان

XRViewerPose

ژست موقعیت و جهت گیری چیزی در فضای سه بعدی است. هم بینندگان و هم دستگاه های ورودی یک ژست دارند، اما ما در اینجا به ژست بیننده می پردازیم. هر دو حالت بیننده و دستگاه ورودی دارای یک ویژگی transform هستند که موقعیت آن را به عنوان یک بردار و جهت آن را به عنوان یک کواترنیون نسبت به مبدا توصیف می کند. مبدا بر اساس نوع فضای مرجع درخواستی هنگام فراخوانی XRSession.requestReferenceSpace() مشخص می شود.

توضیح فضاهای مرجع کمی طول می کشد. من آنها را به طور عمیق در واقعیت افزوده پوشش می دهم. نمونه‌ای که من به‌عنوان مبنای این مقاله استفاده می‌کنم از یک فضای مرجع 'local' استفاده می‌کند که به این معنی است که مبدأ در موقعیت بیننده در زمان ایجاد جلسه بدون یک طبقه کاملاً مشخص است، و موقعیت دقیق آن ممکن است بسته به پلت‌فرم متفاوت باشد.

XRView

نما مربوط به دوربینی است که صحنه مجازی را مشاهده می کند. یک view همچنین دارای یک ویژگی transform است که موقعیت آن را به عنوان یک بردار و جهت آن را توصیف می کند. اینها هم به عنوان یک جفت برداری/کواترنیون و هم به عنوان یک ماتریس معادل ارائه می شوند، بسته به اینکه کدام یک به بهترین وجه با کد شما مطابقت دارد، می توانید از هر یک از این نمایش ها استفاده کنید. هر نما مربوط به یک نمایشگر یا بخشی از نمایشگر است که توسط یک دستگاه برای ارائه تصاویر به بیننده استفاده می شود. اشیاء XRView در یک آرایه از شی XRViewerPose برگردانده می شوند. تعداد نمایش ها در آرایه متفاوت است. در دستگاه‌های تلفن همراه، یک صحنه AR یک نمای دارد که ممکن است صفحه دستگاه را بپوشاند یا نداشته باشد. هدست ها معمولاً دو نمای دارند، یکی برای هر چشم.

لایه XRWebGLL

لایه ها منبعی از تصاویر بیت مپ و توضیحاتی در مورد نحوه نمایش آن تصاویر در دستگاه ارائه می دهند. این توضیحات کاملاً کاری را که این بازیکن انجام می دهد نشان نمی دهد. من به آن به عنوان یک واسطه بین یک دستگاه و یک WebGLRenderingContext فکر کردم. MDN تقریباً همین دیدگاه را دارد و بیان می کند که "ارتباط" بین این دو را فراهم می کند. به این ترتیب، دسترسی به سایر بازیکنان را فراهم می کند.

به طور کلی، اشیاء WebGL اطلاعات وضعیت را برای ارائه گرافیک های دو بعدی و سه بعدی ذخیره می کنند.

WebGLFramebuffer

یک فریم بافر داده های تصویر را به WebGLRenderingContext ارائه می دهد. پس از بازیابی آن از XRWebGLLayer ، به سادگی آن را به WebGLRenderingContext فعلی ارسال می کنید. به غیر از فراخوانی bindFramebuffer() (در ادامه در مورد آن بیشتر توضیح خواهیم داد) هرگز مستقیماً به این شی دسترسی نخواهید داشت. شما فقط آن را از XRWebGLLayer به WebGLRenderingContext منتقل می کنید.

XRViewport

یک viewport مختصات و ابعاد یک ناحیه مستطیلی را در WebGLFramebuffer ارائه می‌کند.

WebGLRenderingContext

زمینه رندرینگ یک نقطه دسترسی برنامه‌ای برای بوم (فضایی که روی آن ترسیم می‌کنیم) است. برای انجام این کار، به یک WebGLFramebuffer و یک XRViewport نیاز دارد.

به رابطه بین XRWebGLLayer و WebGLRenderingContext توجه کنید. یکی مربوط به دستگاه بیننده و دیگری مربوط به صفحه وب است. WebGLFramebuffer و XRViewport از اولی به دومی منتقل می شوند.

رابطه بین XRWebGLLayer و WebGLRenderingContext
رابطه بین XRWebGLLayer و WebGLRenderingContext

بازی

حالا که می دانیم بازیکنان چه کسانی هستند، بیایید به بازی آنها نگاه کنیم. این یک بازی است که با هر فریم از نو شروع می شود. به یاد بیاورید که فریم ها بخشی از یک حلقه فریم هستند که با سرعتی اتفاق می افتد که به سخت افزار زیرین بستگی دارد. برای برنامه های VR، فریم در ثانیه می تواند از 60 تا 144 باشد. AR برای اندروید با سرعت 30 فریم در ثانیه اجرا می شود. کد شما نباید نرخ فریم خاصی را در نظر بگیرد.

فرآیند اصلی حلقه فریم به صورت زیر است:

  1. XRSession.requestAnimationFrame() را فراخوانی کنید. در پاسخ، عامل کاربر XRFrameRequestCallback را فراخوانی می کند که توسط شما تعریف شده است.
  2. داخل تابع پاسخ به تماس شما:
    1. دوباره XRSession.requestAnimationFrame() را فراخوانی کنید.
    2. ژست بیننده را بگیرید.
    3. WebGLFramebuffer را از XRWebGLLayer به WebGLRenderingContext منتقل کنید ('bind').
    4. روی هر شی XRView تکرار کنید، XRViewport آن را از XRWebGLLayer بازیابی کرده و به WebGLRenderingContext ارسال کنید.
    5. چیزی را به فریم بافر بکشید.

از آنجایی که مراحل 1 و 2a در مقاله قبلی پوشش داده شد، از مرحله 2b شروع می کنم.

ژست بیننده را بگیرید

احتمالاً ناگفته نماند. برای ترسیم هر چیزی در AR یا VR، باید بدانم بیننده کجاست و به کجا نگاه می کند. موقعیت و جهت بیننده توسط یک شی XRViewerPose ارائه می شود. من با فراخوانی XRFrame.getViewerPose() در فریم انیمیشن فعلی، ژست بیننده را دریافت می کنم. من آن را به فضای مرجعی که هنگام تنظیم جلسه به دست آوردم منتقل می کنم. مقادیر بازگردانده شده توسط این شی همیشه نسبت به فضای مرجعی است که هنگام ورود به جلسه فعلی درخواست کردم. همانطور که ممکن است به خاطر داشته باشید، هنگام درخواست ژست باید از فضای مرجع فعلی عبور کنم.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    // Render based on the pose.
  }
}

یک ژست بیننده وجود دارد که موقعیت کلی کاربر را نشان می دهد، یعنی سر بیننده یا دوربین تلفن در مورد گوشی هوشمند. ژست به برنامه شما می گوید که بیننده کجاست. رندر واقعی تصویر از اشیاء XRView استفاده می کند که من کمی به آنها خواهم پرداخت.

قبل از حرکت، آزمایش می‌کنم که در صورتی که سیستم ردیابی را از دست بدهد یا به دلایل حفظ حریم خصوصی ژست را مسدود کند، ژست بیننده برگردانده شده است یا خیر. ردیابی توانایی دستگاه XR برای دانستن مکان آن و/یا دستگاه های ورودی آن نسبت به محیط است. ردیابی می تواند به روش های مختلفی از بین برود و بسته به روش مورد استفاده برای ردیابی متفاوت است. به عنوان مثال، اگر از دوربین‌های روی هدست یا تلفن برای ردیابی استفاده شود، ممکن است دستگاه توانایی خود را برای تعیین مکان در موقعیت‌هایی با نور کم یا بدون نور، یا پوشاندن دوربین‌ها از دست بدهد.

یک مثال از مسدود کردن پوز به دلایل حفظ حریم خصوصی این است که اگر هدست یک گفتگوی امنیتی مانند یک درخواست مجوز را نشان می‌دهد، ممکن است مرورگر ارائه پوز به برنامه را در حالی که این اتفاق می‌افتد متوقف کند. اما من قبلاً XRSession.requestAnimationFrame() را فراخوانی کرده ام تا اگر end بتواند بازیابی شود، حلقه فریم ادامه خواهد داشت.

یک دور زدن کوتاه

مرحله بعدی به اشیایی نیاز دارد که در طول تنظیم جلسه ایجاد شوند. به یاد بیاورید که من یک بوم ایجاد کردم و به آن دستور دادم که یک زمینه رندر Web GL سازگار با XR ایجاد کند که با فراخوانی canvas.getContext() دریافت کردم. تمام طراحی ها با استفاده از WebGL API، WebGL2 API یا یک چارچوب مبتنی بر WebGL مانند Three.js انجام می شود. این متن از طریق updateRenderState() به همراه نمونه جدیدی از XRWebGLLayer به شی جلسه منتقل شد.

let canvas = document.createElement('canvas');
// The rendering context must be based on WebGL or WebGL2
let webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
  });

WebGLFramebuffer را پاس دهید ('bind').

XRWebGLLayer یک فریم بافر برای WebGLRenderingContext فراهم می کند که به طور خاص برای استفاده با WebXR ارائه شده و جایگزین فریم بافر پیش فرض زمینه های رندر می شود. در زبان WebGL به این کار "باید" می گویند.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    // Iterate over the views
  }
}

روی هر شی XRView تکرار کنید

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

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

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      // Pass viewports to the context
    }
  }
}

شی XRViewport را به WebGLRenderingContext ارسال کنید

یک شی XRView به آنچه روی صفحه قابل مشاهده است اشاره دارد. اما برای جلب توجه به آن دیدگاه، به مختصات و ابعادی نیاز دارم که مخصوص دستگاه من باشد. مانند فریم بافر، من آنها را از XRWebGLLayer درخواست می کنم و آنها را به WebGLRenderingContext ارسال می کنم.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      let viewport = glLayer.getViewport(xrView);
      webGLRenContext.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      // Draw something to the framebuffer
    }
  }
}

WebGLRenContext

در نوشتن این مقاله با چند نفر از همکارانم در مورد نامگذاری شی webGLRenContext بحث و جدل داشتم. نمونه اسکریپت ها و اکثر کدهای WebXR به سادگی این متغیر را gl می نامند. وقتی برای درک نمونه ها کار می کردم، مدام فراموش می کردم که gl به چه چیزی اشاره دارد. من آن را webGLRenContext نامیده‌ام تا به شما یادآوری کنم که این نمونه‌ای از WebGLRenderingContext است.

دلیل آن این است که استفاده از gl به نام روش‌ها اجازه می‌دهد تا شبیه همتایان خود در OpenGL ES 2.0 API باشند که برای ایجاد واقعیت مجازی در زبان‌های کامپایل‌شده استفاده می‌شود. اگر برنامه‌های واقعیت مجازی را با استفاده از OpenGL نوشته‌اید، این واقعیت آشکار است، اما اگر کاملاً در این فناوری تازه کار هستید، گیج‌کننده است.

چیزی را به فریم بافر بکشید

اگر واقعاً جاه طلب هستید، می توانید مستقیماً از WebGL استفاده کنید، اما من آن را توصیه نمی کنم. استفاده از یکی از چارچوب های ذکر شده در بالا بسیار ساده تر است.

نتیجه گیری

این پایان به‌روزرسانی‌ها یا مقالات WebXR نیست. می توانید مرجعی برای تمام رابط ها و اعضای WebXR در MDN پیدا کنید. برای بهبودهای آتی خود رابط‌ها، ویژگی‌های فردی را در وضعیت Chrome دنبال کنید.

عکس توسط JESHOOTS.COM در Unsplash