
ملخّص
تمت دعوة ستة فنانين للرسم والتصميم والنحت في الواقع الافتراضي. في ما يلي خطوات تسجيل جلساتهم وتحويل البيانات وعرضها في الوقت الفعلي باستخدام متصفّحات الويب.
https://g.co/VirtualArtSessions
يا له من وقت رائع. مع طرح الواقع الافتراضي كأحد منتجات المستهلكين، يتم اكتشاف إمكانات جديدة وغير مستكشفة. تتيح لك أداة Tilt Brush، وهي أحد منتجات Google المتوفّرة على جهاز HTC Vive، الرسم في فضاء ثلاثي الأبعاد. عندما جرّبنا Tilt Brush للمرة الأولى، بقي شعورنا بالانطباع العميق عند الرسم باستخدام أجهزة تحكّم تتتبّع الحركة، بالإضافة إلى الشعور بأنّنا "في غرفة تتضمّن قوى خارقة". لا تتوفّر تجربة مماثلة لتجربة الرسم في المساحة الفارغة من حولك.

واجه فريق "الفنون المستندة إلى البيانات" في Google تحديًا يتمثل في عرض هذه المحاولة لمستخدمي الويب الذين لا يملكون خوذة الواقع الافتراضي، لأنّ تطبيق Tilt Brush لا يعمل بعد على الويب. لتحقيق ذلك، استعان الفريق بنحات ورسّام ومصمّم مفاهيمي وفنان أزياء وفنان تركيبات وفنانين في الشوارع لإنشاء أعمال فنية بأسلوبهم الخاص في هذا الوسيط الجديد.
تسجيل الرسومات في الواقع الافتراضي
تم إنشاء برنامج Tilt Brush باستخدام Unity، وهو تطبيق مخصّص لأجهزة الكمبيوتر المكتبي ويستخدم تقنية الواقع الافتراضي على مستوى الغرفة لتتبُّع موضع رأسك (شاشة HMD) وأدوات التحكّم في كلتا يديك. يتم تلقائيًا تصدير الأعمال الفنية التي تم إنشاؤها في Tilt Brush كملف .tilt
. لتقديم هذه التجربة على الويب،
أدركنا أنّنا بحاجة إلى أكثر من بيانات العمل الفني فقط. لقد عملنا عن كثب مع
فريق Tilt Brush لتعديل Tilt Brush كي يتم تصدير إجراءات التراجع/الحذف وكذلك
موضع رأس الفنّان ويده بمعدّل 90 مرة في الثانية.
عند الرسم، تأخذ لعبة Tilt Brush موضع جهاز التحكّم وزاويته وتحوّل نقاطًا متعددة بمرور الوقت إلى "خط". يمكنك الاطّلاع على مثال هنا. لقد كتبنا إضافات استخرجت هذه الخطوط وعرضتها بتنسيق JSON خام.
{
"metadata": {
"BrushIndex": [
"d229d335-c334-495a-a801-660ac8a87360"
]
},
"actions": [
{
"type": "STROKE",
"time": 12854,
"data": {
"id": 0,
"brush": 0,
"b_size": 0.081906750798225,
"color": [
0.69848710298538,
0.39136275649071,
0.211316883564
],
"points": [
[
{
"t": 12854,
"p": 0.25791856646538,
"pos": [
[
1.9832634925842,
17.915264129639,
8.6014995574951
],
[
-0.32014992833138,
0.82291424274445,
-0.41208130121231,
-0.22473378479481
]
]
}, ...many more points
]
]
}
}, ... many more actions
]
}
يوضّح المقتطف أعلاه تنسيق ملف JSON الخاص بالرسم.
هنا، يتم حفظ كلّ ضربة كإجراء، بنوع: "STROKE". بالإضافة إلى إجراءات الخطوط، أردنا أن نعرض فنّانًا يرتكب أخطاء ويغيّر رأيه أثناء الرسم، لذلك كان من الضروري حفظ إجراءات "حذف" التي تعمل إما على محو أو التراجع عن إجراءات خط كامل.
يتم حفظ المعلومات الأساسية لكلّ ضربة فرشاة، وبالتالي يتم جمع نوع الفرشاة وحجمها ولونها rgb.
أخيرًا، يتم حفظ كل قمة من قمم الخطوط، بما في ذلك الموضع،
والزاوية، والوقت، بالإضافة إلى قوة الضغط على زر التحكّم (يُشار إليها باسم p
داخل كل نقطة).
يُرجى العلم أنّ الدوران هو رباعي الأبعاد. وهذا مهم لاحقًا عند عرض الخطوط لتجنُّب قفل المحامل الكروية.
تشغيل الرسومات باستخدام WebGL
لعرض الرسومات في متصفّح ويب، استخدمنا مكتبة THREE.js وكتبنا رمزًا لإنشاء الأشكال الهندسية يحاكي العمليات التي تُجريها Tilt Brush في الخلفية.
على الرغم من أنّ تطبيق Tilt Brush ينشئ شرائح مثلثة في الوقت الفعلي استنادًا إلى تحرّك يد المستخدم، يكون الرسم بأكمله قد "انتهى" بحلول الوقت الذي نعرضه فيه على الويب. يتيح لنا ذلك تجاوز الكثير من العمليات الحسابية في الوقت الفعلي وإعداد الأشكال الهندسية عند التحميل.

يُنشئ كل زوج من الرؤوس في الخطوط المرسومة متجهًا للاتجاه (الخطوط الزرقاء
التي تربط كل نقطة كما هو موضّح أعلاه، moveVector
في مقتطف الرمز البرمجي أدناه).
تحتوي كل نقطة أيضًا على اتجاه، وهو رباعي الأبعاد يمثّل
الزاوية الحالية لوحدة التحكّم. لإنشاء شريط مثلث، نكرّر كل نقطة من
هذه النقاط وننشئ اتجاهات عمودية على الاتجاه و
اتجاه وحدة التحكّم.
إنّ عملية احتساب شريط المثلث لكلّ ضربة قلم متطابقة تقريبًا مع الرمز المستخدَم في Tilt Brush:
const V_UP = new THREE.Vector3( 0, 1, 0 );
const V_FORWARD = new THREE.Vector3( 0, 0, 1 );
function computeSurfaceFrame( previousRight, moveVector, orientation ){
const pointerF = V_FORWARD.clone().applyQuaternion( orientation );
const pointerU = V_UP.clone().applyQuaternion( orientation );
const crossF = pointerF.clone().cross( moveVector );
const crossU = pointerU.clone().cross( moveVector );
const right1 = inDirectionOf( previousRight, crossF );
const right2 = inDirectionOf( previousRight, crossU );
right2.multiplyScalar( Math.abs( pointerF.dot( moveVector ) ) );
const newRight = ( right1.clone().add( right2 ) ).normalize();
const normal = moveVector.clone().cross( newRight );
return { newRight, normal };
}
function inDirectionOf( desired, v ){
return v.dot( desired ) >= 0 ? v.clone() : v.clone().multiplyScalar(-1);
}
يؤدي الجمع بين اتجاه الخطوط واتجاهها بحد ذاتهما إلى عرض نتائج غامضة رياضيًا، إذ يمكن أن يتم اشتقاق خطوط عادية متعددة، مما يؤدي في أغلب الأحيان إلى "التواء" في الأشكال الهندسية.
عند تكرار نقاط الخطوط، نحافظ على متجه "يسار مفضّل"
ونمرره إلى الدالة computeSurfaceFrame()
. تمنحنا هذه الدالة
وحدة عادية يمكننا من خلالها اشتقاق رباعي الأضلاع في شريط الرباعيات، استنادًا إلى
اتجاه الخطوط (من النقطة الأخيرة إلى النقطة الحالية) و
اتجاه وحدة التحكّم (رباعي الأبعاد). والأهم من ذلك، أنّه يعرِض أيضًا
مصفوفة "يمين مفضّل" جديدة للمجموعة التالية من العمليات الحسابية.

بعد إنشاء مربّعات استنادًا إلى نقاط التحكّم في كلّ ضربة، نُجري دمجًا للمربّعات من خلال تداخل زواياها، من مربّع إلى آخر.
function fuseQuads( lastVerts, nextVerts) {
const vTopPos = lastVerts[1].clone().add( nextVerts[0] ).multiplyScalar( 0.5
);
const vBottomPos = lastVerts[5].clone().add( nextVerts[2] ).multiplyScalar(
0.5 );
lastVerts[1].copy( vTopPos );
lastVerts[4].copy( vTopPos );
lastVerts[5].copy( vBottomPos );
nextVerts[0].copy( vTopPos );
nextVerts[2].copy( vBottomPos );
nextVerts[3].copy( vBottomPos );
}

يحتوي كلّ رباعي أيضًا على وحدات UV يتمّ إنشاؤها كخطوة تالية. تحتوي بعض الفرش على مجموعة متنوعة من أنماط الخطوط لإعطاء انطباع بأنّ كل خطٍ يبدو كخط مختلف من فرشاة الطلاء. ويتم ذلك باستخدام _أداة تنسيق القوام_، حيث يحتوي كلّ نسيج فرشاة على جميع الاختلافات الممكنة. يتم اختيار النسيج الصحيح من خلال تعديل قيم UV للخط.
function updateUVsForSegment( quadVerts, quadUVs, quadLengths, useAtlas,
atlasIndex ) {
let fYStart = 0.0;
let fYEnd = 1.0;
if( useAtlas ){
const fYWidth = 1.0 / TEXTURES_IN_ATLAS;
fYStart = fYWidth * atlasIndex;
fYEnd = fYWidth * (atlasIndex + 1.0);
}
//get length of current segment
const totalLength = quadLengths.reduce( function( total, length ){
return total + length;
}, 0 );
//then, run back through the last segment and update our UVs
let currentLength = 0.0;
quadUVs.forEach( function( uvs, index ){
const segmentLength = quadLengths[ index ];
const fXStart = currentLength / totalLength;
const fXEnd = ( currentLength + segmentLength ) / totalLength;
currentLength += segmentLength;
uvs[ 0 ].set( fXStart, fYStart );
uvs[ 1 ].set( fXEnd, fYStart );
uvs[ 2 ].set( fXStart, fYEnd );
uvs[ 3 ].set( fXStart, fYEnd );
uvs[ 4 ].set( fXEnd, fYStart );
uvs[ 5 ].set( fXEnd, fYEnd );
});
}



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

لإجراء اختبار الضغط على النظام، أنشأنا رسمًا استغرق 20 دقيقة لملء المساحة بأكبر عدد ممكن من الرؤوس. استمر تشغيل الرسم الناتج بمعدل 60 لقطة في الثانية باستخدام WebGL.
بما أنّ كلّ رأس أصلي من الخطوط يتضمّن أيضًا الوقت، يمكننا تشغيل البيانات بسهولة. إنّ إعادة احتساب الخطوط لكل إطار سيكون عملية بطيئة جدًا، لذا احتسبنا مسبقًا الرسم بأكمله عند تحميله وكشفنا عن كل مربّع عند حلول وقته.
كان إخفاء رباعي الأضلاع يعني ببساطة تصغير رؤوسه إلى النقطة 0,0,0. عندما يحلّ الوقت الذي من المفترض أن يظهر فيه الشكل الرباعي، تتم إعادة وضع الرؤوس في مكانها.
من المجالات التي يمكن تحسينها هي التلاعب بالرؤوس بالكامل على وحدة معالجة الرسومات باستخدام مخطّطات التظليل. يضع الإجراء الحالي هذه النقاط من خلال تكرار صفيف القمة من الطابع الزمني الحالي، والتحقّق من النقاط التي يجب الكشف عنها ثم تعديل الشكل الهندسي. ويؤدي ذلك إلى تحميل وحدة المعالجة المركزية (CPU) بشكل كبير، ما يؤدي بدوره إلى تشغيل المروحة وإهدار عمر البطارية.

تسجيل الفنانين
وشعرنا أنّ الرسومات نفسها لن تكون كافية. أردنا أن نعرض الفنانين داخل رسوماتهم وهم يضعون كل لمسة فرشاة.
لتصوير الفنانين، استخدمنا كاميرات Microsoft Kinect لتسجيل بيانات عمق أجساد الفنانين في الفضاء. يتيح لنا ذلك عرض الأشكال الثلاثية الأبعاد في المساحة نفسها التي تظهر فيها الرسومات.
وبما أنّ جسم الفنان سيسدّ الرؤية ويمنعنا من رؤية ما هو خلفه، استخدمنا نظام Kinect مزدوجًا، كلاهما في جانبَي الغرفة opposite يشيران إلى المركز.
بالإضافة إلى معلومات العمق، التقطنا أيضًا معلومات لون المشهد باستخدام كاميرات DSLR العادية. لقد استخدمنا برنامج DepthKit الرائع لمعايرة الفيديو من كاميرا العمق والكاميرات الملوّنة ودمجهما. يمكن لكاميرا Kinect تسجيل المحتوى بالألوان، ولكن اخترنا استخدام كاميرات DSLR لأنّنا يمكننا التحكّم في إعدادات التعريض واستخدام عدسات رائعة وعالية الجودة وتسجيل المحتوى بدقة عالية.
لتسجيل اللقطات، أنشأنا غرفة خاصة لوضع جهاز HTC Vive والفنان والكاميرا. تم تغطية جميع الأسطح بمادة تمتص ضوء الأشعة تحت الحمراء لتوفير سحابة نقاط أنظف (تم استخدام قماش من قماش المخمل على الجدران، وحصيرة مطاطية ملفوفة على الأرض). في حال ظهور المادة في لقطات سحابة النقاط، اخترنا مادة سوداء كي لا تشتت الانتباه مثل مادة بيضاء.

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

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

وتم ذلك من خلال كتابة مكوّن إضافي مخصّص في Tilt Brush لاستخراج مواضع HMD وأجهزة التحكّم في كل لقطة. بما أنّ تطبيق Tilt Brush يعمل بمعدّل 90 لقطة في الثانية، تم بثّ أطنان من البيانات وكانت بيانات الإدخال الخاصة بالرسم تزيد عن 20 ميغابايت غير مضغوطة. استخدمنا هذه التقنية أيضًا لتسجيل الأحداث التي لا يتم تسجيلها في ملف الحفظ العادي لتطبيق Tilt Brush، مثل عندما يختار الفنان خيارًا في لوحة الأدوات وموضع التطبيق المصغّر للمرآة.
في ما يتعلّق بمعالجة 4 تيرابايت من البيانات التي سجلناها، كان أحد أكبر التحديات هو مواءمة جميع مصادر البيانات/المرئيات المختلفة. يجب محاذاة كل فيديو من كاميرا DSLR مع Kinect المعنيّ، بحيث تكون البكسلات محاذية في المكان والوقت. بعد ذلك، كان علينا مواءمة لقطات الكاميراتين معًا لإنشاء صورة فنية واحدة. بعد ذلك، كان علينا مساعدة الفنان المعني بالرسوم الثلاثية الأبعاد في ضبط إعدادات الرسم وفقًا للبيانات التي تم رصدها. أخيرًا! لقد كتبنا أدوات مستندة إلى المتصفّح للمساعدة في معظم هذه المهام، ويمكنك تجربتها بنفسك هنا.

بعد محاذاة البيانات، استخدمنا بعض النصوص البرمجية المكتوبة بلغة NodeJS لمعالجتها كلها وإنشاء ملف فيديو وسلسلة من ملفات JSON، تم اقتصاصها ومزامنتها. لتقليل حجم الملف، اتّخذنا ثلاث خطوات: أولاً، خفّضنا دقة كل رقم عشري حتى تصل إلى 3 أرقام عشرية كحد أقصى. ثانيًا، خفّضنا عدد النقاط بمقدار الثلث إلى 30 لقطة في الثانية، وحقّقنا التداخل بين مواضع العناصر من جهة العميل. أخيرًا، تم تسلسل البيانات، فبدلًا من استخدام تنسيق JSON العادي مع أزواج المفاتيح/القيم، تم إنشاء ترتيب للقيم لتحديد موضع جهاز الواقع الافتراضي وأجهزة التحكّم واتجاه دورانها. أدّى ذلك إلى تقليل حجم الملف إلى ما يقل قليلاً عن 3 ميغابايت، وهو حجم مقبول لإرساله عبر الإنترنت.

وبما أنّ الفيديو نفسه يتم عرضه كعنصر فيديو HTML5 يتم قراءته من خلال ملف تضاريس WebGL ليصبح جسيمات، كان يجب تشغيل الفيديو نفسه مخفيًا في الخلفية. تعمل أداة تظليل على تحويل الألوان في صور العمق إلى مواضع في الفضاء الثلاثي الأبعاد. شارك "جيمس جورج" مثالاً رائعًا على كيفية استخدام لقطات من DepthKit مباشرةً.
تفرض أجهزة iOS قيودًا على تشغيل الفيديوهات المضمّنة، ويُفترض أنّ الغرض من ذلك هو منع مضايقة المستخدِمين من خلال إعلانات الفيديو على الويب التي يتم تشغيلها تلقائيًا. لقد استخدمنا أسلوبًا مشابهًا للحلول البديلة الأخرى على الويب، وهو نسخ إطار الفيديو إلى لوحة وتعديل وقت التقديم أو الإيقاف في الفيديو يدويًا كل 1/30 من الثانية.
videoElement.addEventListener( 'timeupdate', function(){
videoCanvas.paintFrame( videoElement );
});
function loopCanvas(){
if( videoElement.readyState === videoElement.HAVE\_ENOUGH\_DATA ){
const time = Date.now();
const elapsed = ( time - lastTime ) / 1000;
if( videoState.playing && elapsed >= ( 1 / 30 ) ){
videoElement.currentTime = videoElement.currentTime + elapsed;
lastTime = time;
}
}
}
frameLoop.add( loopCanvas );
كان لنهجنا تأثير جانبي غير مرغوب فيه، وهو خفض معدّل عرض اللقطات في iOS بشكل كبير، لأنّ نسخ ذاكرة التخزين المؤقت للبكسل من الفيديو إلى اللوحة يتطلّب استخدامًا مكثّفًا لوحدة المعالجة المركزية. لحلّ هذه المشكلة، عرضنا ببساطة إصدارات أصغر حجمًا من الفيديوهات نفسها التي تتيح 30 لقطة في الثانية على الأقل على هاتف iPhone 6.
الخاتمة
منذ عام 2016، أصبح الإجماع العام على تطوير برامج الواقع الافتراضي هو إبقاء الأشكال الهندسية وملفات التظليل بسيطة حتى تتمكّن من تشغيلها بمعدّل 90 لقطة في الثانية أو أكثر في جهاز عرض الرأس المتحركة. تبيّن أنّه يمثّل هدفًا رائعًا لعروض WebGL التوضيحية، لأنّ الأساليب المستخدَمة في Tilt Brush تتوافق بشكلٍ رائع مع WebGL.
على الرغم من أنّ متصفّحات الويب التي تعرض شبكات ثلاثية الأبعاد معقّدة ليست مثيرة للاهتمام بحد ذاتها، إلا أنّ هذا الاختبار كان دليلاً على إمكانية استخدام تقنيات الواقع الافتراضي في الويب.