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

परिचय

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

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

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

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

टेस्टिंग प्रोसेस की एक झलक

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

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

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

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

कैमरा पाथ.

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

तूफ़ान का सीन

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

मैट पेंटिंग की तरह

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

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

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

ट्यूटोरियल: स्प्राइट शीट और Retina डिसप्ले के लिए सहायता

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

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

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

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

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

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

स्प्राइट शीट बनाने के बाद, आपको इस तरह की 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, width, height]
  • ऐनिमेशन, हर ऐसेट के नाम होते हैं

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

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

अब हम पूरी तरह से तैयार हैं. इसका इस्तेमाल करने के लिए, हमें सिर्फ़ एक 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'));

अलग-अलग पिक्सल डेंसिटी के बारे में ज़्यादा जानने के लिए, बोरिस स्मस का यह लेख पढ़ें.

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

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

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

हम कुछ समय से इस तरह की समस्या पर काम कर रहे थे, क्योंकि जब भी हमने पहले 3D साइट बनाई थी, तो हमें इस्तेमाल किए जा सकने वाले टूल में सीमाएं मिली थीं. इसलिए, हमने 3D लाइब्रेरियन नाम का यह टूल बनाया था. यह हमारी अंदरूनी रिसर्च का एक हिस्सा है. यह मॉडल, असल नौकरी के लिए इस्तेमाल करने के लिए तैयार था.

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

यह आसान टूल, ज़्यादातर 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 में यह आसान सीन बनाने का तरीका यहां बताया गया है.

सबसे पहले, हम Three.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 और initTerrain फ़ंक्शन कॉल, सीन में घास और इलाके को क्रमशः भरते हैं:

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 )

अब तक, हमने सिर्फ़ एक Three.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

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

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

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

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

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

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

NoiseShader, यूनिफ़ॉर्म के तौर पर पैरामीटर का टाइम, स्केल, और ऑफ़सेट सेट लेता है. साथ ही, Perlin नॉइज़ का अच्छा 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 को Three.js रेंडर टारगेट के तौर पर सेटअप करता है. इसके बाद, उसे NoiseShader से लैस करता है और फिर ऑर्थोग्राफ़िक कैमरे से रेंडर करता है, ताकि पर्सपेक्टिव में कोई गड़बड़ी न हो.

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

यहां initTerrain फ़ंक्शन को फिर से बनाया गया है. इसमें टेक्सचर के तौर पर noiseMap का इस्तेमाल किया गया है:

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 )

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

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

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

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 होता है.

अब हम हर फ़्रेम में, CoffeeScript का इस्तेमाल करके, कणों को थोड़ा-बहुत इधर-उधर घुमाने जा रहे हैं. ऐसा, हवा के हिसाब से नहीं किया जाएगा. यहां कोड दिया गया है.

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 )

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

इस शेडर का कोड, 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;

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

Riders On The Storm

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

गुब्बारे की सवारी का सीन

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

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

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

ट्यूटोरियल: स्टॉर्म शेडर

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

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

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

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

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

एल्गोरिदम के बारे में ज़्यादा जानने के लिए, iq की खास जानकारी देखें: दो त्रिकोणों की मदद से वर्ल्ड रेंडर करना - Iñigo Quilez. glsl.heroku.com पर शेडर की गैलरी भी देखें. यहां इस तकनीक के कई उदाहरण मिलेंगे, जिनका इस्तेमाल करके प्रयोग किया जा सकता है.

शेडर का मुख्य फ़ंक्शन, कैमरे के ट्रांसफ़ॉर्म को सेट अप करता है और एक लूप में चला जाता है. यह लूप, किसी सतह से दूरी का बार-बार आकलन करता है. RaytraceFoggy( direction_vector, 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.coffee फ़ाइल से लिया गया है (हां, यह एक टेस्ट था!).

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

...
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.coffee में 1107वीं लाइन पर, आसान sal2x एल्गोरिदम का इस्तेमाल करके, स्क्रीन पर टॉर्नेडो को रेंडर करते हैं. ऐसा, ब्लॉकी लुक से बचने के लिए किया जाता है. इसका मतलब है कि सबसे खराब स्थिति में, हमें ज़्यादा धुंधला बवंडर दिखेगा. हालांकि, कम से कम यह उपयोगकर्ता के कंट्रोल में काम करता है.

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

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

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

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

बवंडर

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

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

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

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

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

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

प्रीलोडर की ज़रूरत होती है, इससे बचने की ज़रूरत नहीं है. हम जानते हैं कि कभी-कभी ऐसा होता है. ऐसा इसलिए है, क्योंकि प्रोजेक्ट के बड़े होने पर, आपको उन चीज़ों की सूची बनाए रखनी होगी जिन्हें आपको प्रीलोड करना है. सबसे खराब बात यह है कि अगर एक ही समय में कई अलग-अलग संसाधनों को लोड किया जा रहा है, तो लोड होने की प्रोग्रेस का हिसाब कैसे लगाया जाए, यह साफ़ तौर पर नहीं पता चलता. यहां हमारी कस्टम और बहुत सामान्य एब्स्ट्रैक्ट क्लास 'टास्क' काम आती है. इसका मुख्य मकसद, नेस्ट किए गए टास्क के स्ट्रक्चर को अनलिमिटेड तौर पर इस्तेमाल करने की सुविधा देना है. इसमें, किसी टास्क के लिए उसके सब-टास्क बनाए जा सकते हैं. इसके अलावा, हर टास्क की प्रोग्रेस, उसके सब-टास्क की प्रोग्रेस के हिसाब से कैलकुलेट की जाती है, न कि पैरंट टास्क की प्रोग्रेस के हिसाब से. MainPreloadTask, 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 क्लास में, यह ध्यान देने के अलावा कि यह MainPreloadTask के साथ कैसे काम करती है (शेयर किए गए टास्क को लागू करने के ज़रिए), यह भी ध्यान देने लायक है कि हम प्लैटफ़ॉर्म पर निर्भर एसेट को कैसे लोड करते हैं. आम तौर पर, हमारे पास चार तरह की इमेज होती हैं. मोबाइल स्टैंडर्ड (.ext, जहां ext फ़ाइल एक्सटेंशन है, आम तौर पर .png या .jpg), मोबाइल रेटिना (-2x.ext), टैबलेट स्टैंडर्ड (-tab.ext) और टैबलेट रेटिना (-tab-2x.ext). MainPreloadTask में ऐसेट का पता लगाने और चार ऐसेट कलेक्शन को हार्डकोड करने के बजाय, हम सिर्फ़ यह बताते हैं कि प्रीलोड करने के लिए ऐसेट का नाम और एक्सटेंशन क्या है. साथ ही, यह भी बताते हैं कि ऐसेट प्लैटफ़ॉर्म पर निर्भर है या नहीं (responsive = 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 mobile को डेवलप करते समय, हमें पता चला कि हम काम करने के बजाय, फ़ोटो बूथ से खेलने में काफ़ी समय बिताते हैं :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 का साइज़ बहुत बड़ा है और इसमें कई तरह की टेक्नोलॉजी का इस्तेमाल किया गया है. इसलिए, इस लेख में हमने इस्तेमाल किए गए तरीकों में से सिर्फ़ कुछ तरीकों के बारे में बताया है.

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

क्रेडिट

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

रेफ़रंस