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

जब Google Data Arts की टीम ने Moniker और मुझे, WebVR की सुविधाओं को एक्सप्लोर करने के लिए एक साथ काम करने के बारे में बताया, तो मुझे बहुत खुशी हुई. मैंने पिछले कुछ सालों में उनकी टीम के काम को देखा है और उनके प्रोजेक्ट से मुझे हमेशा जुड़ाव महसूस हुआ है. साथ मिलकर काम करने के नतीजे के तौर पर, Dance Tonite का जन्म हुआ. यह LCD Soundsystem और उनके प्रशंसकों के साथ, वीआर में डांस करने का ऐसा अनुभव है जो लगातार बदलता रहता है. हमने ऐसा कैसे किया, यह जानने के लिए यहां जाएं.

बुनियादी जानकारी

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

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

1. 'खुद करके देखें' कैटगरी वाला मोशन कैप्चर

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

Dance Tonite पर खुद को रिकॉर्ड करते हुए कोई व्यक्ति. उनके पीछे की स्क्रीन पर, वह दिखता है जो उन्हें हेडसेट में दिख रहा है

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

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

2. कम से कम चीज़ें और कपड़े

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

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

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

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

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

3. हलचल के लिए लूप पैडल

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

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

4. एक-दूसरे से जुड़े कमरे

एक-दूसरे से जुड़े कमरे

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

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

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

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

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

हमारे पूरे प्रोजेक्ट में सिर्फ़ कुछ 3D ऑब्जेक्ट का इस्तेमाल किया गया है. इसलिए, इंस्टेंस्ड बफ़र ज्यामिति का इस्तेमाल करके, हमें परफ़ॉर्मेंस में काफ़ी बढ़ोतरी मिली. आम तौर पर, इसकी मदद से अपने ऑब्जेक्ट को एक बार जीपीयू में अपलोड किया जा सकता है. साथ ही, एक ही ड्रॉ कॉल में उस ऑब्जेक्ट के जितने चाहे उतने “इंस्टेंस” ड्रॉ किए जा सकते हैं. Dance Tonite में, हमारे पास सिर्फ़ तीन अलग-अलग ऑब्जेक्ट (एक शंकु, एक सिलेंडर, और एक छेद वाला कमरा) हैं. हालांकि, उन ऑब्जेक्ट की संभावित रूप से सैकड़ों कॉपी हो सकती हैं. इंस्टेंस बफ़र ज्यामिति, ThreeJS का एक हिस्सा है. हालांकि, हमने ड्यूसन बोस्नाक के प्रयोग के तौर पर उपलब्ध और अभी तक पूरी तरह से तैयार नहीं किए गए फ़ॉर्क का इस्तेमाल किया है. इसमें 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. मोशन और प्रोग्रेसिव प्लेबैक को सीरियल करना

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

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

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

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

हमें एक ऐसा स्ट्रीमिंग डेटा फ़ॉर्मैट मिला है जिसका इस्तेमाल करना आसान है. इसे NDJSON या न्यूलाइन डीलिमिटेड 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 परफ़ॉर्मेंस दिखाना चाहते थे. इसलिए, हमें अपने डेटा रेट को पहले से भी कम करना पड़ा. डेटा आर्ट टीम ने अपने वर्चुअल आर्ट सेशन प्रोजेक्ट में भी इसी समस्या को हल किया है. इसमें, कलाकारों को Tilt Brush का इस्तेमाल करके वीआर में पेंटिंग करते हुए रिकॉर्ड किया जाता है और फिर उसे चलाया जाता है. इस समस्या को हल करने के लिए, उन्होंने कम फ़्रेम रेट वाले उपयोगकर्ता डेटा के इंटरमीडिएट वर्शन बनाए और फ़्रेम के बीच में इंटरपोलेट करके, उन्हें चलाते हुए इस समस्या को हल किया. हमें यह जानकर हैरानी हुई कि 15 एफ़पीएस पर चलने वाली इंटरपोलेशन की गई रिकॉर्डिंग और ओरिजनल 90 एफ़पीएस रिकॉर्डिंग के बीच का फ़र्क़ मुश्किल से ही पता चलता है.

खुद देखने के लिए, ?dataRate= क्वेरी स्ट्रिंग का इस्तेमाल करके, Dance Tonite को डेटा को अलग-अलग दरों पर चलाने के लिए मजबूर किया जा सकता है. इसका इस्तेमाल करके, 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. Shadows: fake it 'till you make it

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

परछाई

ऐसा हो सकता है कि 3D में शैडो बनाना मुश्किल हो. खास तौर पर, मोबाइल फ़ोन जैसे सीमित डिवाइसों के लिए. शुरुआत में, हमें उन्हें बाहर रखने का मुश्किल फ़ैसला लेना पड़ा. हालांकि, Three.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, सीमित कलर पैलेट का इस्तेमाल करके छोटी फ़ाइलें बना सकता है. हमें पता चला कि ज़्यादातर समय, हर पिक्सल के लिए सबसे मिलता-जुलता रंग ढूंढने में बीत रहा था. हमने इस प्रोसेस को 10 गुना तक ऑप्टिमाइज़ किया है. इसके लिए, हमने एक छोटा शॉर्टकट इस्तेमाल किया है: अगर पिक्सल का रंग पिछले पिक्सल के रंग जैसा है, तो पैलेट में मौजूद उसी रंग का इस्तेमाल करें.

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

6. एक ही जगह पर मौजूद सभी सुविधाएं: Google Cloud और Firebase

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

सॉलिड ग्राउंड

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

हमने Firebase रीयल टाइम डेटाबेस और Cloud Function एंडपॉइंट का इस्तेमाल करके, मॉडरेशन/क्यूरेशन टूल बनाया है. इसकी मदद से, हम सबमिट की गई हर नई प्लेलिस्ट को वर्चुअल रिएलिटी (वीआर) में देख सकते हैं. साथ ही, किसी भी डिवाइस से नई प्लेलिस्ट पब्लिश कर सकते हैं.

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 के इन शुरुआती दिनों में, हमें यह देखने का बेसब्री से इंतज़ार रहेगा कि यह नया माध्यम क्या नया और क्रिएटिव काम करेगा. डांस चालू करें.