वेबवीआर में डांस टोनाइट

जब Google Data Arts की टीम, Monicer और मैं खुद एक साथ काम करने के लिए, WebVR के साथ काम करने के बारे में सोच रही है, तो मुझे बहुत खुशी हुई. मैंने कई सालों तक उनकी टीम के काम को देखा है और उनके प्रोजेक्ट हमेशा मेरे साथ जुड़े रहे हैं. हमने दूसरे क्रिएटर्स के साथ मिलकर डांस टोनिट का इस्तेमाल किया. यह एलसीडी साउंडसिस्टम और उनके प्रशंसकों को वीआर के ज़रिए डांस करने का एक ऐसा अनुभव मिला जो लगातार बदलता रहा. हमने इसे इस तरह किया.

कॉन्सेप्ट

हमने WebVR का इस्तेमाल करके प्रोटोटाइप की एक सीरीज़ बनाने के साथ शुरुआत की. यह एक ऐसा ओपन स्टैंडर्ड है, जिससे अपने ब्राउज़र से किसी वेबसाइट पर जाकर वीआर में जाया जा सकता है. इसका मकसद सभी के लिए वीआर अनुभव पाने को आसान बनाना है, फिर चाहे आपके पास कोई भी डिवाइस हो.

हमने इस समस्या को दिल से लिया है. हम जो भी लेकर आए हैं, उसे सभी तरह के VR पर काम करना चाहिए. Google के Daydream View, Cardboard और Samsung के Gear VR जैसे रूम-स्केल सिस्टम से लेकर HTC VIVE और Oculus Rift जैसे रूम-स्केल सिस्टम तक, जो आपके वर्चुअल परिवेश में आपकी शारीरिक गतिविधियों को दिखाते हैं. सबसे अहम बात यह है कि हमें लगा कि वेब की भावना से ही कुछ ऐसा बनाया जा सकता है जो उन सभी लोगों के लिए भी काम करे जिनके पास वीआर डिवाइस नहीं है.

1. खुद करके देखें मोशन कैप्चर

हम उपयोगकर्ताओं को रचनात्मक रूप से शामिल करना चाहते थे, इसलिए हमने वीआर का इस्तेमाल करके, इस प्रोग्राम में हिस्सा लेने और खुद को ज़ाहिर करने की संभावनाओं पर ध्यान देना शुरू किया. आप वीआर में कितनी बारीकी से इधर-उधर जा सकते हैं और उनमें कितनी सच्चाई थी, हम इस बात से प्रभावित थे. इससे हमें एक आइडिया मिला. उपयोगकर्ताओं से कुछ देखने या बनाने के बजाय, उनकी हरकतों को रिकॉर्ड करना कैसा रहेगा?

कोई व्यक्ति डांस टोनाइट पर खुद को रिकॉर्ड कर रहा है. डिवाइस के पीछे की स्क्रीन से पता चलता है कि वे हेडसेट में क्या देख रहे हैं

हमने एक प्रोटोटाइप बनाया, जिसमें हमने नाचते हुए अपने VR चश्मे और नियंत्रकों की स्थिति को रिकॉर्ड किया. हमने रिकॉर्ड की गई जगहों को ऐब्स्ट्रैक्ट आकार से बदल दिया और नतीजों को देखकर हैरान हो गए. ये नतीजे बेहद मानवीय थे और इनमें पूरी शख्सियत शामिल थी! हमें जल्द ही एहसास हुआ कि हम घर पर सस्ते मोशन कैप्चर करने के लिए WebVR का इस्तेमाल कर सकते हैं.

WebVR में, डेवलपर VRPose ऑब्जेक्ट की मदद से उपयोगकर्ता के सिर की पोज़िशन और स्क्रीन की दिशा को ऐक्सेस कर सकता है. यह मान वीआर हार्डवेयर से हर फ़्रेम को अपडेट किया जाता है, ताकि आपका कोड सही तरीके से नए फ़्रेम रेंडर कर सके. WebVR के साथ GamePad API की मदद से, हम GamepadPose ऑब्जेक्ट की मदद से, उपयोगकर्ता कंट्रोलर की पोज़िशन/ओरिएंटेशन को ऐक्सेस कर सकते हैं. हम इन सभी पोज़िशन और ओरिएंटेशन की वैल्यू को हर फ़्रेम में सेव करते हैं. इस तरह, हम उपयोगकर्ता की गतिविधियों की "रिकॉर्डिंग" बनाते हैं.

2. मिनिमिज़्म और पोशाक

आज के रूम स्केल वीआर उपकरण से, हम उपयोगकर्ता के शरीर के तीन बिंदुओं: उसके सिर और दो हाथ को ट्रैक कर सकते हैं. डांस टोनाइट में, हम अंतरिक्ष के इन तीन बिंदुओं के हिलने पर मानवता पर ध्यान केंद्रित करना चाहते थे. अपने मकसद को पूरा करने के लिए, हमने इस सुविधा पर ज़्यादा ध्यान देने के लिए, जितना हो सके उतना थोड़ा-बहुत कम किया. लोगों का दिमागी काम करने का विचार हमें अच्छा लगा.

इस वीडियो में, स्वीडिश मनोवैज्ञानिक गुन्नार योहानसन का काम दिखाया गया है. ऐसे वीडियो का उदाहरण हमने उन चीज़ों को हटाने के बारे में बताया है. यह दिखाता है कि तैरते हुए सफ़ेद बिंदुओं को गति में देखे जाने पर वे कैसे तुरंत पहचान लेते हैं.

देखने में, हमें रंग-बिरंगे कमरों और ज्यामितीय कपड़ों से प्रेरणा मिली. यह रिकॉर्डिंग 1970 में ऑस्कर श्लैमर के ट्राएडिक बैले की रिकॉर्डिंग में मार्गरेट हेस्टिंग्स की बनाई गई थी.

श्लैमर के लिए ऐब्सट्रैक्ट ज्यामितीय पोशाक चुनने की वजह यह थी कि उनके डांसर को कठपुतलियों और कठपुतलियों तक सीमित कर दिया गया. इसके अलावा, हमारा दूसरी जगह 'डांस टोनाइट' था.

हमने अपनी पसंद के आकार को इस आधार पर तय किया कि वे रोटेशन के ज़रिए कितनी जानकारी दें. भले ही, इसे कैसे भी घुमाया गया हो, एक ऑर्ब एक जैसा दिखता है. हालांकि, एक कोन उस दिशा में ले जाता है जहां वह दिखता है. साथ ही, वह पीछे की ओर से अलग दिखता है.

3. हिलने-डुलने के लिए लूप पैडल

हम रिकॉर्ड किए गए लोगों के बड़े समूहों को एक-दूसरे के साथ नाचते और चलते हुए दिखाना चाहते थे. ऐसा करना मुमकिन नहीं होगा, क्योंकि वीआर डिवाइस की संख्या बहुत ज़्यादा नहीं है. लेकिन हम अब भी चाहते थे कि लोगों का समूह एक-दूसरे के साथ एक-दूसरे के लिए प्रतिक्रिया दे. हमारा दिमाग 1964 के अपने वीडियो “कैनन” में नॉर्मन मैकक्लेरन के बार-बार होने वाले परफ़ॉर्मेंस को देखता है.

मैकक्लेरन की परफ़ॉर्मेंस में कई तरह की कोरियोग्राफ़ की गई गतिविधियां शामिल हैं, जो हर लूप के बाद एक-दूसरे से इंटरैक्ट करना शुरू कर देती हैं. यह संगीत के लूप पैडल की तरह है, जहां संगीतकार लाइव संगीत के अलग-अलग हिस्सों को लेयर करके खुद को ढालते हैं. हम चाहते थे कि हम एक ऐसा माहौल बनाएं जिसमें लोग बिना किसी रुकावट के परफ़ॉर्मेंस को बेहतरीन तरीके से पेश कर सकें.

4. आपस में जुड़े हुए रूम

आपस में जुड़े हुए रूम

बहुत सारे संगीत की तरह, एलसीडी साउंडसिस्टम के ट्रैक भी सही समय पर ही बनाए गए हैं. उनके ट्रैक, Tonite को हमारे प्रोजेक्ट में दिखाया गया है. इसके ट्रैक में सिर्फ़ आठ सेकंड की अवधि का डेटा है. हम चाहते थे कि उपयोगकर्ता, ट्रैक में होने वाले हर 8 सेकंड के लूप के लिए परफ़ॉर्मेंस बेहतर बनाएं. इन तरीकों से संगीत की लय में कोई बदलाव नहीं होता, लेकिन इनके संगीत से जुड़े कॉन्टेंट में बदलाव होता रहता है. जैसे-जैसे गाना आगे बढ़ता है, वैसे-वैसे अलग-अलग इंस्ट्रुमेंट और आवाज़ के साथ कुछ ऐसे पल खास होते हैं जिन पर कलाकार अलग-अलग तरह से प्रतिक्रिया दे सकते हैं. इनमें से हर एक को एक कमरे के तौर पर दिखाया जाता है, जिसमें लोग अपनी ज़रूरत के हिसाब से परफ़ॉर्मेंस बना सकते हैं.

परफ़ॉर्मेंस के लिए ऑप्टिमाइज़ेशन: फ़्रेम को न छोड़ें

एक ही कोड बेस पर चलने वाला ऐसा मल्टी-प्लैटफ़ॉर्म VR अनुभव बनाना आसान काम नहीं है जो हर डिवाइस या प्लैटफ़ॉर्म के लिए सबसे अच्छा परफ़ॉर्म करता हो.

वीआर में, सबसे ज़्यादा उल्टी होने की वजह यह होती है कि फ़्रेम रेट आपके मूवमेंट के साथ मेल नहीं खाता. अगर आप अपना सिर घुमाते हैं, लेकिन आपकी आंखों को दिखाई देने वाले विज़ुअल आपके कान के अंदर की महसूस होने वाली गति से मेल नहीं खाते, तो इससे आपका पेट तुरंत चर्न आउट हो जाएगा. इस वजह से, हमें ज़्यादा फ़्रेम रेट देर से बचने की ज़रूरत थी. यहां कुछ ऑप्टिमाइज़ेशन दिए गए हैं जिन्हें हमने लागू किया है.

1. इंस्टेंस्ड बफ़र ज्यामिति

हमारे पूरे प्रोजेक्ट में सिर्फ़ कुछ 3d ऑब्जेक्ट का इस्तेमाल किया जाता है, इसलिए हम इंस्टेंस्ड बफ़र जियोमेट्री का इस्तेमाल करके परफ़ॉर्मेंस को काफ़ी बेहतर बना पाए. इसका मतलब है कि इससे आप अपने ऑब्जेक्ट को जीपीयू पर एक बार अपलोड कर सकते हैं और एक ही ड्रॉ कॉल में उस ऑब्जेक्ट के कई "इंस्टेंस" पा सकते हैं. डांस टोनाइट में, हमारे पास सिर्फ़ 3 अलग-अलग चीज़ें हैं (एक कोन, एक सिलेंडर, और छेद वाला कमरा), लेकिन हो सकता है कि उन चीज़ों की सैकड़ों कॉपी हों. इंस्टेंस बफ़र जियोमेट्री, ThreeJS का एक हिस्सा है. हालांकि, हमने Dusan Bosnjak के एक्सपेरिमेंट और पहले से चल रहे फ़ोर्क का इस्तेमाल किया, जो THREE.InstanceMesh को लागू करता है. इसकी मदद से, इंस्टेंस्ड बफ़र जियोमेट्री के साथ काम करना ज़्यादा आसान हो जाता है.

2. कचरा इकट्ठा करने वाले व्यक्ति से बचना

दूसरी स्क्रिप्टिंग भाषाओं की तरह, JavaScript अपने-आप मेमोरी खाली करता है. ऐसा करने के लिए यह पता लगाया जाता है कि जो ऑब्जेक्ट दिए गए हैं उनका इस्तेमाल नहीं किया जाता. इस प्रक्रिया को गारबेज कलेक्शन कहते हैं.

ऐसा कब होगा, इस पर डेवलपर का कोई कंट्रोल नहीं होता. कचरा इकट्ठा करने वाला व्यक्ति किसी भी समय हमारे दरवाज़े पर पहुंच सकता है और कचरे को खाली करना शुरू कर सकता है. इससे वही फ़्रेम ड्रॉप हो जाता है जो उसे अच्छा लगता है.

इस समस्या का हल यह है कि हम अपनी चीज़ों को रीसाइकल करके कम से कम कचरा पैदा करें. हर कैलकुलेशन के लिए एक नया वेक्टर ऑब्जेक्ट बनाने के बजाय, हमने स्क्रैच ऑब्जेक्ट को फिर से इस्तेमाल करने के लिए मार्क किया. हम अपने संदर्भ को अपने दायरे से बाहर लेकर उन पर रोक लगाते हैं, इसलिए उन्हें हटाने के लिए मार्क नहीं किया जाता था.

उदाहरण के लिए, इस कोड के ज़रिए उपयोगकर्ता के सिर और हाथों की लोकेशन मैट्रिक्स को पोज़िशन/घूर्णन वैल्यू की अरे में बदला जा सकता है. हर फ़्रेम को स्टोर किया जाता है. SERIALIZE_POSITION, SERIALIZE_ROTATION, और SERIALIZE_SCALE का फिर से इस्तेमाल करने से, हम मेमोरी के बंटवारे और गार्बेज कलेक्शन से बच जाते हैं. यह ऐसा तब होता है, जब फ़ंक्शन को हर बार कॉल करने पर नए ऑब्जेक्ट बनाए जाते हैं.

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. मोशन और प्रोग्रेसिव प्लेबैक का क्रम तय करना

वीआर में लोगों की गतिविधियों को कैप्चर करने के लिए, हमें उनके हेडसेट और कंट्रोलर की स्थिति और उनके रोटेशन को क्रम से लगाना होगा. साथ ही, इस डेटा को अपने सर्वर पर अपलोड करना होगा. हमने हर फ़्रेम के लिए, पूरी तरह से ट्रांसफ़ॉर्मेशन मैट्रिक्स कैप्चर करना शुरू कर दिया है. इसने अच्छा परफ़ॉर्म किया, लेकिन 90 फ़्रेम प्रति सेकंड की दर से 16 संख्याओं को हर जगह की 3 पोज़िशन से गुणा करने पर, बहुत बड़ी फ़ाइलें बनती हैं. इस वजह से, डेटा अपलोड और डाउनलोड करने के लिए ज़्यादा इंतज़ार करना पड़ता है. ट्रांसफ़ॉर्मेशन मैट्रिक्स से सिर्फ़ पोज़िशनल और घूर्णन डेटा लेकर, हम इन वैल्यू को 16 से 7 तक ला पाए.

वेब पर आने वाले लोग अक्सर किसी लिंक पर क्लिक कर देते हैं. हालांकि, उन्हें इस बात की सटीक जानकारी नहीं होती कि उन्हें क्या देखना है. इसलिए, हमें विज़ुअल कॉन्टेंट तुरंत दिखाना होता है, नहीं तो वे कुछ सेकंड में चले जाएंगे.

इस वजह से, हम यह पक्का करना चाहते थे कि हमारा प्रोजेक्ट जल्द से जल्द शुरू हो जाए. शुरुआत में, हम अपनी हलचल का डेटा लोड करने के लिए, JSON को फ़ॉर्मैट के तौर पर इस्तेमाल कर रहे थे. समस्या यह है कि हमें पूरी JSON फ़ाइल लोड करनी पड़ती है, ताकि हम उसे पार्स कर सकें. बहुत प्रोग्रेसिव नहीं.

डांस टोनाइट जैसे किसी प्रोजेक्ट को ज़्यादा से ज़्यादा फ़्रेम रेट पर दिखाने के लिए, ब्राउज़र को JavaScript कैलकुलेशन के लिए हर फ़्रेम के लिए कुछ ही समय मिलता है. अगर ज़्यादा समय लगता है, तो ऐनिमेशन अटकने लगते हैं. शुरुआत में हमें स्टटर का सामना करना पड़ा, क्योंकि ब्राउज़र ने इन बड़ी JSON फ़ाइलों को डिकोड कर दिया.

हमें स्ट्रीम करने के लिए, डेटा का एक आसान फ़ॉर्मैट मिला है. इसे NDJSON या Newline आधिकारिक JSON नाम से जाना जाता है. यहां सलाह यह है कि कई मान्य JSON स्ट्रिंग वाली एक फ़ाइल बनाई जाए. हर एक स्ट्रिंग अपनी-अपनी लाइन में हो. इससे, फ़ाइल लोड होने के दौरान उसे पार्स किया जा सकता है. इससे, हम पूरी तरह से लोड होने से पहले परफ़ॉर्मेंस दिखा पाते हैं.

यहां बताया गया है कि हमारी किसी रिकॉर्डिंग का सेक्शन कैसा दिखता है:

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

NDJSON का इस्तेमाल करके, हम परफ़ॉर्मेंस के अलग-अलग फ़्रेम के डेटा को स्ट्रिंग के तौर पर बनाए रखते हैं. हमें सही समय पर डेटा को पोज़िशनल डेटा में डिकोड करने और समय के साथ प्रोसेसिंग की ज़रूरत को पूरा करने के लिए इंतज़ार करना पड़ता था.

4. इंटरपोलेटिंग मूवमेंट

एक ही समय में हम 30 से 60 परफ़ॉर्मेंस के बीच दिखाना चाहते थे, इसलिए हमें अपनी डेटा दर को पहले से कहीं नीचे लाना था. Data Arts टीम ने इसी समस्या को अपने वर्चुअल आर्ट सेशन प्रोजेक्ट में हल किया. इसमें, वे til Brush का इस्तेमाल करके, वीआर में कलाकारों की पेंटिंग की रिकॉर्डिंग चलाती हैं. उन्होंने इस समस्या को हल करने के लिए, कम फ़्रेम रेट के साथ उपयोगकर्ता के डेटा के इंटरमीडिएट वर्शन बनाए. साथ ही, उन्हें चलाते समय फ़्रेम के बीच इंटरपोलेशन किया. हमें यह जानकर हैरानी हुई कि हम 15 FPS (फ़्रेम प्रति सेकंड) पर चलने वाली इंटरपोलेट की गई रिकॉर्डिंग और 90 FPS (फ़्रेम प्रति सेकंड) की ओरिजनल रिकॉर्डिंग के बीच का फ़र्क़ पता शायद ही लगा पाएं.

खुद को देखने के लिए, आप ?dataRate= क्वेरी स्ट्रिंग का इस्तेमाल करके डांस टनाइट को अलग-अलग रेट पर डेटा चलाने के लिए मजबूर कर सकते हैं. इसका इस्तेमाल करके, रिकॉर्ड की गई गति की तुलना 90 फ़्रेम प्रति सेकंड, 45 फ़्रेम प्रति सेकंड या 15 फ़्रेम प्रति सेकंड की रफ़्तार से की जा सकती है.

पोज़िशन तय करने के लिए, हम पिछले मुख्य-फ़्रेम और अगले मुख्य-फ़्रेम के बीच एक लीनियर इंटरपोलेशन करते हैं. यह इस आधार पर तय किया जाता है कि हम मुख्य-फ़्रेम के बीच के समय में कितना करीब हैं (अनुपात):

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

ओरिएंटेशन के लिए, हम मुख्य-फ़्रेम के बीच एक गोलाकार लीनियर इंटरपोलेशन (स्लरप) करते हैं. स्क्रीन की दिशा को क्वाटर्नियन के तौर पर सेव किया जाता है.

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. मूवमेंट को संगीत के साथ सिंक किया जा रहा है

रिकॉर्ड किए गए ऐनिमेशन के किस फ़्रेम को चलाना है, यह जानने के लिए हमें संगीत का मौजूदा समय मिलीसेकंड तक जानने की ज़रूरत है. एचटीएमएल ऑडियो एलिमेंट, धीरे-धीरे लोड करने और चलाने के लिए सबसे बढ़िया है, लेकिन इससे मिलने वाली समय प्रॉपर्टी, ब्राउज़र के फ़्रेम लूप के साथ सिंक नहीं होती है. यह हमेशा थोड़ी देर के लिए बंद होता है. कभी-कभी मिलीसेकंड का कुछ समय बहुत जल्दी का, कभी-कभी बहुत देर से.

इससे हमारी खूबसूरत डांस रिकॉर्डिंग में रुकावट आती है और हम चाहते हैं कि किसी भी कीमत पर परफ़ॉर्मेंस न हो. इसे ठीक करने के लिए, हमने JavaScript में अपना टाइमर लागू किया. इस तरह से हम यह पक्का कर सकते हैं कि फ़्रेम के बीच स्विच करने में लगने वाला समय, बिलकुल वही समय है जो आखिरी फ़्रेम के बाद बीत चुका है. जब भी हमारा टाइमर संगीत के साथ 10 मि॰से॰ से ज़्यादा समय तक सिंक नहीं होता है, तो हम उसे फिर से सिंक कर देते हैं.

6. घुलना-मिलना और कोहरा

हर कहानी का अंत अच्छा होता है और हम उन लोगों के लिए कुछ अलग करना चाहते थे जिन्होंने इस कहानी को देखने के बाद आखिरी दौर तक पहुंचाया. आखिरी कमरे से बाहर निकलते ही, आपको वहां जाने का मौका मिलता है जहां कोन और सिलेंडर का शांत माहौल दिखता है. “क्या यह खत्म हो गया?”, आपको हैरानी हुई. जैसे-जैसे आप मैदान में आगे बढ़ते हैं, अचानक संगीत के रंग में अलग-अलग तरह के कोन और सिलेंडर के समूह आने लगते हैं, जो नाचने लगते हैं. आप खुद को एक बहुत बड़ी पार्टी के बीच में पाते हैं! जैसे-जैसे संगीत अचानक रूक जाता है, वैसे-वैसे सब कुछ वहीं पर गिरने लगता है.

एक दर्शक के तौर पर यह उन्हें शानदार लगा, लेकिन परफ़ॉर्मेंस को बेहतर बनाने के लिए कुछ रुकावटें आईं. रूम स्केल वीआर डिवाइस और उनके हाई एंड गेमिंग रिग ने 40 और परफ़ॉर्मेंस के साथ शानदार परफ़ॉर्म किया. ये परफ़ॉर्मेंस, गेम के नए आखिर के लिए ज़रूरी हैं. हालांकि, कुछ मोबाइल डिवाइसों पर फ़्रेम रेट आधा हो गया था.

इसे रोकने के लिए, हमने फ़ॉग शुरू किया. कुछ दूरी के बाद सब कुछ धीरे-धीरे काला हो जाता है. हमें न दिखने वाले कॉन्टेंट का हिसाब लगाने या ड्रॉ करने की ज़रूरत नहीं है, इसलिए हम उन कमरों में भी परफ़ॉर्मेंस को फ़िल्टर करते हैं जो नहीं दिखते. इससे हम सीपीयू और जीपीयू, दोनों के काम को सेव कर पाते हैं. लेकिन सही दूरी कैसे तय करें?

कुछ डिवाइस में ऐसी कोई भी चीज़ हो जाती है जिसे आप फेंकते हैं और अन्य डिवाइस में ज़्यादा रुकावट होती है. हमने स्लाइडिंग स्केल को लागू करने का विकल्प चुना है. फ़्रेम प्रति सेकंड की मात्रा को लगातार मापकर, हम अपने कोहरे की दूरी को उसके मुताबिक हिस्सा ले सकते हैं. जब तक फ़्रेम रेट ठीक से चल रहा है, तब तक हम कोहरे को कम करके रेंडर का ज़्यादा काम करने की कोशिश करते हैं. अगर फ़्रेमरेट ठीक से नहीं चल रहा है, तो हम फ़ॉगरेट को नज़दीक ला देते हैं. इससे हम अंधेरे में रेंडरिंग परफ़ॉर्मेंस को छोड़ देते हैं.

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

सभी के लिए कुछ न कुछ: वेब के लिए वीआर बनाना

मल्टी-प्लैटफ़ॉर्म को डिज़ाइन करने और डेवलप करने का मतलब है, हर उपयोगकर्ता की ज़रूरतों को ध्यान में रखना. डिज़ाइन से जुड़े हर फ़ैसले के साथ, हमें यह देखना था कि वह अन्य उपयोगकर्ताओं पर कैसे असर डाल सकता है. आप यह कैसे पक्का करेंगे कि आप जो वीआर में देखते हैं वह वीआर के बिना और वीज़ा के बिना भी रोमांचक हैं?

1. द येलो ऑर्ब

ऐसे में हमारे रूम-स्केल वाले वीआर उपयोगकर्ता परफ़ॉर्मेंस देंगे, लेकिन मोबाइल वीआर डिवाइस (जैसे, Cardboard, Daydream View या Samsung Gear) के उपयोगकर्ता इस प्रोजेक्ट का अनुभव किस तरह करेंगे? इसके लिए, हमने अपने परिवेश में एक नया एलिमेंट पेश किया है: पीला ऑर्ब.

द येलो ऑर्ब
द येलो ऑर्ब

जब आप वीआर में कोई प्रोजेक्ट देखते हैं, तो आप उसे पीले रंग के ऑर्ब के नज़रिए से देखते हैं. एक कमरे से दूसरे कमरे में फ़्लोट करने पर, डांसर आपकी मौजूदगी पर प्रतिक्रिया देते हैं. वे आपके हाथ में बात करते हैं, आपके चारों ओर नाचते हैं, आपकी पीठ के पीछे मज़ेदार इशारे करते हैं, और तेज़ी से बाहर निकल जाते हैं, ताकि वे आप पर टकरा न जाएं. पीला ऑर्ब हमेशा ध्यान का केंद्र होता है.

इसकी वजह यह है कि किसी परफ़ॉर्मेंस को रिकॉर्ड करते समय, पीले रंग का ऑर्ब कमरे के बीच में घूमता है और संगीत के साथ वापस घूमता है. ओर्ब की स्थिति से कलाकार को पता चलता है कि वे समय में कहां हैं और उनके लूप में कितना समय बचा है. इससे उन्हें परफ़ॉर्मेंस को बेहतर बनाने पर फ़ोकस करने में मदद मिलती है.

2. एक और नज़रिया

हम वीआर के बिना लोगों को छोड़ना नहीं चाहते, खास तौर पर इसलिए, क्योंकि वे हमारे सबसे बड़े दर्शक हो सकते हैं. नकली वीआर अनुभव देने के बजाय, हम स्क्रीन पर आधारित डिवाइस को उनका अनुभव देना चाहते हैं. हमारे पास ऊपर दी गई परफ़ॉर्मेंस को आइसोमेट्रिक नज़रिए से दिखाने का आइडिया था. कंप्यूटर गेम में इस तरह की चीज़ों का अनुभव रहा है. इसका इस्तेमाल पहली बार 1982 में अंतरिक्ष शूटर गेम Zaxxon में किया गया. जहां वीआर के उपयोगकर्ता स्क्रीन के किनारों पर बने रहते हैं, वहीं आइसोमेट्रिक रवैया देखने पर कार्रवाई को ईश्वर जैसा लगता है. हमने मॉडल को थोड़ा बड़ा बनाया, जिससे डॉल-हाउस जैसा दिखता है.

3. शैडो: इसे तब तक नकली बनाएं, जब तक आप इसे न बनाएं

हमने पाया कि हमारे कुछ उपयोगकर्ताओं को हमारे आइसोमेट्रिक नज़रिए को गहराई से देखने में मुश्किल हो रही थी. मुझे पूरा यकीन है कि यही वजह है कि ज़क्सन इतिहास के उन पहले कंप्यूटर गेम में से भी एक था, जिसमें फ़्लाइंग ऑब्जेक्ट के नीचे बहुत तेज़ी से परछाई दिखा सकती थी.

परछाइयां हटाएं

यह पता चला कि 3D में परछाई बनाना कठिन है. यह खास तौर पर, सीमित परेशानी वाले डिवाइसों के लिए है. जैसे, मोबाइल फ़ोन. शुरुआत में, हमें इस समीकरण से उन्हें हटाने का एक मुश्किल फ़ैसला लेना पड़ा. हालांकि, थ्री.js के लेखक और डेमो हैकर Mr Doob से सलाह लेने के बाद, उन्होंने उन्हें धोखा देने का नया आइडिया दिया.

हमारी हर तैरती हुई चीज़ किस तरह हमारी रोशनी को धुंधली कर रही है और अलग-अलग आकार की परछाई फेंक रही है, इसका हिसाब लगाने की बजाय, हम हर एक के नीचे एक समान गोलाकार धुंधली टेक्स्चर की इमेज बनाते हैं. हमारे विज़ुअल असल ज़िंदगी की नकल करने की कोशिश नहीं कर रहे हैं, इसलिए हमने पाया कि हम थोड़ा-बहुत बदलाव करके, इसे बेहतर बनाने की कोशिश कर सकते हैं. जब चीज़ें ज़मीन के करीब आती हैं, तब टेक्सचर का रंग गहरा और छोटा हो जाता है. जब वे ऊपर की ओर बढ़ते हैं, तो हम टेक्सचर को और पारदर्शी और बड़ा बनाते हैं.

उन्हें बनाने के लिए, हमने इस टेक्सचर को सॉफ़्ट व्हाइट से ब्लैक ग्रेडिएंट (बिना ऐल्फ़ा ट्रांसपेरेंसी के) के साथ इस्तेमाल किया. हम सामग्री को पारदर्शी के तौर पर सेट करते हैं और इसमें सब्सक्रिप्शन शामिल करते हैं. इससे उन्हें ओवरलैप होने पर, बेहतर तरीके से ब्लेंड करने में मदद मिलती है:

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. वहां होने पर

वीआर के बिना दर्शक, कलाकार के सिर पर क्लिक करके डांसर के नज़रिए से चीज़ें देख सकते हैं. इस नज़रिए से, बहुत सारी छोटी-छोटी जानकारी साफ़ तौर पर दिखती है. अपनी परफ़ॉर्मेंस को एक जैसा रखने की कोशिश में डांसर तेज़ी से एक-दूसरे को देखते हैं. जैसे ही ऑर्ब कमरे में आता है, आप उन्हें घबराहट में अपनी दिशा में देख रहे होते हैं. एक दर्शक के तौर पर, इन गतिविधियों पर आपका कोई असर नहीं होता, लेकिन इससे इमर्सिव अनुभव शानदार होता है. एक बार फिर, हमने अपने उपयोगकर्ताओं को माउस से कंट्रोल किया जाने वाला एक ज़बरदस्त वीआर वर्शन देने की जगह ऐसा करना ही सही बताया.

5. रिकॉर्डिंग शेयर की जा रही हैं

हमें पता है कि एक-दूसरे पर प्रतिक्रिया देते हुए कलाकारों की 20 लेयर की बारीक कोरियोग्राफ़ी की मदद से आप कितना गर्व महसूस कर सकते हैं. हम जानते थे कि हमारे उपयोगकर्ता इसे अपने दोस्तों को दिखाना चाहेंगे. हालांकि, इस उपलब्धि को दिखाने वाली इमेज, इस बात को ज़्यादा बेहतर नहीं बना पाती. इसके बजाय, हम उपयोगकर्ताओं को उनकी परफ़ॉर्मेंस का वीडियो शेयर करने की अनुमति देना चाहते थे. दरअसल, GIF क्यों नहीं बना है? हमारे एनिमेशन सपाट हैं, जो फ़ॉर्मैट के सीमित रंग पटल के लिए बेहतरीन हैं.

रिकॉर्डिंग शेयर की जा रही हैं

हमने GIF.js का रुख किया है, जो एक JavaScript लाइब्रेरी है. इसकी मदद से, ब्राउज़र में ही ऐनिमेशन वाले GIF को कोड में बदला जा सकता है. यह फ़्रेम की एन्कोडिंग को वेब वर्कर पर ऑफ़लोड कर देता है, जो बैकग्राउंड में अलग-अलग प्रोसेस के रूप में चल सकते हैं. इस तरह, साथ-साथ काम करने वाले कई प्रोसेसर का फ़ायदा ले सकते हैं.

अफ़सोस की बात यह है कि ऐनिमेशन के लिए हमें जितने फ़्रेम की ज़रूरत थी, वह भी धीमा था. GIF, सीमित रंग पटल का इस्तेमाल करके छोटी फ़ाइलें बना सकता है. हमने पाया कि हर पिक्सल के लिए सबसे नज़दीकी रंग ढूंढने में ज़्यादातर समय बीतता था. हम एक छोटे शॉर्ट कट में हैकिंग करके इस प्रोसेस को दस गुना ऑप्टिमाइज़ कर पाए: अगर पिक्सल का रंग बिलकुल पिछले रंग जैसा ही है, तो पैलेट के उसी रंग का इस्तेमाल करें जो पहले था.

अब हमारे पास तेज़ी से कोड में बदलने का विकल्प मौजूद था, लेकिन उनसे बनने वाली GIF फ़ाइलों का साइज़ बहुत बड़ा था. GIF फ़ॉर्मैट की मदद से, यह बताया जा सकता है कि हर फ़्रेम को सबसे ऊपर दिखाने के लिए, उसे किस तरह से नष्ट करना है. छोटी फ़ाइलें पाने के लिए, हम हर फ़्रेम के हर पिक्सल को अपडेट करने के बजाय, सिर्फ़ उन पिक्सल को अपडेट करते हैं जिनमें बदलाव हुआ है. कोड में बदलने की प्रोसेस को फिर से धीमा करने पर, हमारी फ़ाइल का साइज़ भी ठीक से कम हुआ.

6. ठोस आधार: Google Cloud और Firebase

“यूज़र जनरेटेड कॉन्टेंट : यूजीसी” साइट का बैकएंड अक्सर मुश्किल और नाज़ुक हो सकता है, लेकिन हमने एक ऐसा सिस्टम बनाया है जो Google Cloud और Firebase की मदद से आसान और मज़बूत है. जब कोई कलाकार सिस्टम में नया डांस अपलोड करता है, तो Firebase से पुष्टि करने की सुविधा से उसकी पहचान छिपाकर पुष्टि की जाती है. उन्हें Firebase के लिए Cloud Storage का इस्तेमाल करके, अपनी रिकॉर्डिंग को किसी अस्थायी स्पेस में अपलोड करने की अनुमति दी जाती है. अपलोड पूरा होने के बाद, क्लाइंट मशीन अपने Firebase टोकन का इस्तेमाल करके Firebase के लिए Cloud Functions एचटीटीपी ट्रिगर को कॉल करती है. इससे ऐसी सर्वर प्रोसेस ट्रिगर होती है जो सबमिशन की पुष्टि करती है, डेटाबेस रिकॉर्ड बनाती है, और रिकॉर्डिंग को Google Cloud Storage पर सार्वजनिक डायरेक्ट्री में ले जाती है.

ठोस मैदान

हमारा पूरा सार्वजनिक कॉन्टेंट, Cloud Storage बकेट में फ़्लैट फ़ाइलों की एक सीरीज़ में स्टोर होता है. इसका मतलब है कि हमारा डेटा दुनिया भर में तेज़ी से ऐक्सेस किया जा सकता है. साथ ही, हमें इस बात की चिंता करने की ज़रूरत नहीं है कि डेटा की उपलब्धता पर बहुत ज़्यादा ट्रैफ़िक आता है.

हमने Firebase रीयल टाइम डेटाबेस और क्लाउड फ़ंक्शन एंडपॉइंट का इस्तेमाल करके एक आसान मॉडरेशन/क्यूरेशन टूल बनाया है. इससे हम वीआर में सबमिट किए गए हर नए कॉन्टेंट को देख सकते हैं और किसी भी डिवाइस से नई प्लेलिस्ट पब्लिश कर सकते हैं.

7. सर्विस वर्कर

सर्विस वर्कर, हाल ही में हुआ एक इनोवेशन है, जो वेबसाइट ऐसेट की कैश मेमोरी को मैनेज करने में मदद करता है. हमारे मामले में, सर्विस वर्कर वापस आने वाले विज़िटर के लिए हमारी सामग्री को बहुत तेज़ी से लोड करते हैं और साइट को ऑफ़लाइन भी काम करने देते हैं. ये ज़रूरी सुविधाएं हैं, क्योंकि हमारे कई विज़िटर अलग-अलग क्वालिटी वाले मोबाइल कनेक्शन का इस्तेमाल करते हैं.

इस्तेमाल में आसान वेबपैक प्लगिन की मदद से, इस प्रोजेक्ट में सर्विस वर्कर जोड़ना आसान था. नीचे दिए गए कॉन्फ़िगरेशन में, हम एक सर्विस वर्कर जनरेट करते हैं, जो हमारी सभी स्टैटिक फ़ाइलों को अपने-आप कैश मेमोरी में सेव करेगा. अगर यह उपलब्ध है, तो नेटवर्क से नई प्लेलिस्ट फ़ाइल आ जाएगी, क्योंकि प्लेलिस्ट हर समय अपडेट होती रहेगी. उपलब्ध होने पर, रिकॉर्डिंग की सभी json फ़ाइलों को कैश मेमोरी से लिया जाना चाहिए, क्योंकि ये कभी नहीं बदलेंगे.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

फ़िलहाल, प्लगिन हमारी संगीत फ़ाइलों जैसी धीरे-धीरे लोड होने वाली मीडिया एसेट को हैंडल नहीं करता. इसलिए, हमने इस सुविधा को बेहतर बनाने के लिए इन फ़ाइलों के लिए Cloud Storage Cache-Control हेडर को public, max-age=31536000 पर सेट किया है. ऐसा इसलिए किया गया है, ताकि ब्राउज़र इस फ़ाइल को एक साल तक कैश मेमोरी में सेव रखे.

नतीजा

हम यह देखने के लिए उत्साहित हैं कि कलाकार इस अनुभव को कैसे बेहतर बनाएंगे और मोशन का इस्तेमाल करके क्रिएटिव एक्सप्रेशन के लिए इसका इस्तेमाल कैसे करेंगे. हमने सभी कोड ओपन सोर्स रिलीज़ कर दिए हैं. इन्हें https://github.com/puckey/dance-tonite पर देखा जा सकता है. वीआर के इन शुरुआती दिनों में और खास तौर पर WebVR के लिए, हम यह देखना चाहते हैं कि यह नया माध्यम क्या नए क्रिएटिव और अनपेक्षित निर्देशों को अपनाएगा. डांस ऑन है.