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

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

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