शेडर्स का परिचय

Starter pack

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

प्रस्तावना

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

अगर शुरुआत में सब कुछ समझ में नहीं आता है तो हतोत्साहित न हों। यह सामान्य है। यह एक नई अवधारणा है और इसे समझने के लिए अभ्यास की आवश्यकता होती है। मैं आपको समय लेने, प्रयोग करने और अभ्यास करने की सलाह दूंगा, लेकिन मैं वादा करता हूँ, यह सार्थक होगा! शेडर्स बेहद शक्तिशाली हैं और आपको किसी भी विज़ुअल इफेक्ट को बनाने का नियंत्रण देंगे जिसकी आप कल्पना कर सकते हैं।

इसके अलावा, हम सभी की अलग-अलग सीखने की शैलियाँ होती हैं। कुछ लोग पढ़कर बेहतर सीखते हैं, कुछ वीडियो देखकर और कुछ करके। इस विषय के लिए विशेष रूप से, विभिन्न स्रोतों को क्रॉस-रेफरेंस करना बहुत सहायक हो सकता है। मैं इस अध्याय के अंत में आपके साथ संसाधन साझा करूंगा ताकि आप अपने ज्ञान को संकलित कर सकें और यहाँ से परे जा सकें।

मुझे उम्मीद है कि मैंने आपको डराया नहीं है और आप शेडर्स के बारे में सीखने के लिए उत्साहित हैं। चलिए शुरू करते हैं!

शेडर्स क्या हैं?

शेडर्स छोटे प्रोग्राम होते हैं जो GPU (Graphics Processing Unit) पर चलते हैं। इन्हें GLSL (OpenGL Shading Language) में लिखा जाता है, जो C जैसी होती है।

इन्हें एक mesh के vertices को पोजीशन करने (Vertex Shader) और faces के प्रत्येक pixel को रंगीन करने (Fragment Shader) के लिए उपयोग किया जाता है।

वास्तव में, हम हमेशा से शेडर्स का उपयोग कर रहे हैं। जब हम एक material बनाते हैं, तो हम एक shader का उपयोग कर रहे होते हैं। उदाहरण के लिए, जब हम MeshBasicMaterial बनाते हैं, तो हम एक shader का उपयोग कर रहे होते हैं जो mesh को एकल रंग से रंगीन करता है। जब हम MeshStandardMaterial बनाते हैं, तो हम एक shader का उपयोग करते हैं जो लाइटिंग, शैडोज़ और रिफ्लेक्शन्स को सिमुलेट करता है।

Vertex Shader

एक vertex shader एक प्रोग्राम है जो geometry के प्रत्येक vertex के लिए निष्पादित होता है। इसका प्रमुख कार्य vertices को 3D स्पेस (हमारी 3D दुनिया) से 2D स्पेस (हमारी स्क्रीन या viewport) में परिवर्तित करना है। यह परिवर्तन कई matrices का उपयोग करके होता है:

  • View Matrix: यह matrix दृश्य में कैमरे की स्थिति और उन्मुखता का प्रतिनिधित्व करती है। यह vertices को world space से camera space में transform करती है।
  • Projection Matrix: यह matrix, या तो perspective या orthographic, vertices को camera space से normalized device coordinates (NDC) में convert करती है, उन्हें अंतिम प्रोजेक्शन के लिए 2D स्क्रीन पर तैयार करती है।
  • Model Matrix: यह matrix दृश्य में प्रत्येक व्यक्तिगत ऑब्जेक्ट की स्थिति, घुमाव और स्केल को समाहित करती है। यह vertices को object space से world space में transform करती है।

इसके अलावा, vertex shader भी vertex की मूल स्थिति और इसके साथ अथवा अन्य attributes को शामिल करता है।

Schema of the vertex shader

प्रत्येक geometry के vertex के लिए, vertex shader निष्पादित होगा।

अंत में, 2D स्पेस में vertex की रूपांतरित स्थिति को पूर्वनिर्धारित variable gl_Position के माध्यम से वापस किया जाता है। सभी vertices के रूपांतरण के बाद, GPU उनके बीच के मूल्यों को interpolate करता है ताकि geometry के faces उत्पन्न हो सकें, जिन्हें तब rasterize और स्क्रीन पर प्रदर्शित किया जाता है।

Fragment Shader

एक फ्रैगमेंट शेडर, जिसे पिक्सेल शेडर के नाम से भी जाना जाता है, एक प्रोग्राम है जो प्रत्येक फ्रैगमेंट (या पिक्सेल) के लिए निष्पादित होता है जो रास्टराइजेशन प्रक्रिया द्वारा उत्पन्न होता है। इसका मुख्य कार्य स्क्रीन पर प्रत्येक पिक्सेल का अंतिम रंग निर्धारित करना है।

Schema of the fragment shader

रास्टराइजेशन के दौरान उत्पन्न प्रत्येक फ्रैगमेंट के लिए, फ्रैगमेंट शेडर निष्पादित होगा।

फ्रैगमेंट शेडर वर्टेक्स शेडर से इंटरपोलेटेड मान प्राप्त करता है, जैसे रंग, टेक्सचर कोऑर्डिनेट्स, नॉर्मल्स, और जिओमेट्री के वर्टिसेस से जुड़े अन्य एट्रिब्यूट्स। इन इंटरपोलेटेड मानों को varyings कहा जाता है, और ये प्रत्येक फ्रैगमेंट स्थान पर सतह गुणों के बारे में जानकारी प्रदान करते हैं।

इंटरपोलेटेड मानों के अतिरिक्त, फ्रैगमेंट शेडर टेक्सचर सैंपल्स भी कर सकता है और यूनिफॉर्म वेरिएबल्स तक पहुंच सकता है, जो सभी फ्रैगमेंट्स में समान होती हैं। ये यूनिफॉर्म वेरिएबल्स प्रकाश स्थानों, material गुणधर्मों, या शेडिंग गणनाओं के लिए आवश्यक किसी भी अन्य डेटा का प्रतिनिधित्व कर सकती हैं।

हम इस पाठ में बाद में attributes और uniforms पर फिर से आएंगे।

इनपुट डेटा का उपयोग करते हुए, फ्रैगमेंट शेडर विभिन्न गणनाएँ करता है ताकि फ्रैगमेंट का अंतिम रंग निर्धारित किया जा सके। इसमें जटिल प्रकाश गणनाएं, टेक्सचर मैपिंग, शेडिंग प्रभाव, या दृश्य में वांछित किसी भी अन्य दृश्य प्रभाव शामिल हो सकते हैं।

जब रंग गणना पूरी हो जाती है, तो फ्रैगमेंट शेडर gl_FragColor पूर्वनिर्धारित वेरिएबल का उपयोग करके फ्रैगमेंट का अंतिम रंग आउटपुट करता है।

मैंने जितना संभव हो सका, शेडर्स को सरल तरीके से समझाने की कोशिश की और उद्देश्यपूर्वक कुछ तकनीकी विवरणों को नहीं शामिल किया। लेकिन मैं समझता हूँ कि यह अभी भी कुछ हद तक अमूर्त हो सकता है। आइए एक सरल शेडर बनाएं ताकि देख सकें कि यह व्यवहार में कैसे काम करता है।

आपका पहला शेडर

स्टार्टर पैक चलाएं। आपको स्क्रीन के बीच में काले प्लेन वाला यह frame देखना चाहिए:

A frame with a black plane

ShaderPlane.jsx फाइल खोलें, इसमें प्लेन ज्योमेट्री और बेसिक material वाला एक साधारण mesh है। हम इस material को कस्टम शेडर material से बदल देंगे।

shaderMaterial

कस्टम शेडर material बनाने के लिए, हम Drei लाइब्रेरी से shaderMaterial फ़ंक्शन का उपयोग करते हैं।

यह 3 पैरामीटर लेता है:

  • uniforms: एक ऑब्जेक्ट जिसमें शेडर में उपयोग किए गए यूनिफॉर्म वेरिएबल्स होते हैं। इसे अभी के लिए खाली रखें।
  • vertexShader: वर्टेक्स शेडर के GLSL कोड वाली एक स्ट्रिंग।
  • fragmentShader: फ्रैगमेंट शेडर के GLSL कोड वाली एक स्ट्रिंग।

हमारी फाइल के ऊपर, आइए एक नया शेडर material घोषित करें जिसका नाम MyShaderMaterial हो:

import { shaderMaterial } from "@react-three/drei";

const MyShaderMaterial = shaderMaterial(
  {},
  `
  void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  `
  void main() {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
  }
  `
);

हम थोड़ी देर में शेडर कोड के अंदर जाएंगे।

इसे React Three Fiber के साथ डिक्लेयरेटिव तरीके से उपयोग करने में सक्षम होने के लिए, हम extend मेथड का उपयोग करते हैं:

import { extend } from "@react-three/fiber";
// ...

extend({ MyShaderMaterial });

अब हम <meshBasicMaterial> को हमारे नए शेडर material से बदल सकते हैं:

import { shaderMaterial } from "@react-three/drei";
import { extend } from "@react-three/fiber";

const MyShaderMaterial = shaderMaterial(
  {},
  `
  void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  `
  void main() {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
  }
  `
);

extend({ MyShaderMaterial });

export const ShaderPlane = ({ ...props }) => {
  return (
    <mesh {...props}>
      <planeGeometry args={[1, 1]} />
      <myShaderMaterial />
    </mesh>
  );
};

आपको पहले जैसा ही काला प्लेन देखना चाहिए। हमने अभी तक कुछ नहीं बदला है, लेकिन अब हम एक कस्टम शेडर material का उपयोग कर रहे हैं।

यह सत्यापित करने के लिए कि यह काम कर रहा है, आइए उस रंग को बदलें जो हम फ्रैगमेंट शेडर में लौटा रहे हैं। gl_FragColor लाइन को निम्नलिखित से बदलें:

gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

gl_FragColor एक पूर्वनिर्धारित वेरिएबल है जो फ्रैगमेंट के रंग का प्रतिनिधित्व करता है। यह एक vec4 (4 घटकों वाला एक वेक्टर) है जो रंग के red, green, blue, और alpha चैनलों का प्रतिनिधित्व करता है। प्रत्येक घटक एक float होता है जो 0 से 1 के बीच होता है।

पहले घटक को 1.0 पर सेट करके, हम red चैनल को उसकी अधिकतम मान में सेट कर रहे हैं, जिससे red रंग परिणामित होगा।

आपको स्क्रीन के बीच में एक red प्लेन देखना चाहिए:

A frame with a red plane

बधाई हो! आपने अपना पहला शेडर material बना लिया है। यह एक साधारण है, लेकिन यह एक शुरुआत है।

Shader Code

आगे बढ़ने से पहले, चलिए अपना डेवलपमेंट वातावरण सेटअप करते हैं ताकि हम शेडर कोड आराम से लिख सकें।

आपके पास शेडर कोड लिखने के दो विकल्प हैं:

  • Inline: आप शेडर कोड को सीधे JavaScript फाइल में लिख सकते हैं।
  • External: आप शेडर कोड को एक अलग फाइल में .glsl एक्सटेंशन के साथ लिख सकते हैं और इसे अपने JavaScript फाइल में इम्पोर्ट कर सकते हैं।

मैं सामान्यतः सही material फाइल्स के अंदर inline तरीके को पसंद करता हूँ, इस तरह से शेडर कोड material डिक्लेरेशन के करीब होता है।

लेकिन इसे और अच्छे से लिखने और पढ़ने के लिए, मैं GLSL के लिए एक syntax highlighter का उपयोग करने की सिफारिश करता हूँ। आप Comment tagged templates एक्सटेंशन को Visual Studio Code के लिए उपयोग कर सकते हैं। यह टेम्पलेट स्ट्रिंग्स के अंदर GLSL कोड को हाइलाइट करेगा।

GLSL syntax highlighter

इंस्टॉल करने के बाद, syntax highlighter को सक्षम करने के लिए, आपको शेडर कोड की शुरुआत में निम्नलिखित कमेंट जोड़ने होंगे:

const MyShaderMaterial = shaderMaterial(
  {},
  /* glsl */ `
  void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  /* glsl */ `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
  `
);

आपको टेम्पलेट स्ट्रिंग्स में GLSL कोड हाइलाइटेड दिखाई देगा:

GLSL syntax highlighter in action

ऊपर का वर्टेक्स शेडर अब सही syntax highlighting के साथ है और पढ़ने में आसान है।

यह सब कुछ है जो आपको inline शेडर कोड के लिए चाहिए। यदि आप चाहें तो अपना शेडर कोड अलग रखने के लिए एक external फाइल का उपयोग कर सकते हैं। चलिए देखते हैं कैसे करना है।

GLSL फ़ाइलें आयात करें

सबसे पहले, src फोल्डर में एक नया फोल्डर shaders नाम से बनाएँ। इस फोल्डर के अंदर दो फ़ाइलें बनाएँ: myshader.vertex.glsl और myshader.fragment.glsl, और इन फ़ाइलों में संबंधित शेडर कोड कॉपी करें।

myshader.vertex.glsl:

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

myshader.fragment.glsl:

void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

आप अपनी पसंद की नामकरण योजना का उपयोग करने के लिए स्वतंत्र हैं, और यदि आपके पास बहुत से शेडर फाइलें हैं तो आप उप फोल्डर में इन्हें समूहित कर सकते हैं।

फिर, इन फ़ाइलों को अपने JavaScript फ़ाइल में आयात करने में सक्षम होने के लिए, हमें इस प्लगइन को विकास निर्भरता के रूप में इंस्टॉल करना होगा: vite-plugin-glsl:

yarn add vite-plugin-glsl --dev

फिर, अपने vite.config.js फ़ाइल में प्लगइन को आयात करें और इसे plugins एरे में जोड़ें:

import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import glsl from "vite-plugin-glsl";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), glsl()],
});

अब आप GLSL फ़ाइलों को अपने JavaScript फ़ाइल में आयात कर सकते हैं और इन्हें शेडर कोड के रूप में उपयोग कर सकते हैं:

import myShaderFragment from "./shaders/myshader.fragment.glsl";
import myShaderVertex from "./shaders/myshader.vertex.glsl";

const MyShaderMaterial = shaderMaterial({}, myShaderVertex, myShaderFragment);

अब हमारे पास शेडर कोड लिखने और आयात करने का एक आरामदायक तरीका है, हम शेडर कोड के विभिन्न भागों का पता लगाना शुरू कर सकते हैं।

GLSL

Shader कोड GLSL (OpenGL Shading Language) में लिखा जाता है। यह एक C-जैसी भाषा है, चलिए इसके बुनियादी पहलू देखते हैं।

Types

GLSL में कई प्रकार होते हैं, लेकिन सबसे सामान्य हैं:

  • bool: एक boolean मान (true या false).
  • int: एक पूर्णांक संख्या।
  • float: एक तैरती बिंदु संख्या।
  • vectors: संख्याओं का एक संग्रह। vec2 2 तैरती बिंदु संख्याओं (x और y) का संग्रह है, vec3 3 तैरती बिंदु संख्याओं (x, y, और z) का संग्रह है, और vec4 4 तैरती बिंदु संख्याओं (x, y, z, और w) का संग्रह है। x, y, z, और w की बजाय, आप रंगों के लिए r, g, b, और a का भी उपयोग कर सकते हैं, ये आपस में विनिमेय होते हैं।
  • matrices: vectors का संग्रह। उदाहरण के लिए, mat2 2 vectors का संग्रह है, mat3 3 vectors का संग्रह है, और mat4 4 vectors का संग्रह है।

Swizzling और manipulations

आप swizzling का उपयोग करके vector के घटकों को एक्सेस कर सकते हैं। उदाहरण के लिए, आप एक vector के घटकों का उपयोग करके एक नया vector बना सकते हैं:

vec3 a = vec3(1.0, 2.0, 3.0);
vec2 b = a.xy;

इस उदाहरण में, b a के x और y घटकों के साथ एक vector होगा।

आप घटकों के क्रम को बदलने के लिए भी swizzling का उपयोग कर सकते हैं:

vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = a.zyx;

इस उदाहरण में, b vec3(3.0, 2.0, 1.0) के समान होगा।

यदि आपको समान घटकों के साथ एक नया vector बनाना है, तो आप constructor का उपयोग कर सकते हैं:

vec3 a = vec3(1.0);

इस उदाहरण में, a vec3(1.0, 1.0, 1.0) के समान होगा।

आपरेटर

GLSL में सामान्य अंकगणित आपरेटर होते हैं: +, -, *, /, +=, /=, *= और सामान्य तुलना आपरेटर होते हैं: ==, !=, >, <, >=, <=.

इन्हें सही प्रकारों के साथ उपयोग किया जाना चाहिए। उदाहरण के लिए, आप एक integer को एक float में नहीं जोड़ सकते, आपको पहले integer को float में बदलना होगा:

int a = 1;
float b = 2.0;
float c = float(a) + b;

आप vectors और matrices पर भी ऑपरेशन कर सकते हैं:

vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(4.0, 5.0, 6.0);
vec3 c = a + b;

जो कि निम्न के समान है:

vec3 c = vec3(a.x + b.x, a.y + b.y, a.z + b.z);

फंक्शन

vertex और fragment shaders का entry point main फंक्शन है। यह वह फंक्शन है जिसे shader को कॉल करते समय execute किया जाता है।

void main() {
  // आपका कोड यहाँ
}

void फंक्शन का return type है। इसका मतलब है कि फंक्शन कुछ वापस नहीं करता।

आप अपने खुद के फंक्शन भी परिभाषित कर सकते हैं:

float add(float a, float b) {
  return a + b;
}

आप इस फंक्शन को main फंक्शन में कॉल कर सकते हैं:

void main() {
  float result = add(1.0, 2.0);
  // ...
}

GLSL सामान्य ऑपरेशनों के लिए कई built-in फंक्शन प्रदान करता है जैसे sin, cos, max, min, abs, round, floor, ceil, और कई उपयोगी अन्य जैसे mix, step, length, distance, और अधिक।

हम अगले पाठ में आवश्यक वाले फंक्शनों की खोज करेंगे और उनके साथ अभ्यास करेंगे।

लूप्स और शर्तें

GLSL for लूप्स और if स्टेटमेंट्स का समर्थन करता है। ये JavaScript के समान काम करते हैं:

for (int i = 0; i < 10; i++) {
  // यहाँ आपका कोड
}

if (condition) {
  // यहाँ आपका कोड
} else {
  // यहाँ आपका कोड
}

लॉगिंग / डीबगिंग

चूंकि शेडर प्रोग्राम प्रत्येक वर्टेक्स और फ्रेगमेंट के लिए समानांतर में चलते हैं, इसलिए अपने कोड को डीबग करने के लिए console.log का उपयोग करना या ब्रेकपॉइंट्स जोड़ना संभव नहीं है। यही बात शेडर्स को डीबग करना मुश्किल बनाती है।

शेडर्स को डीबग करने का एक सामान्य तरीका यह है कि आप अपने वेरिएबल्स के मानों को विजुअलाइज़ करने के लिए gl_FragColor का उपयोग करें।

संकलन त्रुटियाँ

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

आइये gl_FragColor से alpha चैनल को हटाते हैं और देखते हैं कि क्या होता है:

void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0);
}

आपको कंसोल में एक संकलन त्रुटि दिखाई देनी चाहिए:

Compilation error

हमें यह बताता है कि gl_FragColor को 4 घटकों की आवश्यकता होती है, लेकिन हमने केवल 3 प्रदान किए।

त्रुटि को हटाने के लिए alpha चैनल को 1.0 पर पुनर्स्थापित करना न भूलें।

Uniforms

JavaScript कोड से shader में डेटा पास करने के लिए हम uniforms का उपयोग करते हैं। ये सभी vertices और fragments में constant रहते हैं।

projectionMatrix, modelViewMatrix, और position ऐसे built-in uniforms के उदाहरण हैं जो स्वचालित रूप से shader में पास किए जाते हैं।

चलो एक कस्टम uniform बनाते हैं ताकि हम shader में एक रंग पास कर सकें। हम इसका उपयोग plane को रंगने के लिए करेंगे। हम इसे uColor कहेंगे। कोड में यह स्पष्ट करने के लिए कि यह एक uniform है, इसके नाम के साथ u उपसर्ग करना एक अच्छा अभ्यास है।

पहले, चलो इसे shaderMaterial के uniforms वस्तु में घोषित करें:

import { Color } from "three";
// ...
const MyShaderMaterial = shaderMaterial(
  {
    uColor: new Color("pink"),
  }
  // ...
);

// ...

फिर, हम इसे fragment shader में उपयोग कर सकते हैं:

uniform vec3 uColor;

void main() {
  gl_FragColor = vec4(uColor, 1.0);
}

आपको plane गुलाबी रंग में रंगा हुआ दिखना चाहिए:

A frame with a pink plane

यहां, गुलाबी रंग uniform का डिफ़ॉल्ट मान है। हम इसे सीधे material पर बदल सकते हैं:

<MyShaderMaterial uColor={"lightblue"} />

A frame with a light blue plane

plane अब हल्के नीले रंग में रंगा हुआ है।

दोनों vertex और fragment shaders uniforms को एक्सेस कर सकते हैं। चलो समय को दूसरे uniform के रूप में vertex shader में जोड़ें ताकि plane को ऊपर और नीचे ले जाया जा सके:

End of lesson preview

To get access to the entire lesson, you need to purchase the course.