केस स्टडी - ऑज़ तक पहुंचने का रास्ता ढूंढें

शुरुआती जानकारी

“Find Your Way to Oz” Google Chrome का एक नया एक्सपेरिमेंट है. इसे Disney की ओर से वेब पर लाया गया है. इसमें आपको कैंसस सर्कस के एक इंटरैक्टिव सफ़र पर जाना होगा. इस सर्कस में आप एक बड़े तूफ़ान से तबाह हो जाएंगे, जब आप ऑज़ की ज़मीन पर पहुंच जाएंगे.

हमारा लक्ष्य था, सिनेमा की समृद्धि को ब्राउज़र की तकनीकी क्षमताओं के साथ मिलाना, ताकि उपयोगकर्ताओं को एक ऐसा मज़ेदार और इमर्सिव अनुभव मिल सके जिसके साथ वे मज़बूत रिश्ता बना सकें.

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

कई लोगों ने इस सुविधा का इस्तेमाल करने के लिए काफ़ी मेहनत की है: इतने ज़्यादा लोगों को इस सूची में शामिल करने की ज़रूरत नहीं है. पूरी कहानी देखने के लिए, मेन्यू सेक्शन में क्रेडिट पेज पर जाने के लिए कृपया साइट पर जाएं.

एक नज़र

डेस्कटॉप पर Oz के लिए अपना रास्ता खोजें, एक शानदार दुनिया है. हम पारंपरिक फ़िल्ममेकिंग वाले इफ़ेक्ट के 3D और कई लेयर वाले वीडियो का इस्तेमाल करते हैं. इनकी मदद से, सीन करीब-करीब असली जैसा दिखता है. सबसे प्रमुख टेक्नोलॉजी हैं तीन.js वाला WebGL, पसंद के मुताबिक बनाए गए शेडर, और CSS3 की सुविधाओं का इस्तेमाल करने वाले DOM ऐनिमेशन एलिमेंट. इसके अलावा, getUserMedia API (WebRTC) का इस्तेमाल, इंटरैक्टिव अनुभवों के लिए किया जाता है, ताकि उपयोगकर्ता 3D साउंड के लिए वेबकैम और WebAudio से अपनी इमेज सीधे जोड़ सकें.

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

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

इस राज़ को शेयर करने से पहले, हम आपको चेतावनी देना चाहते हैं कि यह हादसा हो सकता है. यह ठीक वैसा ही है जैसे कार इंजन के अंदर थोड़ी-सी तलाश करने पर. पक्का करें कि आपके पास कोई ज़रूरी कॉन्टेंट न हो. साथ ही, साइट के मुख्य यूआरएल पर जाएं और पते में ?debug=on को जोड़ें. साइट के लोड होने तक इंतज़ार करें और इसके अंदर जाने (दबाएं?) कुंजी Ctrl-I और आपको दाईं ओर एक ड्रॉपडाउन दिखेगा. अगर आपने “कैमरा पाथ से बाहर निकलें” से सही का निशान हटा दिया है, तो A, W, S, D, और माउस बटन का इस्तेमाल करके स्पेस में आसानी से इधर-उधर मूव किया जा सकता है.

कैमरा पाथ.

हम यहां सभी सेटिंग के बारे में नहीं बताएंगे, लेकिन हमारा सुझाव है कि आप एक्सपेरिमेंट करें: बटन, अलग-अलग सीन में अलग-अलग सेटिंग के बारे में बताते हैं. आखिरी स्टॉर्म सीक्वेंस में एक अतिरिक्त बटन होता है: Ctrl-A की मदद से, ऐनिमेशन को टॉगल करके नेविगेट किया जा सकता है. इस सीन में, अगर Esc को (माउस लॉक फ़ंक्शन से बाहर निकलने के लिए) दबाया जाता है और फिर से Ctrl-I को दबाया जाता है, तो आपके पास उन सेटिंग को ऐक्सेस करने का विकल्प होगा जो खास तौर पर तूफ़ान के सीन के लिए हैं. आस-पास देखें और नीचे दिए गए पोस्टकार्ड के शानदार नज़ारों को कैप्चर करें.

तूफ़ान का सीन

ऐसा करने और यह पक्का करने के लिए कि यह हमारी ज़रूरतों के हिसाब से सही हो, हमने dat.gu की एक लाइब्रेरी का इस्तेमाल किया. इस लाइब्रेरी को इस्तेमाल करने का तरीका जानने के लिए, यहां देखें. इससे हमें उन सेटिंग को तुरंत बदलने की सुविधा मिली जो साइट पर आने वाले लोगों को दिखती थीं.

एक जैसी मैट पेंटिंग

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

हमने कई मायनों में अनुभव की संरचना एक जैसी है. हालांकि, कुछ “लेयर” स्टैटिक विज़ुअल से कहीं ज़्यादा हैं. असल में, ज़्यादा जटिल कंप्यूटेशन के हिसाब से वे चीज़ों के दिखने के तरीके पर असर डालती हैं. फिर भी, कम से कम जिस बड़े स्तर पर हम व्यू से बात कर रहे हैं, उनमें से एक को दूसरे के ऊपर जोड़कर देखा जा रहा है. सबसे ऊपर आपको यूज़र इंटरफ़ेस (यूआई) लेयर दिखती है, जिसके नीचे 3D सीन है. यह सीन अलग-अलग कॉम्पोनेंट से बना है.

टॉप इंटरफ़ेस लेयर को DOM और CSS 3 का इस्तेमाल करके बनाया गया था. इसका मतलब था कि इवेंट की चुनी हुई सूची के अनुसार, इंटरैक्शन में दोनों के बीच कम्यूनिकेशन के साथ-साथ 3D अनुभव के अलावा, कई तरीकों से इंटरैक्शन में बदलाव किया जा सकता था. यह कम्यूनिकेशन, backबोन राऊटर + onHashChange HTML5 इवेंट का इस्तेमाल करता है, जो यह कंट्रोल करता है कि किस हिस्से को अंदर या बाहर ऐनिमेट करना चाहिए. (प्रोजेक्ट सोर्स: /develop/कॉफ़ी/router/Router.कॉफ़ी).

ट्यूटोरियल: Sprite Sheets और Retina की सहायता

इंटरफ़ेस को ऑप्टिमाइज़ करने की एक मज़ेदार तकनीक यह थी कि सर्वर के अनुरोधों को कम करने के लिए, कई इंटरफ़ेस ओवरले इमेज को एक ही PNG फ़ॉर्मैट में जोड़ा जाए. इस प्रोजेक्ट में, इंटरफ़ेस को 70 से ज़्यादा इमेज (3D टेक्सचर की गिनती नहीं करते हुए) से बनाया गया था, ताकि वेबसाइट की इंतज़ार के समय को कम करने के लिए, पहले से लोड की गई सभी इमेज का इस्तेमाल किया जा सके. लाइव स्प्राइट शीट को यहां देखा जा सकता है:

सामान्य डिसप्ले - http://findyourwaytooz.com/img/home/interface_1x.png रेटिना डिसप्ले - http://findyourwaytooz.com/img/home/interface_2x.png

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

स्प्राइटशीट बनाना

Spritesheet बनाने के लिए हमने TexturePacker का इस्तेमाल किया. यह आपकी ज़रूरत के हिसाब से किसी भी फ़ॉर्मैट में नतीजे देता है. इस मामले में, हमने EaselJS के तौर पर एक्सपोर्ट किया है. यह बहुत ही साफ़-सुथरा है और इसका इस्तेमाल ऐनिमेटेड स्प्राइट बनाने के लिए भी किया जा सकता है.

जनरेट की गई स्प्राइट शीट का इस्तेमाल करना

Sprite शीट बनाने के बाद, आपको JSON फ़ाइल इस तरह दिखेगी:

{
   "images": ["interface_2x.png"],
   "frames": [
       [2, 1837, 88, 130],
       [2, 2, 1472, 112],
       [1008, 774, 70, 68],
       [562, 1960, 86, 86],
       [473, 1960, 86, 86]
   ],

   "animations": {
       "allow_web":[0],
       "bottomheader":[1],
       "button_close":[2],
       "button_facebook":[3],
       "button_google":[4]
   },
}

जगह:

  • इमेज स्प्राइट शीट के यूआरएल से जुड़ी है
  • फ़्रेम हर यूज़र इंटरफ़ेस (यूआई) एलिमेंट [x, y, चौड़ाई, ऊंचाई] के निर्देशांक होते हैं
  • ऐनिमेशन हर ऐसेट का नाम हैं

ध्यान दें कि हमने स्प्राइट शीट बनाने के लिए ज़्यादा सघनता वाली इमेज का इस्तेमाल किया है. इसके बाद, हमने सामान्य वर्शन बना दिया है. इसके लिए हमने स्प्राइट शीट को आधा साइज़ दिया है.

सारा खर्च एक साथ जोड़ना

अब जब हम पूरी तरह से तैयार हैं, तो हमें इसका इस्तेमाल करने के लिए बस एक JavaScript स्निपेट की ज़रूरत होती है.

var SSAsset = function (asset, div) {
  var css, x, y, w, h;

  // Divide the coordinates by 2 as retina devices have 2x density
  x = Math.round(asset.x / 2);
  y = Math.round(asset.y / 2);
  w = Math.round(asset.width / 2);
  h = Math.round(asset.height / 2);

  // Create an Object to store CSS attributes
  css = {
    width                : w,
    height               : h,
    'background-image'   : "url(" + asset.image_1x_url + ")",
    'background-size'    : "" + asset.fullSize[0] + "px " + asset.fullSize[1] + "px",
    'background-position': "-" + x + "px -" + y + "px"
  };

  // If retina devices

  if (window.devicePixelRatio === 2) {

    /*
    set -webkit-image-set
    for 1x and 2x
    All the calculations of X, Y, WIDTH and HEIGHT is taken care by the browser
    */

    css['background-image'] = "-webkit-image-set(url(" + asset.image_1x_url + ") 1x,";
    css['background-image'] += "url(" + asset.image_2x_url + ") 2x)";

  }

  // Set the CSS to the DIV
  div.css(css);
};

इसका इस्तेमाल इस तरह किया जा सकता है:

logo = new SSAsset(
{
  fullSize     : [1024, 1024],               // image 1x dimensions Array [x,y]
  x            : 1790,                       // asset x coordinate on SpriteSheet         
  y            : 603,                        // asset y coordinate on SpriteSheet
  width        : 122,                        // asset width
  height       : 150,                        // asset height
  image_1x_url : 'img/spritesheet_1x.png',   // background image 1x URL
  image_2x_url : 'img/spritesheet_2x.png'    // background image 2x URL
},$('#logo'));

वैरिएबल पिक्सल डेंसिटी के बारे में ज़्यादा जानने के लिए, Bris Smus का यह लेख पढ़ें.

3D कॉन्टेंट पाइपलाइन

WebGL लेयर पर एनवायरमेंट अनुभव सेट अप किया गया हो. जब कोई 3D सीन बनाया जाता है, तो सबसे मुश्किल सवाल यह होता है कि यह कैसे पक्का किया जा सकता है कि आप ऐसा कॉन्टेंट बना पाएं जिससे मॉडलिंग, ऐनिमेशन, और इफ़ेक्ट की मदद से ज़्यादा से ज़्यादा कमाई की जा सके. कॉन्टेंट तैयार करने की इस समस्या की मुख्य वजह है: 3D सीन के लिए कॉन्टेंट बनाने के लिए एक प्रोसेस तय की गई है.

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

हम कुछ समय से इस तरह की समस्या पर काम कर रहे थे, क्योंकि पिछली बार जब भी हमने 3D साइट बनाई थी, तब हमने उन टूल में सीमाएं पाई थीं जिनका हम इस्तेमाल कर सकते थे. इसलिए हमने 3D लाइब्रेरियन नाम के इस टूल का निर्माण किया: यह आंतरिक शोध का एक हिस्सा है. और हां, यह नौकरी में आवेदन करने के लिए तैयार था.

इस टूल का कुछ इतिहास था: मूल रूप से यह Flash के लिए था और इसकी मदद से आप एक बड़ी फ़ाइल को कंप्रेस की गई एक बड़ी फ़ाइल के रूप में ला पाएंगे. यह सीन अनपैक करने के रनटाइम के लिए ऑप्टिमाइज़ किया गया था. यह इसलिए सबसे बेहतर था, क्योंकि इसमें सीन को असरदार तरीके से उसी डेटा स्ट्रक्चर में पैक किया गया था जिसमें रेंडर और ऐनिमेशन के दौरान बदलाव किया गया था. लोड होने पर फ़ाइल को बहुत कम पार्स करने की ज़रूरत होती है. Flash में अनपैक करना बहुत तेज़ था क्योंकि फ़ाइल AMF प्रारूप में थी, जिससे Flash स्थानीय रूप से अनपैक कर सकता था. WebGL में इसी फ़ॉर्मैट का इस्तेमाल करने के लिए, सीपीयू पर थोड़ा और काम करना ज़रूरी है. असल में हमें कोड की डेटा-अनपैक JavaScript लेयर को फिर से बनाना था, जो उन फ़ाइलों को पूरी तरह से कंप्रेस कर देगा और WebGL के काम करने के लिए ज़रूरी डेटा स्ट्रक्चर को फिर से बना देगा. पूरे 3D सीन को अनपैक करना, सीपीयू की तरह बहुत ज़्यादा काम का होता है: Find Your Way To Oz के पहले सीन को अनपैक करने में, मिड से हाई एंड मशीन पर करीब 2 सेकंड लगते हैं. इसलिए, “सीन सेटअप” के समय (सीन लॉन्च होने से पहले), वेब वर्कर टेक्नोलॉजी का इस्तेमाल करके ऐसा किया जाता है, ताकि लोगों को अच्छा अनुभव न मिले.

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

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

ट्यूटोरियल: हवा में चलना

“Find Your Way To Oz” को बार-बार दिखाने वाली थीम हवा थी. कहानी का सिलसिला इस तरह तैयार किया गया है कि वह हवा का बेहतरीन बहाव तैयार कर सके.

कार्निवल का पहला सीन कुछ हद तक शांत है. इस तरह के दृश्यों से गुज़रते हुए, व्यक्ति को तेज़ी से तेज़ हवा का सामना करना पड़ता है. इस वजह से, आखिरी सीन में तूफ़ान आता है.

इसलिए, हवा का बेहतरीन इफ़ेक्ट इस्तेमाल करना ज़रूरी था.

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

मुलायम कपड़ा.

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

WebGL / JavaScript में, हमारे पास (अभी तक) एक पूरी तरह से उड़ा हुआ भौतिकी सिम्युलेशन चलाने की सुविधा नहीं है. इसलिए, ऑज़ में हमें हवा के असर को बनाने का एक तरीका खोजना था, जो असल में उसकी नकल नहीं किया जा सकता था.

हमने हर ऑब्जेक्ट के लिए "पवन सेंसिटिविटी" की जानकारी को 3D मॉडल में एम्बेड किया है. 3D मॉडल के हर वर्टेक्स में ”विंड एट्रिब्यूट” होता था, जिससे यह पता चलता है कि उस वर्टेक्स को हवा से कितना प्रभावित होना चाहिए था. इसलिए, 3D ऑब्जेक्ट पर आधारित हवा के लिए इसकी संवेदनशीलता. इसके बाद हमें हवा को बनाने की ज़रूरत पड़ी.

हमने पर्लिन नॉइज़ वाली इमेज जनरेट करके ऐसा किया है. यह इमेज "हवा का क्षेत्र" को कवर करने के लिए बनाई गई है. इसलिए, इसके बारे में सोचने का सबसे अच्छा तरीका यह है कि 3D सीन के एक आयताकार क्षेत्र में बादल जैसे शोर की तस्वीर दिखाई जाए. इस इमेज का हर पिक्सल, ग्रे लेवल की वैल्यू से पता चलता है कि "इसके आस-पास" वाले 3D एरिया में, किसी खास पल में हवा कितनी तेज़ होगी.

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

अ सिंपल 3D विंड ट्यूटोरियल

आइए, अब Three.js में एक आसान से 3D सीन में विंड का इफ़ेक्ट तैयार करते हैं.

हम एक आसान ”घास के मैदान” में हवा बनाने वाले हैं.

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

घास से भरा इलाका
घास से भरा इलाका

CoffeeScript का इस्तेमाल करके, Three.js में इस आसान सीन को बनाने का तरीका यहां बताया गया है.

सबसे पहले हम तीन.js सेटअप करेंगे और उसे कैमरा, माउस कंट्रोलर, और कुछ लाइट के साथ जोड़ें:

constructor: ->

   @clock =  new THREE.Clock()

   @container = document.createElement( 'div' );
   document.body.appendChild( @container );

   @renderer = new THREE.WebGLRenderer();
   @renderer.setSize( window.innerWidth, window.innerHeight );
   @renderer.setClearColorHex( 0x808080, 1 )
   @container.appendChild(@renderer.domElement);

   @camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
   @camera.position.x = 5;
   @camera.position.y = 10;
   @camera.position.z = 40;

   @controls = new THREE.OrbitControls( @camera, @renderer.domElement );
   @controls.enabled = true

   @scene = new THREE.Scene();
   @scene.add( new THREE.AmbientLight 0xFFFFFF )

   directional = new THREE.DirectionalLight 0xFFFFFF
   directional.position.set( 10,10,10)
   @scene.add( directional )

   # Demo data
   @grassTex = THREE.ImageUtils.loadTexture("textures/grass.png");
   @initGrass()
   @initTerrain()

   # Stats
   @stats = new Stats();
   @stats.domElement.style.position = 'absolute';
   @stats.domElement.style.top = '0px';
   @container.appendChild( @stats.domElement );
   window.addEventListener( 'resize', @onWindowResize, false );
   @animate()

initGrass और init चैट फ़ंक्शन कॉल की मदद से, सीन में घास और इलाके की जानकारी अपने-आप भर जाती है:

initGrass:->
   mat = new THREE.MeshPhongMaterial( { map: @grassTex } )
   NUM = 15
   for i in [0..NUM] by 1
       for j in [0..NUM] by 1
           x = ((i/NUM) - 0.5) * 50 + THREE.Math.randFloat(-1,1)
           y = ((j/NUM) - 0.5) * 50 + THREE.Math.randFloat(-1,1)
           @scene.add( @instanceGrass( x, 2.5, y, 5.0, mat ) )

instanceGrass:(x,y,z,height,mat)->
   geometry = new THREE.CylinderGeometry( 0.9, 0.0, height, 3, 5 )
   mesh = new THREE.Mesh( geometry, mat )
   mesh.position.set( x, y, z )
   return mesh

यहां हम 15 x 15 बिट घास का ग्रिड बना रहे हैं. हम हर घास की पोज़िशन को किसी भी क्रम में लगाते हैं, ताकि वे सैनिकों की तरह लाइन में न लग जाएं. यह अजीब लगेगा.

यह इलाक़ा सिर्फ़ एक क्षैतिज तल है, जो घास के टुकड़ों (y = 2.5) के नीचे रखा गया है.

initTerrain:->
  @plane = new THREE.Mesh( new THREE.PlaneGeometry(60, 60, 2, 2), new THREE.MeshPhongMaterial({ map: @grassTex }))
  @plane.rotation.x = -Math.PI/2
  @scene.add( @plane )

इसलिए, अब तक हमने सिर्फ़ तीन.js सीन बनाया है. इसके अलावा, घास के कुछ टुकड़े जोड़े हैं. ये घास के कुछ टुकड़े जोड़े गए हैं, जो प्रक्रिया के हिसाब से उलटे कोन से बनाए गए हैं और एक सामान्य इलाका है.

अभी तक कुछ भी नहीं है.

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

हम इस जानकारी को घास 3D मॉडल के हर शीर्ष के लिए एक कस्टम विशेषता के रूप में एम्बेड करने वाले हैं. अब हम इस नियम के हिसाब से नियम लागू करेंगे कि: घास के मॉडल के निचले सिरे (कोन की सिरे) पर ज़ीरो सेंसिटिविटी है, क्योंकि यह ज़मीन से लगा है. घास के मॉडल (कोन का आधार) के ऊपरी हिस्से को हवा से बहुत ज़्यादा परेशानी होती है, क्योंकि यह हिस्सा ज़मीन से दूर होता है.

यहां बताया गया है कि instanceGrass फ़ंक्शन को री-कोड कैसे किया जाता है, ताकि घास के 3D मॉडल के लिए कस्टम एट्रिब्यूट के तौर पर विंड सेंसिटिविटी जोड़ी जा सके.

instanceGrass:(x,y,z,height)->

  geometry = new THREE.CylinderGeometry( 0.9, 0.0, height, 3, 5 )

  for i in [0..geometry.vertices.length-1] by 1
      v = geometry.vertices[i]
      r = (v.y / height) + 0.5
      @windMaterial.attributes.windFactor.value[i] = r * r * r

  # Create mesh
  mesh = new THREE.Mesh( geometry, @windMaterial )
  mesh.position.set( x, y, z )
  return mesh

अब हम पहले इस्तेमाल किए गए MashPhongMaterial की जगह पसंद के मुताबिक बनाए गए मटीरियल, windMaterial का इस्तेमाल करते हैं. WindMaterial, उन WindMeshShader को रैप करता है जो हम एक मिनट में देखने वाले हैं.

इसलिए, instanceGrass में मौजूद कोड, घास के मॉडल के सभी वर्टेक्स से गुज़रता है और हर वर्टेक्स के लिए, यह एक कस्टम वर्टेक्स एट्रिब्यूट जोड़ता है, जिसे windFactor कहा जाता है. घास के मॉडल (जहां इसे इलाके को छूना चाहिए) के निचले सिरे के लिए, इस विंडफ़ैक्टर को 0 पर सेट किया गया है. साथ ही, घास के मॉडल के ऊपरी सिरे के लिए इसकी वैल्यू 1 है.

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

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

इसलिए, पर्लिन की यह शोर की बनावट हमारे इलाक़े के एक्सटेंशन को जगह के हिसाब से कवर करेगी. साथ ही, टेक्सचर का हर पिक्सल उस इलाके की हवा की तीव्रता तय करेगा जहां वह पिक्सल गिरता है. इलाक़ा रेक्टैंगल, “हवा का क्षेत्र” होगा.

पर्लिन का शोर एक शेडर के ज़रिए जनरेट होता है, जिसे NoiseShader कहा जाता है. यह शेडर, 3d सिंप्लेक्स नॉइज़ एल्गोरिदम का इस्तेमाल करता है: https://github.com/ashima/webgl-noise . इसका WebGL वर्शन, MrDoob के थ्री.js नमूनों में से एक से हूबहू लिए गया था. यह जानकारी http://mrdoob.github.com/three.js/examples/webgl_terrain_dynamic.html से ली गई थी.

NoiseShader: एक समय, एक स्केल, और पैरामीटर के ऑफ़सेट सेट को यूनिफ़ॉर्म की तरह इस्तेमाल करता है. यह पर्लिन नॉइज़ का अच्छे से 2D डिस्ट्रिब्यूशन करता है.

class NoiseShader

  uniforms:     
    "fTime"  : { type: "f", value: 1 }
    "vScale"  : { type: "v2", value: new THREE.Vector2(1,1) }
    "vOffset"  : { type: "v2", value: new THREE.Vector2(1,1) }

...

हम इस शेडर का इस्तेमाल अपने पर्लिन नॉइज़ को एक टेक्सचर में रेंडर करने के लिए करेंगे. ऐसा initNoiseShader फ़ंक्शन में किया जाता है.

initNoiseShader:->
  @noiseMap  = new THREE.WebGLRenderTarget( 256, 256, { minFilter: THREE.LinearMipmapLinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat } );
  @noiseShader = new NoiseShader()
  @noiseShader.uniforms.vScale.value.set(0.3,0.3)
  @noiseScene = new THREE.Scene()
  @noiseCameraOrtho = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2,  window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 );
  @noiseCameraOrtho.position.z = 100
  @noiseScene.add( @noiseCameraOrtho )

  @noiseMaterial = new THREE.ShaderMaterial
      fragmentShader: @noiseShader.fragmentShader
      vertexShader: @noiseShader.vertexShader
      uniforms: @noiseShader.uniforms
      lights:false

  @noiseQuadTarget = new THREE.Mesh( new THREE.PlaneGeometry(window.innerWidth,window.innerHeight,100,100), @noiseMaterial )
  @noiseQuadTarget.position.z = -500
  @noiseScene.add( @noiseQuadTarget )

ऊपर दिया गया कोड, noiseMap को तीन.js रेंडर टारगेट के तौर पर सेटअप करता है. साथ ही, इसे NoiseShader से लैस करता है और फिर इसे ऑर्थोग्राफ़िक कैमरे के साथ रेंडर करता है, ताकि ऐंगल से जुड़ी गड़बड़ी से बचा जा सके.

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

नॉइज़मैप एक बनावट के रूप में इस्तेमाल करके, फिर से बनाया गया initTerrain फ़ंक्शन यहां दिया गया है:

initTerrain:->
  @plane = new THREE.Mesh( new THREE.PlaneGeometry(60, 60, 2, 2), new THREE.MeshPhongMaterial( { map: @noiseMap, lights: false } ) )
  @plane.rotation.x = -Math.PI/2
  @scene.add( @plane )

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

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

हम यहां पूरे शेडर कोड को कॉपी नहीं कर रहे हैं (सोर्स कोड फ़ाइल में इसे बेझिझक देखें), क्योंकि इसमें से ज़्यादातर कोड, MeshPhongMaterial शेडर की नकल होंगे. चलिए, Vertex Shader में बदले गए, हवा से जुड़े, पुर्ज़ों पर एक नज़र डालते हैं.

vec4 wpos = modelMatrix * vec4( position, 1.0 );
vec4 wpos = modelMatrix * vec4( position, 1.0 );

wpos.z = -wpos.z;
vec2 totPos = wpos.xz - windMin;
vec2 windUV = totPos / windSize;
vWindForce = texture2D(tWindForce,windUV).x;

float windMod = ((1.0 - vWindForce)* windFactor ) * windScale;
vec4 pos = vec4(position , 1.0);
pos.x += windMod * windDirection.x;
pos.y += windMod * windDirection.y;
pos.z += windMod * windDirection.z;

mvPosition = modelViewMatrix *  pos;

इसलिए, यह शेडर सबसे पहले वर्टेक्स की 2D, xz (हॉरिज़ॉन्टल) स्थिति के आधार पर, windUV टेक्सचर लुकअप कोऑर्डिनेट को कैलकुलेट करता है. इस यूवी निर्देशांक का इस्तेमाल, पर्लिन की नॉइज़ विंड टेक्सचर से, हवा की ताकत, vWindForce का पता लगाने के लिए किया जाता है.

यह vWindForce वैल्यू, वर्टेक्स के लिए खास windFactor के साथ कंपोज़िट किया जाता है, इस कस्टम एट्रिब्यूट के बारे में ऊपर बताया गया है, ताकि यह पता लगाया जा सके कि वर्टेक्स को कितने डिफ़ॉर्मेशन की ज़रूरत है. हमारे पास हवा की पूरी ताकत को कंट्रोल करने के लिए, एक ग्लोबल windScale पैरामीटर है. साथ ही, एक windDirection, वेक्टर भी है, जो बताता है कि हवा में बदलाव किस दिशा में होना चाहिए.

इसलिए, हवा की वजह से घास के इन टुकड़े में खराबी आ जाती है. हालांकि, हमने अभी तक कोई कार्रवाई नहीं की. जैसा कि अभी है, यह बदलाव स्टैटिक है और यह हवा वाले इलाके का असर नहीं बताता.

जैसा कि हमने पहले बताया था, हमें समय के साथ नॉइज़ टेक्सचर को हवा के उस हिस्से पर स्लाइड करना होगा, ताकि हमारा ग्लास लहरा सके.

ऐसा समय के साथ, vOffset यूनिफ़ॉर्म को शिफ़्ट करके किया जाता है. इसे NoiseShader को पास किया जाता है. यह एक vec2 पैरामीटर है, जिससे हम एक खास दिशा (हमारी हवा की दिशा) के साथ नॉइज़ ऑफ़सेट तय कर पाएंगे.

हम यह काम रेंडर फ़ंक्शन में करते हैं, जिसे हर फ़्रेम पर कॉल किया जाता है:

render: =>
  delta = @clock.getDelta()

  if @windDirection
      @noiseShader.uniforms[ "fTime" ].value += delta * @noiseSpeed
      @noiseShader.uniforms[ "vOffset" ].value.x -= (delta * @noiseOffsetSpeed) * @windDirection.x
      @noiseShader.uniforms[ "vOffset" ].value.y += (delta * @noiseOffsetSpeed) * @windDirection.z
...

बस इतना ही! हमने अभी-अभी एक सीन बनाया है, जिसमें "घास वाली घास" का इस्तेमाल किया गया है.

घोल में गंदगी डाली जा रही है

चलिए, अब अपने सीन को थोड़ा मज़ेदार बनाते हैं. आइए, इस सीन को और मज़ेदार बनाने के लिए, थोड़ी हवा में धूल चटाएं.

धूल जोड़ना
गंदगी डालना

वैसे तो धूल पर हवा का असर होना चाहिए, इसलिए हमारी हवा के आस-पास धूल उड़ना सबसे सही रहता है.

initDust फ़ंक्शन में, पार्टिकल सिस्टम के तौर पर डस्ट को सेटअप किया गया है.

initDust:->
  for i in [0...5] by 1
      shader = new WindParticleShader()
      params = {}
      params.fragmentShader = shader.fragmentShader
      params.vertexShader   = shader.vertexShader
      params.uniforms       = shader.uniforms
      params.attributes     = { speed: { type: 'f', value: [] } }

      mat  = new THREE.ShaderMaterial(params)
      mat.map = shader.uniforms["map"].value = THREE.ImageUtils.loadCompressedTexture("textures/dust#{i}.dds")
      mat.size = shader.uniforms["size"].value = Math.random()
      mat.scale = shader.uniforms["scale"].value = 300.0
      mat.transparent = true
      mat.sizeAttenuation = true
      mat.blending = THREE.AdditiveBlending
      shader.uniforms["tWindForce"].value      = @noiseMap
      shader.uniforms[ "windMin" ].value       = new THREE.Vector2(-30,-30 )
      shader.uniforms[ "windSize" ].value      = new THREE.Vector2( 60, 60 )
      shader.uniforms[ "windDirection" ].value = @windDirection            

      geom = new THREE.Geometry()
      geom.vertices = []
      num = 130
      for k in [0...num] by 1

          setting = {}

          vert = new THREE.Vector3
          vert.x = setting.startX = THREE.Math.randFloat(@dustSystemMinX,@dustSystemMaxX)
          vert.y = setting.startY = THREE.Math.randFloat(@dustSystemMinY,@dustSystemMaxY)
          vert.z = setting.startZ = THREE.Math.randFloat(@dustSystemMinZ,@dustSystemMaxZ)

          setting.speed =  params.attributes.speed.value[k] = 1 + Math.random() * 10
          
          setting.sinX = Math.random()
          setting.sinXR = if Math.random() < 0.5 then 1 else -1
          setting.sinY = Math.random()
          setting.sinYR = if Math.random() < 0.5 then 1 else -1
          setting.sinZ = Math.random()
          setting.sinZR = if Math.random() < 0.5 then 1 else -1

          setting.rangeX = Math.random() * 5
          setting.rangeY = Math.random() * 5
          setting.rangeZ = Math.random() * 5

          setting.vert = vert
          geom.vertices.push vert
          @dustSettings.push setting

      particlesystem = new THREE.ParticleSystem( geom , mat )
      @dustSystems.push particlesystem
      @scene.add particlesystem

यहां धूल के 130 कण बनते हैं. ध्यान दें कि उनमें से हर एक में एक खास WindParticleShader भी है.

अब, हर फ़्रेम में हम कॉफ़ीस्क्रिप्ट का इस्तेमाल करते हुए, हवा से स्वतंत्र रूप से कणों के आस-पास जाएंगे. यह कोड है.

moveDust:(delta)->

  for setting in @dustSettings

    vert = setting.vert
    setting.sinX = setting.sinX + (( 0.002 * setting.speed) * setting.sinXR)
    setting.sinY = setting.sinY + (( 0.002 * setting.speed) * setting.sinYR)
    setting.sinZ = setting.sinZ + (( 0.002 * setting.speed) * setting.sinZR) 

    vert.x = setting.startX + ( Math.sin(setting.sinX) * setting.rangeX )
    vert.y = setting.startY + ( Math.sin(setting.sinY) * setting.rangeY )
    vert.z = setting.startZ + ( Math.sin(setting.sinZ) * setting.rangeZ )

इसके अलावा, हम कण की हर स्थिति को हवा के हिसाब से ऑफ़सेट करेंगे. यह विंडपार्टिकलशेडर में किया जाता है. खास तौर पर, वर्टेक्स शेडर में.

इस शेडर का कोड, Three.js ParticleMaterial का बदला गया वर्शन है और इसका कोर ऐसा दिखता है:

vec4 mvPosition;
vec4 wpos = modelMatrix * vec4( position, 1.0 );
wpos.z = -wpos.z;
vec2 totPos = wpos.xz - windMin;
vec2 windUV = totPos / windSize;
float vWindForce = texture2D(tWindForce,windUV).x;
float windMod = (1.0 - vWindForce) * windScale;
vec4 pos = vec4(position , 1.0);
pos.x += windMod * windDirection.x;
pos.y += windMod * windDirection.y;
pos.z += windMod * windDirection.z;

mvPosition = modelViewMatrix *  pos;

fSpeed = speed;
float fSize = size * (1.0 + sin(time * speed));

#ifdef USE_SIZEATTENUATION
    gl_PointSize = fSize * ( scale / length( mvPosition.xyz ) );
#else,
    gl_PointSize = fSize;
#endif

gl_Position = projectionMatrix * mvPosition;

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

आंधी में राइडर

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

बलून राइड का सीन

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

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

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

ट्यूटोरियल: तूफ़ान शेडर

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

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

कस्टम वॉल्यूमेट्रिक शेडर का इस्तेमाल करके, माउस न्यूरॉन के अंदर
कस्टम वॉल्यूमट्रिक शेडर का इस्तेमाल करते हुए, माउस न्यूरॉन के अंदर

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

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

खास जानकारी में iq से एल्गोरिदम का एक अच्छा रेफ़रंस देखा जा सकता है: दो त्रिभुजों के साथ दुनिया को रेंडर करना - इनिगो क्विलेस. साथ ही, glsl.heroku.com पर शेडर की गैलरी को एक्सप्लोर किया जा रहा है. वहां इस तकनीक के कई उदाहरण मौजूद हैं, जिन्हें आज़माया जा सकता है.

शेडर का दिल, मुख्य फ़ंक्शन से शुरू होता है. इसकी मदद से, कैमरा पूरी तरह बदल जाता है और एक लूप में चला जाता है. यह लूप, बार-बार सतह से दूरी का आकलन करता है. RaytraceFoggy( direct_सदिश, max_iterations, color, color_multiplier ) कॉल में ही किया जाता है, जहां कोर रे मार्चिंग कैलकुलेशन होती है.

for(int i=0;i < number_of_steps;i++) // run the ray marching loop
{
  old_d=d;
  float shape_value=Shape(q); // find out the approximate distance to or density of the tornado cone
  float density=-shape_value;
  d=max(shape_value*step_scaling,0.0);// The max function clamps values smaller than 0 to 0

  float step_dist=d+extra_step; // The point is advanced by larger steps outside the tornado,
  //  allowing us to skip empty space quicker.

  if (density>0.0) {  // When density is positive, we are inside the cloud
    float brightness=exp(-0.6*density);  // Brightness decays exponentially inside the cloud

    // This function combines density layers to create a translucent fog
    FogStep(step_dist*0.2,clamp(density, 0.0,1.0)*vec3(1,1,1), vec3(1)*brightness, colour, multiplier); 
  }
  if(dist>max_dist || multiplier.x < 0.01) { return;  } // if we've gone too far stop, we are done
  dist+=step_dist; // add a new step in distance
  q=org+dist*dir; // trace its direction according to the ray casted
}

इसका विचार यह है कि जैसे-जैसे हम बवंडर के आकार में आगे बढ़ते हैं, हम नियमित रूप से पिक्सेल के अंतिम रंग मान में रंगों के योगदान को शामिल करते हैं, साथ ही किरणों के साथ-साथ ओपैसिटी में भी योगदान देते हैं. इससे बवंडर की बनावट में कई लेयर वाली नर्म क्वालिटी बन जाती है.

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

mat2 Spin(float angle){
  return mat2(cos(angle),-sin(angle),sin(angle),cos(angle)); // a rotation matrix
}

// This takes noise function and makes ridges at the points where that function crosses zero
float ridged(float f){ 
  return 1.0-2.0*abs(f);
}

// the isosurface shape function, the surface is at o(q)=0 
float Shape(vec3 q) 
{
    float t=time;

    if(q.z < 0.0) return length(q);

    vec3 spin_pos=vec3(Spin(t-sqrt(q.z))*q.xy,q.z-t*5.0); // spin the coordinates in time

    float zcurve=pow(q.z,1.5)*0.03; // a density function dependent on z-depth

    // the basic cloud of a cone is perturbed with a distortion that is dependent on its spin 
    float v=length(q.xy)-1.5-zcurve-clamp(zcurve*0.2,0.1,1.0)*snoise(spin_pos*vec3(0.1,0.1,0.1))*5.0; 

    // create ridges on the tornado
    v=v-ridged(snoise(vec3(Spin(t*1.5+0.1*q.z)*q.xy,q.z-t*4.0)*0.3))*1.2; 

    return v;
}

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

समस्या का पहला हिस्सा: इस शेडर को हमारे सीन के लिए ऑप्टिमाइज़ करना. इससे निपटने के लिए हमें एक "सुरक्षित" तरीका अपनाना था, क्योंकि शेडर बहुत भारी होने वाला था. ऐसा करने के लिए, हमने टॉर्नेडो शेडर को सीन के बाकी हिस्से से अलग सैंपल वाले रिज़ॉल्यूशन के हिसाब से कंपोज़ किया है. यह stormTest.कॉफ़ी फ़ाइल से लिया गया है (हां, यह एक टेस्ट था!).

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

...
Line 1383
@tornadoRT = new THREE.WebGLRenderTarget( @SCENE_WIDTH, @SCENE_HEIGHT, paramsN )

... 
Line 1403 
# Change settings based on FPS
if @fpsCount > 0
    if @fpsCur < 20
        @tornadoSamples = Math.min( @tornadoSamples + 1, @MAX_SAMPLES )
    if @fpsCur > 25
        @tornadoSamples = Math.max( @tornadoSamples - 1, @MIN_SAMPLES )
    @tornadoW = @SCENE_WIDTH  / @tornadoSamples // decide tornado resWt
    @tornadoH = @SCENE_HEIGHT / @tornadoSamples // decide tornado resHt

आखिर में, हमने stormTest.कॉफ़ी में @line 1107 का इस्तेमाल करके, आसान sal2x एल्गोरिदम (ब्लॉकी लुक से बचने के लिए) को स्क्रीन पर दिखाने के लिए बवंडर को रेंडर किया. इसका मतलब है कि बदतर स्थिति में हमें ज़्यादा धुंधला बवंडर दिखता है, लेकिन कम से कम यह तरीका उपयोगकर्ता से कंट्रोल किए बिना काम करता है.

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

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

क्रॉस वीडियो बोर्ड के साथ काम करने से जुड़ी समस्याओं के समाधान भी मिलते-जुलते थे: पक्का करें कि स्टैटिक स्थिरांक जैसे कि परिभाषित किए गए डेटा टाइप में डाला गया हो, IE: फ़्लोट के लिए 0.0 और int के लिए 0. लंबे फ़ंक्शन लिखते समय सावधानी बरतें. चीज़ों को कई आसान फ़ंक्शन और अंतरिम वैरिएबल में तोड़ने को प्राथमिकता दी जाती है, क्योंकि ऐसा लग रहा था कि कंपाइलर कुछ मामलों को सही तरीके से हैंडल नहीं कर पा रहे हैं. पक्का करें कि सभी टेक्स्चर्स 2 की घात हों, बहुत बड़ी नहीं हों और किसी भी स्थिति में लूप में टेक्सचर डेटा देखते समय “सावधानी” का इस्तेमाल कर रहे हों.

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

बवंडर

मोबाइल वेब साइट

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

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

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

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

मोबाइल सुझाव और तरकीबें

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

प्रीलोडर

इस तरह के तरीके और टास्क क्लास की मदद से, हम ग्लोबल प्रोग्रेस (MainPreloadTask) या सिर्फ़ ऐसेट (AssetPreloadTask) की प्रोग्रेस या लोडिंग टेंप्लेट (TemplatePreFetchTask) की प्रोग्रेस को आसानी से जान सकते हैं. किसी खास फ़ाइल की प्रोग्रेस का डेटा भी. यह कैसे होता है, यह देखने के लिए /m/javascripts/raw/util/Task.js पर टास्क क्लास और /m/javascripts/preloading/task पर जाकर टास्क को लागू करने के तरीके देखें. उदाहरण के लिए, यह इस बात से लिया गया डेटा है कि हमने /m/javascripts/preloading/task/MainPreloadTask.js क्लास को सेट अप किया है. यह पहले से लोड करने का हमारा सबसे सटीक रैपर है:

Package('preloading.task', [
  Import('util.Task'),
...

  Class('public MainPreloadTask extends Task', {

    _public: {
      
  MainPreloadTask : function() {
        
    var subtasks = [
      new AssetPreloadTask([
        {name: 'cutout/cutout-overlay-1', ext: 'png', type: ImagePreloader.TYPE_BACKGROUND, responsive: true},
        {name: 'journey/scene1', ext: 'jpg', type: ImagePreloader.TYPE_IMG, responsive: false}, ...
...
      ]),

      new TemplatePreFetchTask([
        'page.HomePage',
        'page.CutoutPage',
        'page.JourneyToOzPage1', ...
...
      ])
    ];
    
    this._super(subtasks);

      }
    }
  })
]);

/m/javascripts/preloading/task/subtask/AssetPreloadTask.js क्लास में यह ध्यान दें कि यह MenPreloadTask के साथ कैसे कम्यूनिकेट करता है (शेयर किए गए टास्क के ज़रिए), यह ध्यान देने के लिए ज़रूरी है कि हम प्लैटफ़ॉर्म पर निर्भर एसेट को कैसे लोड करते हैं. दरअसल, हमारे पास चार तरह की इमेज हैं. मोबाइल स्टैंडर्ड (.ext, जहां ऐसेट फ़ाइल एक्सटेंशन है, आम तौर पर .png या .jpg), मोबाइल रेटिना (-2x.ext), टैबलेट स्टैंडर्ड (-tab.ext) और टैबलेट रेटिना (-tab-2x.ext). ManyPreloadTask में पहचान करने और चार ऐसेट अरे हार्डकोड करने के बजाय, हम सिर्फ़ यह बताते हैं कि प्रीलोड करने के लिए ऐसेट का नाम और एक्सटेंशन क्या है. साथ ही, अगर ऐसेट प्लैटफ़ॉर्म पर निर्भर है (रिस्पॉन्सिव = true / false). इसके बाद, AssetPreloadTask हमारे लिए फ़ाइल का नाम जनरेट करेगा:

resolveAssetUrl : function(assetName, extension, responsive) {
  return AssetPreloadTask.ASSETS_ROOT + assetName + (responsive === true ? ((Detection.getInstance().tablet ? '-tab' : '') + (Detection.getInstance().retina ? '-2x' : '')) : '') + '.' +  extension;
}

क्लास चेन में नीचे, एसेट को पहले से लोड करने वाला असल कोड इस तरह दिखता है (/m/javascripts/raw/util/ImagePreloader.js):

loadUrl : function(url, type, completeHandler) {
  if(type === ImagePreloader.TYPE_BACKGROUND) {
    var $bg = $('<div>').hide().css('background-image', 'url(' + url + ')');
    this.$preloadContainer.append($bg);
  } else {
    var $img= $('<img />').attr('src', url).hide();
    this.$preloadContainer.append($img);
  }

  var image = new Image();
  this.cache[this.generateKey(url)] = image;
  image.onload = completeHandler;
  image.src = url;
}

generateKey : function(url) {
  return encodeURIComponent(url);
}

ट्यूटोरियल: HTML5 फ़ोटो बूथ (iOS6/Android)

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

मोबाइल फ़ोटो बूथ
मोबाइल फ़ोटो बूथ

आप यहां एक लाइव डेमो देख सकते हैं (इसे अपने iPhone या Android फ़ोन पर चलाएं):

http://u9html5rocks.appspot.com/demos/mobile_photo_booth

इसे सेट अप करने के लिए, आपको एक मुफ़्त Google App Engine ऐप्लिकेशन इंस्टेंस की ज़रूरत होगी, जहां आप बैकएंड चला सकें. फ़्रंट एंड कोड जटिल नहीं है, लेकिन कुछ गॉकाच हो सकते हैं. आइए, अब इनके बारे में बात करते हैं:

  1. इस तरह की इमेज की अनुमति है हम चाहते हैं कि लोग सिर्फ़ इमेज अपलोड कर पाएं. यह एक फ़ोटो बूथ है, वीडियो बूथ नहीं. आम तौर पर, फ़िल्टर को एचटीएमएल में इस तरह से तय किया जा सकता है: input id="fileInput" class="fileInput" type="file" name="file" accept="image/*" हालांकि, ऐसा लगता है कि यह सिर्फ़ iOS पर काम करता है. इसलिए, फ़ाइल चुनने के बाद, हमें RegExp के लिए एक और जांच जोड़नी होगी:
   this.$fileInput.fileupload({
          
   dataType: 'json',
   autoUpload : true,
   
   add : function(e, data) {
     if(!data.files[0].name.match(/(\.|\/)(gif|jpe?g|png)$/i)) {
      return self.onFileTypeNotSupported();
     }
   }
   });
  1. अपलोड या फ़ाइल चुनने की प्रोसेस को रद्द करना डेवलपमेंट प्रोसेस के दौरान, हमें एक और अंतर नज़र आया कि कैसे अलग-अलग डिवाइस, रद्द की गई फ़ाइल के चुने जाने की सूचना देते हैं. iOS फ़ोन और टैबलेट कोई कार्रवाई नहीं करते, वे बिलकुल भी सूचना नहीं देते. इसलिए, हमें इस मामले में किसी खास कार्रवाई की ज़रूरत नहीं है. हालांकि, कोई फ़ाइल न चुनने पर भी Android फ़ोन add() फ़ंक्शन को ट्रिगर करते हैं. इसके लिए, यह तरीका अपनाएं:
    add : function(e, data) {

    if(data.files.length === 0 || (data.files[0].size === 0 && data.files[0].name === "" && data.files[0].fileName === "")) {
            
    return self.onNoFileSelected();

    } else if(data.files.length > 1) {

    return self.onMultipleFilesSelected();            
    }
    }

बाकी सभी प्लैटफ़ॉर्म पर आसानी से काम करता है. आनंद लें!

नतीजा

Find Your Way To Oz के बड़े आकार और इसमें शामिल कई तरह की टेक्नोलॉजी को देखते हुए, हम उन तरीकों में से सिर्फ़ कुछ पर बात कर पाए जिनका हमने इस्तेमाल किया.

अगर आपको पूरे एन्किलाडा के बारे में और जानना है, तो इस लिंक पर जाएं और 'ओज़ ढूंढने के लिए ढूंढें' के पूरे सोर्स कोड पर नज़र डालें.

क्रेडिट देखें

पूरे क्रेडिट की सूची देखने के लिए यहां क्लिक करें

रेफ़रंस