शेडर के बारे में जानकारी

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

मैंने पहले आपको Three.js का परिचय दिया था. अगर आपने नहीं पढ़ा है, तो ऐसा हो सकता है, क्योंकि इस लेख में इस जानकारी को आधार बनाया जा रहा है.

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

1. हमारे दो शेडर

OpenGL, फ़िक्स्ड पाइपलाइन का इस्तेमाल नहीं करता है. इसे आसान शब्दों में कहें, तो यह आपको अपने कॉन्टेंट को बॉक्स से बाहर रेंडर करने का कोई तरीका नहीं देता है. हालांकि, यह Programmable पाइपलाइन को ऑफ़र करता है, जो ज़्यादा असरदार है. हालांकि, इसे समझना और इस्तेमाल करना मुश्किल है. कम शब्दों में कहें, तो प्रोग्रामेबल पाइपलाइन का मतलब है, एक प्रोग्रामर के तौर पर, वर्टेक्स वगैरह को स्क्रीन पर लाने की ज़िम्मेदारी आपकी होती है. शेडर इस पाइपलाइन का हिस्सा हैं और ये दो तरह के होते हैं:

  1. वर्टेक्स शेडर
  2. फ़्रैगमेंट शेडर

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

2. वर्टेक्स शेडर

एक गोले की तरह, एक मानक आदिम आकार लें. यह वर्टेक्स से बना है, है न? इनमें से हर शीर्ष को एक वर्टेक्स शेडर असाइन किया जाता है और इससे गड़बड़ी हो सकती है. यह वर्टेक्स शेडर पर निर्भर करता है कि वह हर एक के साथ असल में क्या करता है, लेकिन इसकी एक ज़िम्मेदारी है: इसे कभी न कुछ ऐसा सेट करना चाहिए जिसे gl_Position कहते हैं. यह एक 4D फ़्लोट वेक्टर होता है, जो स्क्रीन पर वर्टेक्स की आखिरी पोज़िशन है. खुद में और खुद में यह एक दिलचस्प प्रक्रिया है, क्योंकि हम असल में 2D स्क्रीन पर 3D पोज़िशन (x,y,z वाला वर्टेक्स) या प्रोजेक्ट करने की बात कर रहे हैं. हमारे लिए शुक्र है कि अगर हम नियमों का पालन कर रहे हैं, तो हमारे पास gl_Position को सेट करने का आसान तरीका होगा.हालांकि, इससे बहुत मुश्किल नहीं होगी.

3. फ़्रैगमेंट शेडर

अब हमारे पास अपना ऑब्जेक्ट है, जिसके शिखर हैं और हमने उन्हें 2D स्क्रीन पर दिखाया है, लेकिन हम जिन रंगों का इस्तेमाल करते हैं, उनका क्या? बनावट और रोशनी के बारे में क्या? फ़्रैगमेंट शेडर इसी तरह काम करता है. वर्टेक्स शेडर की तरह, फ़्रैगमेंट शेडर में भी सिर्फ़ एक काम करना ज़रूरी है: इसे gl_FragColor वैरिएबल को सेट या खारिज करना चाहिए. इसके अलावा, यह 4D फ़्लोट वेक्टर भी सेट या खारिज करना चाहिए जो हमारे फ़्रैगमेंट का आखिरी रंग है. लेकिन फ़्रैगमेंट क्या है? तीन शीर्षों के बारे में सोचें, जिनसे त्रिभुज बनता है. उस त्रिभुज में मौजूद हर पिक्सल को ड्रॉ करना होगा. फ़्रैगमेंट, तीनों वर्टेक्स से मिला डेटा होता है, ताकि उस त्रिभुज में हर पिक्सल को बनाने के मकसद से बनाया जा सके. इस वजह से, फ़्रैगमेंट को उनके मूल शीर्षों से इंटरपोलेट की गई वैल्यू मिलती हैं. अगर एक शीर्ष का रंग लाल है और पास का पासा नीला है, तो हम रंगों की वैल्यू लाल से बैंगनी से नीले रंग में देखेंगे.

4. शेडर वैरिएबल

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

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

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

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

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

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

5. बोंजॉर्नो वर्ल्ड

इसके बाद, वर्टेक्स शेडर की नमस्ते दुनिया देखें:

/**
* Multiply each vertex by the model-view matrix
* and the projection matrix (both provided by
* Three.js) to get a final vertex position
*/
void main() {
gl_Position = projectionMatrix *
                modelViewMatrix *
                vec4(position,1.0);
}   

और फ़्रैगमेंट शेडर के लिए भी ऐसा ही है:

/**
* Set the colour to a lovely pink.
* Note that the color is a 4D Float
* Vector, R,G,B and A and each part
* runs from 0.0 to 1.0
*/
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}

हालांकि, यह मुश्किल नहीं है, है न?

वर्टेक्स शेडर में, हमेंThree.js की मदद से कुछ यूनिफ़ॉर्म भेजी जाती हैं. ये दोनों यूनिफ़ॉर्म 4D मैट्रिक्स हैं. इन्हें मॉडल-व्यू मैट्रिक्स और प्रोजेक्शन मैट्रिक्स कहा जाता है. आपको यह जानने की बहुत ज़रूरत नहीं है कि इनकी सटीक जानकारी कैसे काम करती है. हालांकि, यह समझना हमेशा बेहतर होता है कि अगर ये काम कर सकें, तो ये चीज़ें कैसे काम करती हैं. कम शब्दों में कहा जाए, तो इस तरह से वर्टेक्स की 3D पोज़िशन को स्क्रीन पर आखिरी 2D पोज़िशन पर दिखाया जाता है.

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

6. मेशशेडर मटीरियल का इस्तेमाल करना

ठीक है, तो हमने एक शेडर सेट अप कर लिया है, लेकिन हम नियमों को Three.js के साथ कैसे इस्तेमाल कर सकते हैं? लगता है कि यह बहुत आसान है. इसके बजाय, यह कुछ ऐसा होगा:

/**
* Assume we have jQuery to hand and pull out
* from the DOM the two snippets of text for
* each of our shaders
*/
var shaderMaterial = new THREE.MeshShaderMaterial({
vertexShader:   $('vertexshader').text(),
fragmentShader: $('fragmentshader').text()
});

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

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

7. अगले चरण

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

8. एक नकली रोशनी

आइए कलरिंग को अपडेट करें, ताकि यह चपटे रंग वाली कोई चीज़ न हो. हम इस बात पर एक नज़र डाल सकते हैं कि थ्री.js किस तरह लाइटिंग को हैंडल करता है, लेकिन मुझे यकीन है कि आपको यह पसंद आएगा और यह हमारी ज़रूरत से ज़्यादा जटिल होगा, इसलिए हम इसमें नकल करने का काम कर रहे हैं. आपको पूरी तरह से उन शानदार शेडर के बारे में जानना चाहिए जो थ्री.js का हिस्सा हैं. साथ ही, आपको क्रिस मिल्क और Google के Rome के हाल ही के शानदार WebGL प्रोजेक्ट के बेहतरीन शेडर भी देखने चाहिए. हमारे शेडर को वापस देखें. हम फ़्रैगमेंट शेडर के लिए सामान्य तौर पर उपलब्ध हर वर्टेक्स शेडर को उपलब्ध कराने के लिए, अपने Vertex शेडर को अपडेट करेंगे. हम इन तरीकों की मदद से ऐसा करते हैं:

// create a shared variable for the
// VS and FS containing the normal
varying vec3 vNormal;

void main() {

// set the vNormal value with
// the attribute value passed
// in by Three.js
vNormal = normal;

gl_Position = projectionMatrix *
                modelViewMatrix *
                vec4(position,1.0);
}

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

// same name and type as VS
varying vec3 vNormal;

void main() {

// calc the dot product and clamp
// 0 -> 1 rather than -1 -> 1
vec3 light = vec3(0.5,0.2,1.0);
    
// ensure it's normalized
light = normalize(light);

// calculate the dot product of
// the light to the vertex normal
float dProd = max(0.0, dot(vNormal, light));

// feed into our frag colour
gl_FragColor = vec4(dProd, dProd, dProd, 1.0);

}

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

आगे क्या करना है? अच्छा होगा कि शायद कुछ वर्टेक्स पोज़िशन्स के साथ छेड़छाड़ करके देखें.

9. विशेषताएं

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

आइए, वर्टेक्स शेडर में एट्रिब्यूट जोड़कर शुरुआत करते हैं:

attribute float displacement;
varying vec3 vNormal;

void main() {

vNormal = normal;

// push the displacement into the three
// slots of a 3D vector so it can be
// used in operations with other 3D
// vectors like positions and normals
vec3 newPosition = position + 
                    normal * 
                    vec3(displacement);

gl_Position = projectionMatrix *
                modelViewMatrix *
                vec4(newPosition,1.0);
}

यह कैसा दिखता है?

वाकई में कुछ खास नहीं! इसकी वजह यह है कि इस एट्रिब्यूट को MeshShaderMaterial में सेट अप नहीं किया गया है, इसलिए शेडर इफ़ेक्ट के बजाय शून्य वैल्यू का इस्तेमाल करता है. यह इस समय प्लेसहोल्डर की तरह है. हम अगले कुछ सेकंड में JavaScript में MashShaderMaterial में एट्रिब्यूट जोड़ देंगे और तीन.js हमारे लिए अपने-आप दोनों को एक साथ टाई कर देगा.

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

10. MeshShader सामग्री को अपडेट किया जा रहा है

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

var attributes = {
displacement: {
    type: 'f', // a float
    value: [] // an empty array
}
};

// create the material and now
// include the attributes property
var shaderMaterial = new THREE.MeshShaderMaterial({
attributes:     attributes,
vertexShader:   $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});

// now populate the array of attributes
var vertices = sphere.geometry.vertices;
var values = attributes.displacement.value
for(var v = 0; v < vertices.length; v++) {
values.push(Math.random() * 30);
}

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

11. चूसने के लिए बने वीडियो को ऐनिमेट किया जा रहा है

हमें इसे पूरी तरह से ऐनिमेट करना चाहिए. हम यह कैसे करते हैं? वैसे तो हमें इन दो चीज़ों पर काम करना होगा:

  1. हर फ़्रेम में कितने डिस्प्लेसमेंट लागू होने चाहिए, इसे ऐनिमेट करने के लिए एक यूनिफ़ॉर्म. हम इसके लिए साइन या कोसाइन का इस्तेमाल कर सकते हैं, क्योंकि वे -1 से 1 तक चलते हैं
  2. JS में ऐनिमेशन लूप

हम MeshShader सामग्री और Vertex Shader, दोनों में यूनिफ़ॉर्म को जोड़ने जा रहे हैं. पहला Vertex Shader:

uniform float amplitude;
attribute float displacement;
varying vec3 vNormal;

void main() {

vNormal = normal;

// multiply our displacement by the
// amplitude. The amp will get animated
// so we'll have animated displacement
vec3 newPosition = position + 
                    normal * 
                    vec3(displacement *
                        amplitude);

gl_Position = projectionMatrix *
                modelViewMatrix *
                vec4(newPosition,1.0);
}

इसके बाद, हम MeshShaderMaterial को अपडेट करते हैं:

// add a uniform for the amplitude
var uniforms = {
amplitude: {
    type: 'f', // a float
    value: 0
}
};

// create the final material
var shaderMaterial = new THREE.MeshShaderMaterial({
uniforms:       uniforms,
attributes:     attributes,
vertexShader:   $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});

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

JavaScript में, अब हमें रेंडर कॉल को किसी फ़ंक्शन में रैप करना होगा. इसके बाद, उसे कॉल करने के लिए requestAnimationFrame का इस्तेमाल करना होगा. वहां हमें वर्दी की वैल्यू भी अपडेट करनी होगी.

var frame = 0;
function update() {

// update the amplitude based on
// the frame value
uniforms.amplitude.value = Math.sin(frame);
frame += 0.1;

renderer.render(scene, camera);

// set up the next call
requestAnimFrame(update);
}
requestAnimFrame(update);

12. नतीजा

यह बहुत आसान है! अब आपको दिखेगा कि यह अजीब और हल्के-फुल्के पलट वाले तरीके से ऐनिमेट हो रहा है.

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