1,00,000 तारे बनाना

नमस्कार! मेरा नाम माइकल चांग है और मैं Google की डेटा आर्ट टीम में काम करता/करती हूं. हाल ही में, हमने 1,00,000 स्टार का Chrome एक्सपेरिमेंट पूरा किया. इस एक्सपेरिमेंट में, आस-पास मौजूद तारे दिखाए गए हैं. इस प्रोजेक्ट को THREE.js और CSS3D की मदद से बनाया गया था. इस केस स्टडी में, हम खोज की प्रोसेस के बारे में बताएंगे. साथ ही, प्रोग्रामिंग की कुछ तकनीकें शेयर करेंगे. आखिर में, हम आने वाले समय में सुधार करने के बारे में कुछ बातें करेंगे.

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

1,00,000 स्टार, Chrome पर एक्सपेरिमेंट के तौर पर उपलब्ध सुविधा, जिसे डेटा आर्ट टीम ने बनाया है
100,000 Stars, आकाशगंगा में आस-पास मौजूद तारों को विज़ुअलाइज़ करने के लिए THREE.js का इस्तेमाल करता है

स्पेस डिस्कवर करना

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

मैंने ऐसे डेटा की खोज शुरू की जिसका इस्तेमाल करके, कणों की पोज़िशन को इंजेक्ट किया जा सके. इस खोज के दौरान, मुझे astronexus.com का HYG डेटाबेस मिला. यह तीन डेटा सोर्स (Hipparcos, Yale Bright Star Catalog, और Gliese/Jahreiss Catalog) का कलेक्शन है. इसमें पहले से कैलकुलेट किए गए xyz कार्टेशियन निर्देशांक भी शामिल हैं. आइए शुरू करते हैं!

स्टार डेटा प्लॉट करना.
सबसे पहले, कैटलॉग में मौजूद हर तारे को एक कण के तौर पर प्लॉट करें.
नाम वाले तारे.
कैटलॉग में मौजूद कुछ स्टार के नाम यहां लेबल किए गए हैं.

तारे के डेटा को 3D स्पेस में डालने के लिए, करीब एक घंटा लगा. डेटा सेट में कुल 1,19,617 तारे हैं. इसलिए, हर तारे को किसी कण से दिखाना, आधुनिक जीपीयू के लिए कोई समस्या नहीं है. इसमें 87 ऐसे तारे भी हैं जिनकी पहचान अलग-अलग की गई है. इसलिए, मैंने Small Arms Globe में बताई गई तकनीक का इस्तेमाल करके, सीएसएस मार्कर ओवरले बनाया है.

इस दौरान, मैंने Mass Effect सीरीज़ पूरी की थी. इस गेम में, प्लेयर को गैलेक्सी को एक्सप्लोर करने का न्योता दिया जाता है. साथ ही, अलग-अलग ग्रहों को स्कैन करने और उनके बारे में पढ़ने का मौका मिलता है. हालांकि, इन ग्रहों का इतिहास पूरी तरह से काल्पनिक है और यह विकिपीडिया जैसा लगता है. जैसे, ग्रह पर कौनसी प्रजाति रहती थी, उसका भूवैज्ञानिक इतिहास वगैरह.

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

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

Galaxy बनाने की कहानी

मेरा प्लान था कि मैं गैलेक्सी का ऐसा मॉडल जनरेट करूं जो तारे के डेटा को संदर्भ में रख सके. मुझे उम्मीद है कि इससे हमें मिल्की वे में अपनी जगह का शानदार व्यू मिलेगा.

गैलेक्सी का शुरुआती प्रोटोटाइप.
आकाशगंगा के पार्टिकल सिस्टम का प्रोटोटाइप.

मिल्की वे को जनरेट करने के लिए, मैंने 1,00,000 कण बनाए और उन्हें स्पाइरल में रखा. ऐसा मैंने गैलेक्टिक आर्म बनने के तरीके को एमुलेट करके किया. मुझे स्पाइरल आर्म के बनने की खास बातों की ज़्यादा चिंता नहीं थी, क्योंकि यह गणित के बजाय, एक प्रतिनिधि मॉडल होगा. हालांकि, मैंने स्पाइरल आर्म की संख्या को कम या ज़्यादा सही करने की कोशिश की है. साथ ही, उन्हें "सही दिशा" में घुमाने की कोशिश की है.

मिल्की वे मॉडल के बाद के वर्शन में, मैंने कणों के साथ-साथ गैलेक्सी की प्लैनर इमेज का इस्तेमाल किया है. इससे, मुझे उम्मीद है कि यह ज़्यादा फ़ोटोग्राफ़ी वाली इमेज जैसी दिखेगी. असल इमेज, स्पाइरल गैलेक्सी NGC 1232 की है. यह हमसे करीब 7 करोड़ प्रकाश वर्ष दूर है. इमेज में बदलाव करके, इसे मिल्की वे जैसा दिखाया गया है.

आकाशगंगा का स्केल तय करना.
GL की हर इकाई एक प्रकाश वर्ष होती है. इस मामले में, गोला 1,10,000 लाइट ईयर चौड़ा है, जिसमें पार्टिकल सिस्टम शामिल है.

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

मैंने एक और तरीका अपनाया. मैंने कैमरे को घुमाने के बजाय, पूरे सीन को घुमाया. मैंने कुछ अन्य प्रोजेक्ट में भी ऐसा किया है. इसका एक फ़ायदा यह है कि हर चीज़ को "टर्नटेबल" पर रखा जाता है, ताकि माउस को बाईं और दाईं ओर खींचकर छोड़ने से, ऑब्जेक्ट घूम जाए. हालांकि, ज़ूम इन करने के लिए, सिर्फ़ camera.position.z को बदलना होता है.

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

गैलेक्सी को रेंडर करने के अलग-अलग तरीके.
(ऊपर) शुरुआती कण गैलेक्सी. (नीचे) इमेज प्लेन के साथ कण.

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

सौर मंडल.
सूर्य के चारों ओर ग्रहों की कक्षा और कूइपर बेल्ट को दिखाने वाला गोला.

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

इसी तरह की तकनीक का इस्तेमाल, सूरज के कोरोना के लिए किया गया था. हालांकि, यह एक फ़्लैट स्प्राइट कार्ड होगा, जो https://github.com/mrdoob/three.js/blob/master/src/extras/core/Gyroscope.js का इस्तेमाल करके, हमेशा कैमरे की ओर होगा.

सोल रेंडर हो रहा है.
सूर्य का पुराना वर्शन.

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

यहां मुझे GL प्रिसीज़न की वजह से, z-फ़ाइटिंग से जुड़ी कुछ समस्याएं आ रही थीं. सटीक जानकारी के लिए सभी वैरिएबल, THREE.js में पहले से तय थे. इसलिए, ज़्यादा काम किए बिना सटीक जानकारी को बेहतर नहीं बनाया जा सकता. ऑरिजिन के आस-पास, सटीक गतिविधियों से जुड़ी समस्याएं उतनी गंभीर नहीं थीं. हालांकि, जब मैंने दूसरे स्टार सिस्टम का मॉडल बनाना शुरू किया, तो यह समस्या बन गई.

स्टार मॉडल.
सूर्य को रेंडर करने के लिए इस्तेमाल किए गए कोड को बाद में, दूसरे तारों को रेंडर करने के लिए भी इस्तेमाल किया गया.

मैंने z-फ़ाइटिंग को कम करने के लिए कुछ हैक इस्तेमाल किए. THREE की Material.polygonoffset एक ऐसी प्रॉपर्टी है जिसकी मदद से पॉलीगॉन को किसी दूसरी जगह पर रेंडर किया जा सकता है. इसका इस्तेमाल, कोरोना प्लेन को हमेशा सूरज की सतह के ऊपर रेंडर करने के लिए किया जाता था. इसके नीचे, सूरज का "हलो" रेंडर किया गया था, ताकि गोले से दूर जाने वाली तेज़ रोशनी की किरणें दिखाई जा सकें.

सटीक जानकारी से जुड़ी एक और समस्या यह थी कि सीन को ज़ूम इन करने पर, स्टार मॉडल हिलने लगते थे. इसे ठीक करने के लिए, मुझे सीन के रोटेशन को "शून्य पर सेट" करना पड़ा. साथ ही, तारे के मॉडल और एनवायरमेंट मैप को अलग से घुमाना पड़ा, ताकि यह एहसास दिया जा सके कि आप तारे की कक्षा में हैं.

लेंस फ़्लेयर बनाना

ज़्यादा ताकत के साथ ज़्यादा ज़िम्मेदारी आती है.
बड़ी ताकत के साथ ज़िम्मेदारी भी बढ़ जाती है.

स्पेस विज़ुअलाइज़ेशन में, मुझे लगता है कि लेंसफ़्लेयर का ज़्यादा इस्तेमाल किया जा सकता है. THREE.LensFlare इस काम के लिए इस्तेमाल किया जा सकता है. इसके लिए, मुझे कुछ ऐनामॉर्फ़िक हेक्सागॉन और जे जे एब्रम्स की शैली में कुछ बदलाव करने थे. नीचे दिए गए स्निपेट में, अपने सीन में इन्हें बनाने का तरीका बताया गया है.

// This function returns a lesnflare THREE object to be .add()ed to the scene graph
function addLensFlare(x,y,z, size, overrideImage){
var flareColor = new THREE.Color( 0xffffff );

lensFlare = new THREE.LensFlare( overrideImage, 700, 0.0, THREE.AdditiveBlending, flareColor );

// we're going to be using multiple sub-lens-flare artifacts, each with a different size
lensFlare.add( textureFlare1, 4096, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );

// and run each through a function below
lensFlare.customUpdateCallback = lensFlareUpdateCallback;

lensFlare.position = new THREE.Vector3(x,y,z);
lensFlare.size = size ? size : 16000 ;
return lensFlare;
}

// this function will operate over each lensflare artifact, moving them around the screen
function lensFlareUpdateCallback( object ) {
var f, fl = this.lensFlares.length;
var flare;
var vecX = -this.positionScreen.x _ 2;
var vecY = -this.positionScreen.y _ 2;
var size = object.size ? object.size : 16000;

var camDistance = camera.position.length();

for( f = 0; f < fl; f ++ ) {
flare = this.lensFlares[ f ];

flare.x = this.positionScreen.x + vecX * flare.distance;
flare.y = this.positionScreen.y + vecY * flare.distance;

flare.scale = size / camDistance;
flare.rotation = 0;

}
}

टेक्स्चर स्क्रोल करने का आसान तरीका

Homeworld से प्रेरित.
स्पेशल ओरिएंटेशन में मदद करने के लिए, कार्टेशियन प्लेन.

"स्पेशल ओरिएंटेशन प्लेन" के लिए, एक विशाल THREE.CylinderGeometry() बनाया गया और उसे सूरज के बीच में रखा गया. बाहर की ओर फैलने वाली "लाइट की लहर" बनाने के लिए, मैंने समय के साथ इसके टेक्स्चर ऑफ़सेट में इस तरह बदलाव किया:

mesh.material.map.needsUpdate = true;
mesh.material.map.onUpdate = function(){
this.offset.y -= 0.001;
this.needsUpdate = true;
}

map, मटीरियल से जुड़ा टेक्स्चर है. इसमें onUpdate फ़ंक्शन होता है, जिसे ओवर-राइट किया जा सकता है. इसके ऑफ़सेट को सेट करने से, टेक्सचर को उस अक्ष के साथ "स्क्रोल" किया जाता है. साथ ही, needsUpdate = true को स्पैम करने से, इस व्यवहार को लूप में चलाया जाएगा.

कलर रैंप का इस्तेमाल करना

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

तारे रेंडर करते समय, मुझे इस डेटा के आधार पर हर कण को अपना रंग देना था. ऐसा करने के लिए, कणों पर लागू किए गए शेडर मटीरियल को "एट्रिब्यूट" दिए गए थे.

var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: datastarUniforms,
attributes: datastarAttributes,
/_ ... etc _/
});
var datastarAttributes = {
size: { type: 'f', value: [] },
colorIndex: { type: 'f', value: [] },
};

colorIndex कलेक्शन को भरने से, शेडर में हर कण को अपना यूनीक रंग मिलेगा. आम तौर पर, कोई व्यक्ति रंग vec3 पास करेगा, लेकिन इस मामले में मैं रंग रैंप लुक-अप के लिए फ़्लोट पास कर रहा हूं.

कलर रैंप.
स्टार के कलर इंडेक्स से दिखने वाले रंग को देखने के लिए इस्तेमाल किया जाने वाला कलर रैंप.

कलर रैंप ऐसा दिखता है. हालांकि, मुझे JavaScript से उसका बिटमैप कलर डेटा ऐक्सेस करना था. मैंने ऐसा करने के लिए, पहले इमेज को DOM पर लोड किया. इसके बाद, उसे कैनवस एलिमेंट में ड्रॉ किया और फिर कैनवस बिटमैप को ऐक्सेस किया.

// make a blank canvas, sized to the image, in this case gradientImage is a dom image element
gradientCanvas = document.createElement('canvas');
gradientCanvas.width = gradientImage.width;
gradientCanvas.height = gradientImage.height;

// draw the image
gradientCanvas.getContext('2d').drawImage( gradientImage, 0, 0, gradientImage.width, gradientImage.height );

// a function to grab the pixel color based on a normalized percentage value
gradientCanvas.getColor = function( percentage ){
return this.getContext('2d').getImageData(percentage \* gradientImage.width,0, 1, 1).data;
}

इसके बाद, स्टार मॉडल व्यू में अलग-अलग स्टार को रंगने के लिए भी यही तरीका इस्तेमाल किया जाता है.

मेरी आँखें!
इसी तकनीक का इस्तेमाल, किसी तारे की स्पेक्ट्रल क्लास के लिए कलर लुकअप करने के लिए किया जाता है.

शेडर को ठीक करना

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

// list of shaders we'll load
var shaderList = ['shaders/starsurface', 'shaders/starhalo', 'shaders/starflare', 'shaders/galacticstars', /*...etc...*/];

// a small util to pre-fetch all shaders and put them in a data structure (replacing the list above)
function loadShaders( list, callback ){
var shaders = {};

var expectedFiles = list.length \* 2;
var loadedFiles = 0;

function makeCallback( name, type ){
return function(data){
if( shaders[name] === undefined ){
shaders[name] = {};
}

    shaders[name][type] = data;

    //  check if done
    loadedFiles++;
    if( loadedFiles == expectedFiles ){
    callback( shaders );
    }

};

}

for( var i=0; i<list.length; i++ ){
var vertexShaderFile = list[i] + '.vsh';
var fragmentShaderFile = list[i] + '.fsh';

//  find the filename, use it as the identifier
var splitted = list[i].split('/');
var shaderName = splitted[splitted.length-1];
$(document).load( vertexShaderFile, makeCallback(shaderName, 'vertex') );
$(document).load( fragmentShaderFile,  makeCallback(shaderName, 'fragment') );

}
}

loadShaders() फ़ंक्शन, शेडर फ़ाइल के नामों की सूची लेता है (फ़्रैगमेंट के लिए .fsh और वर्टिक्स शेडर के लिए .vsh). इसके बाद, वह उनका डेटा लोड करने की कोशिश करता है और फिर सूची को ऑब्जेक्ट से बदल देता है. आखिर में, आपको THREE.js यूनिफ़ॉर्म में नतीजा दिखेगा. इसमें शेडर को इस तरह पास किया जा सकता है:

var galacticShaderMaterial = new THREE.ShaderMaterial( {
vertexShader: shaderList.galacticstars.vertex,
fragmentShader: shaderList.galacticstars.fragment,
/_..._/
});

शायद मैं require.js का इस्तेमाल कर सकता था. हालांकि, इसके लिए कुछ कोड को फिर से जोड़ना पड़ता. यह तरीका काफ़ी आसान है, लेकिन इसमें सुधार किया जा सकता है. शायद इसे THREE.js एक्सटेंशन के तौर पर भी इस्तेमाल किया जा सकता है. अगर आपके पास इस सुविधा को बेहतर बनाने के सुझाव या तरीके हैं, तो कृपया हमें बताएं!

THREE.js के ऊपर सीएसएस टेक्स्ट लेबल

अपने पिछले प्रोजेक्ट, Small Arms Globe में, मैंने THREE.js सीन के ऊपर टेक्स्ट लेबल दिखाने की कोशिश की थी. मैंने जिस तरीके का इस्तेमाल किया था वह उस जगह की सटीक मॉडल पोज़िशन का हिसाब लगाता है जहां मुझे टेक्स्ट दिखाना है. इसके बाद, THREE.Projector() का इस्तेमाल करके स्क्रीन की पोज़िशन तय करता है. आखिर में, सीएसएस एलिमेंट को अपनी पसंद की पोज़िशन पर रखने के लिए, सीएसएस "top" और "left" का इस्तेमाल करता है.

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

बुनियादी आइडिया: CSS3D के मैट्रिक ट्रांसफ़ॉर्म को THREE के कैमरे और सीन से मैच करें. इसके बाद, CSS एलिमेंट को 3D में "जगह" दी जा सकती है, जैसे कि वे THREE के सीन के ऊपर हों. हालांकि, इसमें कुछ सीमाएं हैं. उदाहरण के लिए, आपके पास THREE.js ऑब्जेक्ट के नीचे टेक्स्ट डालने का विकल्प नहीं होगा. यह "top" और "left" सीएसएस एट्रिब्यूट का इस्तेमाल करके लेआउट बनाने की तुलना में अब भी काफ़ी तेज़ है.

टेक्स्ट लेबल.
WebGL के ऊपर टेक्स्ट लेबल डालने के लिए, CSS3D ट्रांसफ़ॉर्म का इस्तेमाल करना.

इसके लिए, डेमो (और सोर्स कोड में कोड) यहां देखें. हालांकि, मुझे पता चला है कि THREE.js के लिए मैट्रिक का क्रम बदल गया है. मैंने जो फ़ंक्शन अपडेट किया है:

/_ Fixes the difference between WebGL coordinates to CSS coordinates _/
function toCSSMatrix(threeMat4, b) {
var a = threeMat4, f;
if (b) {
f = [
a.elements[0], -a.elements[1], a.elements[2], a.elements[3],
a.elements[4], -a.elements[5], a.elements[6], a.elements[7],
a.elements[8], -a.elements[9], a.elements[10], a.elements[11],
a.elements[12], -a.elements[13], a.elements[14], a.elements[15]
];
} else {
f = [
a.elements[0], a.elements[1], a.elements[2], a.elements[3],
a.elements[4], a.elements[5], a.elements[6], a.elements[7],
a.elements[8], a.elements[9], a.elements[10], a.elements[11],
a.elements[12], a.elements[13], a.elements[14], a.elements[15]
];
}
for (var e in f) {
f[e] = epsilon(f[e]);
}
return "matrix3d(" + f.join(",") + ")";
}

टेक्स्ट को कैमरे के सामने रखने की ज़रूरत नहीं है, क्योंकि अब टेक्स्ट को कैमरे के सामने रखने की ज़रूरत नहीं है. इसका समाधान THREE.Gyroscope() का इस्तेमाल करना था. इससे, Object3D को सीन से अपने इनहेरिट किए गए ओरिएंटेशन को "खोने" के लिए मजबूर किया जाता है. इस तकनीक को "बिलबर्डिंग" कहा जाता है. इसके लिए, Gyroscope सबसे सही है.

सबसे अच्छी बात यह है कि सामान्य डीओएम और सीएसएस अब भी काम करती हैं. जैसे, 3D टेक्स्ट लेबल पर कर्सर घुमाने पर, उसे ड्रॉप शैडो के साथ चमकाया जा सकता है.

टेक्स्ट लेबल.
टेक्स्ट लेबल को THREE.Gyroscope() से अटैच करके, उसे हमेशा कैमरे के सामने रखना.

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

संगीत चलाना और लूप करना

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

हमारे प्रोड्यूसर वैलडियन क्लंप ने सैम से संपर्क किया. सैम के पास Mass Effect का "कटिंग फ़्लोर" वाला म्यूजिक था. उन्होंने हमें उसका इस्तेमाल करने की अनुमति दी. ट्रैक का टाइटल "In a Strange Land" है.

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

var musicA = document.getElementById('bgmusicA');
var musicB = document.getElementById('bgmusicB');
musicA.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playB = function(){
musicB.play();
}
// make it wait 15 seconds before playing again
setTimeout( playB, 15000 );
}, false);

musicB.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playA = function(){
musicA.play();
}
// otherwise the music will drive you insane
setTimeout( playA, 15000 );
}, false);

// okay so there's a bit of code redundancy, I admit it
musicA.play();

सुधार की ज़रूरत है

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

हमारे सहयोगी रे मैक्लूर ने भी कुछ समय बिताकर, जनरेटिव "स्पेस नॉइज़" बनाए. हालांकि, वेब ऑडियो एपीआई के अस्थिर होने की वजह से, उन्हें काटना पड़ा. इसकी वजह यह थी कि Chrome बार-बार क्रैश हो रहा था. यह दुर्भाग्य की बात है… लेकिन इससे हमें आने वाले समय में, साउंड स्पेस के बारे में ज़्यादा सोचने में मदद मिली है. हमें पता चला है कि Web Audio API को पैच कर दिया गया है. इसलिए, हो सकता है कि यह अब काम कर रहा हो. आने वाले समय में इस पर नज़र बनाए रखें.

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

क्रेडिट

इस प्रोजेक्ट पर काम करने की अनुमति देने के लिए, एयरॉन कोब्लिन का धन्यवाद. यूज़र इंटरफ़ेस (यूआई) के बेहतरीन डिज़ाइन और उसे लागू करने, टाइप ट्रीटमेंट, और टूर को लागू करने के लिए, जोनो ब्रैंडेल को धन्यवाद. प्रोजेक्ट का नाम और पूरी कॉपी देने के लिए, वैल्डियन क्लंप को धन्यवाद. डेटा और इमेज सोर्स के इस्तेमाल के अधिकार हासिल करने के लिए, सबाह अहमद का धन्यवाद. पब्लिकेशन के लिए सही लोगों से संपर्क करने के लिए, क्लेम राइट को धन्यवाद. डग फ़्रिट्ज़ को तकनीकी उत्कृष्टता के लिए. जॉर्ज ब्रॉवर, जिन्होंने मुझे JS और CSS सिखाया. साथ ही, THREE.js के लिए मिस्टर डूब.

रेफ़रंस