جلسات فنية افتراضية

تفاصيل الجلسة الفنية

ملخّص

تمت دعوة ستة فنانين للرسم والتصميم والنحت في الواقع الافتراضي. هذه هي العملية المتّبعة لتسجيل جلساتهم، وتحويل البيانات، وتقديمها في الوقت الفعلي باستخدام متصفحات الويب.

https://g.co/VirtualArtSessions

يا له من وقت للحياة! مع إدخال الواقع الافتراضي كمنتج استهلاكي، يتم اكتشاف احتمالات جديدة وغير مستكشَفة. وهي عبارة عن أحد منتجات Google المتوفرة على هاتف HTC Vive، وهي تتيح لك إمكانية الرسم في مساحة ثلاثية الأبعاد. عندما جربنا إمالة Brush لأول مرة، فإن شعورك بالرسم باستخدام وحدات التحكم التي تتبع الحركة إلى جانب وجودك "في غرفة بها قوى خارقة" يتخلى عنك؛ فلا توجد تجربة تشبه كثيرًا القدرة على الرسم في كل من المساحة الفارغة من حولك.

قطعة فنية افتراضية

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

تسجيل الرسومات في الواقع الافتراضي

تم دمج برنامج إمالة Brush في Unity، وهو تطبيق سطح مكتب يستخدم الواقع الافتراضي على مستوى الغرف لتتبع وضع رأسك (شاشة مثبَّتة على الرأس أو HMD) ووحدات التحكم في كل من يديك. يتم تلقائيًا تصدير العمل الفني الذي يتم إنشاؤه من خلال "الريشة السحرية" كملف .tilt. لتقديم هذه التجربة إلى الويب، أدركنا أننا بحاجة إلى أكثر من مجرد بيانات العمل الفني. لقد تعاونّا عن كثب مع فريق إمالة Brush لتعديل إمالة Brush من أجل تصدير إجراءات التراجع/الحذف فضلًا عن موضع رأس الفنان ويده 90 مرة في الثانية.

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

يتم حفظ المعلومات الأساسية لكل ضربة، وبالتالي يتم جمع كل من نوع الفرشاة وحجم الفرشاة ولون أحمر أخضر أزرق.

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

لاحظ أن الدوران هو شكل رباعي مكون من 4 مكونات. يعد ذلك مهمًا لاحقًا عندما نعرض الضغطات لتجنب قفل حامل الرأس.

تشغيل الرسومات باستخدام WebGL

من أجل عرض الرسومات في متصفّح ويب، استخدمنا THREE.js وكتبنا رمزًا لإنشاء الأشكال الهندسية يحاكي ما تفعله "الريشة السحرية".

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

رسومات WebGL

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

تكون عملية حساب شريط المثلث لكل ضربة مطابقة تقريبًا للرمز المستخدم في إمالة 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 );
}
وحدات رباعية مُدمَجة
وحدات رباعية مُدمَجة:

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

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 );

    });

}
أربع زخارف في أطلس زخرفة لفرشاة زيت
أربع زخارف في أطلس زخرفة لفرشاة زيتية
مع الفرشاة المائلة
في إمالة Brush
في WebGL
في WebGL

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

يتم تنفيذ الرسم التخطيطي بأكمله أعلاه في مكالمة رسم واحدة في WebGL
يتم تنفيذ الرسم التخطيطي بأكمله أعلاه في مكالمة رسم واحدة في WebGL

لاختبار التحمل على النظام، أنشأنا رسمًا استغرق 20 دقيقة لملء الفراغ بأكبر عدد ممكن من الرؤوس. لا يزال الرسم التخطيطي الناتج يتم تشغيله بسرعة 60 لقطة في الثانية في WebGL.

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

إخفاء أي رباعية يعني ببساطة تصغير رؤوسها إلى نقطة 0,0,0. عندما يصل الوقت إلى النقطة التي من المفترض أن يظهر فيها الرباعية، نُعيد ضبط موضع الرؤوس مرة أخرى.

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

قطعة فنية افتراضية

تسجيل الفنانين

شعرنا أن الرسومات نفسها لن تكون كافية. أردنا إظهار الفنانين داخل رسوماتهم، ورسم كل فرشاة فرشاة.

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

وبما أنّ جسم الفنان كان مغلقًا لمنعنا من رؤية ما خلفه، استخدمنا نظام Kinect المزدوج، كلاهما على جانبي الغرفة يشير إلى المركز.

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

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

فنان التسجيل

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

القنوات الأربع لجلسة فيديو مسجلة (قناتان ملونتان أعلاه وعمقان
أسفل)
القنوات الأربع لجلسة فيديو مسجّلة (قناتان ملوّنتان أعلاه واثنتان في الأسفل)

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

الشاشة التي يتم تركيبها على الرأس ووحدات التحكّم والجسيمات المصطفة
الشاشة التي يتم ارتداؤها على الرأس ووحدات التحكّم والجسيمات المصطفة

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

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

فنّانو التسجيل

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

الفنانون المشاركون في الألبوم

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

يفرض نظام iOS قيودًا على تشغيل الفيديو المضمّن، ونفترض أنّه يهدف إلى منع إزعاج المستخدمين من خلال إعلانات الفيديو على الويب التي يتم تشغيلها تلقائيًا. استخدمنا أسلوبًا مشابهًا للحلول الأخرى على الويب، وهي نسخ إطار الفيديو إلى لوحة وتعديل وقت البحث في الفيديو يدويًا كل 30/1 من الثانية.

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 بشكل ملحوظ، وذلك لأنّ عملية نسخ المخزن المؤقت بالبكسل من الفيديو إلى لوحة الرسم تستهلك قدرًا كبيرًا من وحدات المعالجة المركزية (CPU). للتغلب على هذا الأمر، عرضنا ببساطة إصدارات أصغر حجمًا من الفيديوهات نفسها والتي تسمح بمعدل 30 لقطة في الثانية على الأقل على هاتف iPhone 6.

الخلاصة

الإجماع العام لتطوير برامج الواقع الافتراضي اعتبارًا من عام 2016 هو إبقاء الأشكال الهندسية وأدوات التظليل بسيطة بحيث يمكنك التشغيل بمعدل 90 لقطة في الثانية في HMD. واتضح أن هذا الهدف كان هدفًا رائعًا للعروض التوضيحية لـ WebGL وذلك نظرًا للأساليب المستخدمة في خريطة الريشة السحرية بشكل لطيف جدًا مع WebGL.

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