استخدام الواقع الافتراضي على الويب، الجزء الثاني

كل شيء عن حلقة اللقطات

Joe Medley
Joe Medley

لقد نشرت مؤخرًا مقالة بعنوان الواقع الافتراضي على الويب، وهي مقالة تعرّف على أساسيات WebXR Device API. وقدمتُ أيضًا تعليمات لطلب جلسة الواقع المعزّز والدخول إليها وإنهائها.

توضّح هذه المقالة حلقة اللقطات، وهي حلقة لانهائية تديرها وكيل المستخدم يتم فيها رسم المحتوى بشكل متكرّر على الشاشة. يتم رسم المحتوى في وحدات منفصلة تُعرف باسم اللقطات. يخلق تتابع الإطارات وهمًا بالحركة.

ما لا تتناوله هذه المقالة

إنّ WebGL وWebGL2 هما الوسيلة الوحيدة لعرض المحتوى أثناء حلقة اللقطات في تطبيق WebXR. لحسن الحظ، توفّر العديد من إطارات العمل طبقة من التجريد فوق WebGL وWebGL2. وتشمل أطر العمل هذه three.js وbabylonjs وPlayCanvas، في حين تم تصميم A-Frame وReact 360 للتفاعل مع WebXR.

هذه المقالة ليست دليلاً تعليميًا حول WebGL أو إطار العمل. ويشرح هذا الدليل أساسيات حلقة اللقطات باستخدام نموذج جلسة الواقع الافتراضي الغامر من مجموعة عمل الويب الغامر (العرض التجريبي، المصدر). إذا أردت التعمّق في WebGL أو أحد أطر العمل، يوفّر الإنترنت قائمة متنامية من المقالات.

اللاعبون واللعبة

عندما حاولت فهم حلقة الإطار، كنت أشعر بالانزعاج في التفاصيل. هناك الكثير من الكائنات قيد التشغيل، وبعضها مُسمّى فقط بخصائص مرجعية على كائنات أخرى. لمساعدتك في التركيز، سأصف العناصر التي أُطلق عليها اسم "اللاعبون". بعد ذلك، سأصف كيفية تفاعلها، والتي أُطلق عليها اسم "اللعبة".

اللاعبين

XRViewerPose

الوضع هو موضع واتجاه جسم في الفضاء الثلاثي الأبعاد. يختلف وضع كلّ من المشاهدين وأجهزة الإدخال، ولكننا نهتم بالوضع الذي يجلس فيه المشاهد هنا. تحتوي كلّ من أوضاع المشاهد وجهاز الإدخال على سمة transform تصف موقعه كخطّ متّجه واتجاهه كعدد رباعي نسبيًا إلى نقطة الأصل. يتم تحديد المصدر استنادًا إلى نوع المساحة المرجعية المطلوبة عند استدعاء XRSession.requestReferenceSpace().

يستغرق الأمر بعض الوقت للشرح في المساحات المرجعية. سأتناول هذه المواضيع بالتفصيل في مقالة الواقع المعزّز . يستخدم العيّنة التي أستخدمها كأساس لهذه المقالة مساحة مرجعية 'local'، ما يعني أنّ المصدر يقع عند موضع المشاهد في وقت إنشاء الجلسة بدون أرضية محدّدة بوضوح، وقد يختلف موضعه الدقيق حسب النظام الأساسي.

XRView

تتوافق طريقة العرض مع الكاميرا التي تعرض المشهد الافتراضي. تحتوي طريقة العرض أيضًا على سمة transform تصف موضع العنصر باعتباره متّجهًا واتجاهه. يتم تقديم هذه الكميات على شكل زوج من المتجهات/الأربعيات وكصفيف مكافئ، ويمكنك استخدام أيّ من طرق التمثيل هذه حسب ما يناسب الرمز البرمجي. تتوافق كل طريقة عرض مع عرض أو جزء من شاشة يستخدم الجهاز لتقديم الصور للمشاهد. ويتم عرض عناصر XRView في مصفوفة من الكائن XRViewerPose. يختلف عدد المشاهدات في الصفيف. على الأجهزة الجوالة، يتضمّن مشهد الواقع المعزّز عرضًا واحدًا قد يغطي شاشة الجهاز أو لا يغطيها. عادةً ما تتضمّن سماعات الرأس عرضَين، أحدهما لكل عين.

XRWebGLLayer

توفر الطبقات مصدرًا للصور النقطية وأوصاف كيفية عرض هذه الصور في الجهاز. لا يوضح هذا الوصف ما يفعله هذا اللاعب تمامًا. لقد اعتبرتُه وسيطًا بين الجهاز وأحد WebGLRenderingContext. تتّبع MDN الرأي نفسه، وتشير إلى أنّها "توفّر رابطًا" بين الاثنين. وبالتالي، يتيح الوصول إلى اللاعبين الآخرين.

بشكل عام، تخزّن عناصر WebGL معلومات الحالة لعرض الرسومات المخطّطة ثنائية وثلاثية الأبعاد.

WebGLFramebuffer

يقدّم إطار التخزين المؤقت للصور بيانات الصور إلى WebGLRenderingContext. بعد استرداده من XRWebGLLayer، ما عليك سوى تمريره إلى WebGLRenderingContext الحالي. ولن تتمكّن أبدًا من الوصول إلى هذا الكائن مباشرةً باستثناء استدعاء bindFramebuffer() (وغيره من المعلومات لاحقًا). ما عليك سوى تمريرها من XRWebGLLayer إلى WebGLRenderingContext.

XRViewport

يوفّر إطار العرض إحداثيات وأبعاد منطقة مستطيلة في WebGLFramebuffer.

WebGLRenderingContext

سياق العرض هو نقطة وصول آلية إلى لوحة (المساحة التي نرسم عليها). لتنفيذ ذلك، يجب توفّر منفذَي WebGLFramebuffer وXRViewport.

لاحظ العلاقة بين XRWebGLLayer وWebGLRenderingContext. يرتبط أحدهما بجهاز المشاهد والآخر بصفحة الويب. يتم تمرير WebGLFramebuffer وXRViewport من العنصر السابق إلى العنصر التالي.

العلاقة بين XRWebGLLayer وWebGLRenderingContext
العلاقة بين XRWebGLLayer وWebGLRenderingContext

اللعبة

الآن بعد أن عرفنا هوية اللاعبين، لنلقِ نظرة على اللعبة التي يلعبونها. وهي لعبة تبدأ من جديد مع كل لقطة. تذكر أن الإطارات هي جزء من حلقة الإطار التي تحدث بمعدل يعتمد على الأجهزة الأساسية. بالنسبة إلى تطبيقات الواقع الافتراضي، يمكن أن يتراوح عدد اللقطات في الثانية بين 60 و144. تعمل تقنية الواقع المعزّز في Android بسرعة 30 لقطة في الثانية. يجب ألا يفترض الرمز أي عدد محدّد من اللقطات في الثانية.

تبدو العملية الأساسية لحلقة اللقطات على النحو التالي:

  1. تواصل هاتفيًا مع "XRSession.requestAnimationFrame()". في ردّ على ذلك، يستدعي وكيل المستخدم XRFrameRequestCallback الذي تحدّده.
  2. داخل دالة معاودة الاتصال:
    1. يُرجى الاتصال بـ XRSession.requestAnimationFrame() مرة أخرى.
    2. احصل على وضعية المشاهد.
    3. مرِّر ("ربط") WebGLFramebuffer من XRWebGLLayer إلى WebGLRenderingContext.
    4. يمكنك تكرار كل عنصر XRView، واسترداد XRViewport من XRWebGLLayer ونقله إلى WebGLRenderingContext.
    5. ارسم أي عنصر إلى المخزن المؤقت للإطارات.

بما أنّ الخطوة 1 والخطوة 2(أ) قد تم تناولهما في المقالة السابقة، سأبدأ بالخطوة 2(ب).

الحصول على وضعية المشاهد

لا حاجة إلى قول ذلك. لرسم أي شيء في الواقع المعزّز أو الواقع الافتراضي، أحتاج إلى معرفة الموقع الجغرافي للمشاهد واتجاه نظره. يتم تحديد موضع المشاهد و اتجاهه من خلال عنصر 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 عناصر، وسأتناولها بعد قليل.

قبل الانتقال إلى الخطوة التالية، أختبر ما إذا تم عرض وضعية المشاهد في حال فقد النظام إمكانية التتبّع أو حظر الوضعية لأسباب تتعلّق بالخصوصية. التتبّع هو قدرة جهاز الواقع المعزّز على معرفة موقعه و/أو أجهزة الإدخال الخاصة به بالنسبة إلى البيئة. يمكن أن يتوقّف التتبّع لعدة أسباب، ويختلف ذلك حسب الطريقة المستخدَمة للتتبّع. على سبيل المثال، إذا تم استخدام كاميرات في سماعة الرأس أو الهاتف لتتبُّع الجهاز، قد يفقد الجهاز قدرته على تحديد مكانه في ظروف الإضاءة المنخفضة أو المنعدمة أو ما إذا كانت الكاميرات محجوبة.

من أمثلة حظر الوضعية لأسباب تتعلق بالخصوصية: إذا كانت سماعة الرأس تعرض مربع حوار أمني مثل طلب الحصول على إذن، فقد يتوقف المتصفح عن توفير أوضاع التطبيق أثناء حدوث ذلك. ولكنني اتصلتُ بفريق XRSession.requestAnimationFrame() لكي يواصل عرض اللقطات في حلقة إذا تمكّن النظام من استعادة الأداء. وإذا لم يكن الأمر كذلك، سينهِي وكيل المستخدم الجلسة ويُطلِب من معالج الحدث end.

تحويلة قصيرة

تتطلّب الخطوة التالية العناصر التي تم إنشاؤها أثناء إعداد الجلسة. تذكَّر أنّه لقد أنشأت لوحة وطلبت منها إنشاء سياق معالجة رسومات Web GL متوافق مع الواقع المعزّز، والذي حصلت عليه من خلال استدعاء canvas.getContext(). يتم إجراء جميع عمليات الرسم باستخدام واجهة برمجة تطبيقات WebGL أو 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

توفّر 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