كل شيء عن حلقة اللقطات
لقد نشرت مؤخرًا مقالة بعنوان الواقع الافتراضي على الويب، وهي مقالة تعرّف على أساسيات 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
من العنصر السابق إلى العنصر التالي.
اللعبة
الآن بعد أن عرفنا هوية اللاعبين، لنلقِ نظرة على اللعبة التي يلعبونها. وهي لعبة تبدأ من جديد مع كل لقطة. تذكر أن الإطارات هي جزء من حلقة الإطار التي تحدث بمعدل يعتمد على الأجهزة الأساسية. بالنسبة إلى تطبيقات الواقع الافتراضي، يمكن أن يتراوح عدد اللقطات في الثانية بين 60 و144. تعمل تقنية الواقع المعزّز في Android بسرعة 30 لقطة في الثانية. يجب ألا يفترض الرمز أي عدد محدّد من اللقطات في الثانية.
تبدو العملية الأساسية لحلقة اللقطات على النحو التالي:
- تواصل هاتفيًا مع "
XRSession.requestAnimationFrame()
". في ردّ على ذلك، يستدعي وكيل المستخدمXRFrameRequestCallback
الذي تحدّده. - داخل دالة معاودة الاتصال:
- يُرجى الاتصال بـ
XRSession.requestAnimationFrame()
مرة أخرى. - احصل على وضعية المشاهد.
- مرِّر ("ربط")
WebGLFramebuffer
منXRWebGLLayer
إلىWebGLRenderingContext
. - يمكنك تكرار كل عنصر
XRView
، واستردادXRViewport
منXRWebGLLayer
ونقله إلىWebGLRenderingContext
. - ارسم أي عنصر إلى المخزن المؤقت للإطارات.
- يُرجى الاتصال بـ
بما أنّ الخطوة 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