WebGL की बुनियादी बातें

Gregg Tavares
Gregg Tavares

WebGL की बुनियादी बातें

WebGL की मदद से, ब्राउज़र में रीयल-टाइम 3D ग्राफ़िक्स दिखाए जा सकते हैं. हालांकि, ज़्यादातर लोगों को यह नहीं पता कि WebGL असल में 3D एपीआई नहीं, बल्कि 2D एपीआई है. मुझे बताने दें.

WebGL सिर्फ़ दो चीज़ों पर ध्यान देता है. 2D और रंगों में क्लिपस्पेस के निर्देशांक. WebGL का इस्तेमाल करने वाले प्रोग्रामर के तौर पर, आपको WebGL को ये दो चीज़ें देनी होती हैं. इसके लिए, आपको दो "शेडर" देने होंगे. क्लिपस्पेस कोऑर्डिनेट देने वाला वर्टिक्स शेडर और कलर देने वाला फ़्रैगमेंट शेडर. क्लिपस्पेस निर्देशांक हमेशा -1 से +1 के बीच होते हैं. भले ही, आपका कैनवस किसी भी साइज़ का हो. यहां WebGL का एक आसान उदाहरण दिया गया है, जो WebGL को सबसे आसान तरीके से दिखाता है.

// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl");

// setup a GLSL program
var vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
var fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
var program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);

// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");

// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
        -1.0, -1.0,
         1.0, -1.0,
        -1.0,  1.0,
        -1.0,  1.0,
         1.0, -1.0,
         1.0,  1.0]),
    gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);

यहां दो शेडर दिए गए हैं

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

void main() {
  gl_Position = vec4(a_position, 0, 1);
}
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
  gl_FragColor = vec4(0,1,0,1);  // green
}
</script>

फिर से, क्लिपस्पेस के निर्देशांक हमेशा -1 से +1 के बीच होते हैं. भले ही, कैनवस का साइज़ कुछ भी हो. ऊपर दिए गए उदाहरण में, देखा जा सकता है कि हम सीधे तौर पर अपनी पोज़िशन का डेटा भेज रहे हैं. पोज़िशन डेटा पहले से ही क्लिपस्पेस में है, इसलिए कुछ करने की ज़रूरत नहीं है. अगर आपको 3D चाहिए, तो 3D से 2D में बदलने वाले शेडर की आपके पास ही ज़िम्मेदारी है, क्योंकि WebGL एक 2D एपीआई है! 2D आइटम के लिए, शायद आपको क्लिपस्पेस के बजाय पिक्सल में काम करना हो. इसलिए, शेडर को बदलें, ताकि हम पिक्सल में रेक्टैंगल की वैल्यू दे सकें और उसे क्लिपस्पेस में बदल सकें. नया वर्टिक्स शेडर

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;

void main() {
   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = a_position / u_resolution;

   // convert from 0->1 to 0->2
   vec2 zeroToTwo = zeroToOne * 2.0;

   // convert from 0->2 to -1->+1 (clipspace)
   vec2 clipSpace = zeroToTwo - 1.0;

   gl_Position = vec4(clipSpace, 0, 1);
}
</script>

अब हम अपने डेटा को क्लिपस्पेस से पिक्सल में बदल सकते हैं

// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);

// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    10, 20,
    80, 20,
    10, 30,
    10, 30,
    80, 20,
    80, 30]), gl.STATIC_DRAW);

आपको दिखेगा कि आयत उस हिस्से में सबसे नीचे है. WebGL, सबसे नीचे बाईं ओर को 0,0 मानता है. इसे 2D ग्राफ़िक्स एपीआई के लिए इस्तेमाल किए जाने वाले, ऊपर बाएं कोने में लाने के लिए, हम सिर्फ़ y निर्देशांक को फ़्लिप करते हैं.

gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

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

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

uniform vec4 u_color;

void main() {
   gl_FragColor = u_color;
}
</script>

यहां नया कोड दिया गया है, जो 50 रेक्टैंगल को अलग-अलग जगहों पर और अलग-अलग रंगों में बनाता है.

...

  var colorLocation = gl.getUniformLocation(program, "u_color");
  ...
  // Create a buffer
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

  // draw 50 random rectangles in random colors
  for (var ii = 0; ii < 50; ++ii) {
    // Setup a random rectangle
    setRectangle(
        gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));

    // Set a random color.
    gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }
}

// Returns a random integer from 0 to range - 1.
function randomInt(range) {
  return Math.floor(Math.random() * range);
}

// Fills the buffer with the values that define a rectangle.
function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,
     x2, y1,
     x1, y2,
     x1, y2,
     x2, y1,
     x2, y2]), gl.STATIC_DRAW);
}

हमें उम्मीद है कि आपको यह समझ आ गया होगा कि WebGL एक बहुत ही आसान एपीआई है. 3D में काम करना ज़्यादा मुश्किल हो सकता है. हालांकि, यह मुश्किल प्रोग्रामर, यानी आपके ज़रिए ज़्यादा जटिल शेडर के तौर पर जोड़ी जाती है. WebGL API, 2D और काफ़ी आसान है.

type="x-shader/x-vertex" और type="x-shader/x-fragment" का क्या मतलब है?

<script> टैग में डिफ़ॉल्ट रूप से JavaScript होता है. कोई टाइप नहीं डाला जा सकता या type="javascript" या type="text/javascript" डाला जा सकता है. ऐसा करने पर, ब्राउज़र कॉन्टेंट को JavaScript के तौर पर समझेगा. अगर आपने स्क्रिप्ट टैग में कोई और चीज़ डाली, तो ब्राउज़र उस कॉन्टेंट को अनदेखा कर देगा.

इस सुविधा का इस्तेमाल करके, स्क्रिप्ट टैग में शेडर स्टोर किए जा सकते हैं. इससे भी बेहतर, हम अपना टाइप बना सकते हैं और अपने JavaScript में यह तय कर सकते हैं कि शेडर को वर्टिक्स शेडर या फ़्रैगमेंट शेडर के तौर पर कंपाइल करना है या नहीं.

इस मामले में, फ़ंक्शन createShaderFromScriptElement, तय की गई id वाली स्क्रिप्ट खोजता है. इसके बाद, type को देखकर यह तय करता है कि किस तरह का शेडर बनाना है.

WebGL इमेज प्रोसेसिंग

WebGL में इमेज प्रोसेस करना आसान है. कितना आसान है? नीचे पढ़ें.

WebGL में इमेज बनाने के लिए, हमें टेक्सचर का इस्तेमाल करना होगा. ठीक उसी तरह जिस तरह WebGL, पिक्सल के बजाय रेंडर करते समय क्लिपस्पेस कोऑर्डिनेट की उम्मीद करता है, उसी तरह WebGL, टेक्सचर पढ़ते समय टेक्सचर कोऑर्डिनेट की उम्मीद करता है. टेक्स्चर कोऑर्डिनेट, 0.0 से 1.0 के बीच होते हैं. भले ही, टेक्स्चर के डाइमेंशन कुछ भी हों. हम सिर्फ़ एक रेक्टैंगल (दो त्रिकोण) बना रहे हैं. इसलिए, हमें WebGL को यह बताना होगा कि रेक्टैंगल के हर पॉइंट का टेक्सचर में क्या मतलब है. हम इस जानकारी को वर्टिक्स शेडर से फ़्रैगमेंट शेडर में भेजेंगे. इसके लिए, हम 'बदलाव' नाम के एक खास तरह के वैरिएबल का इस्तेमाल करेंगे. इसे वैरिएबल कहा जाता है, क्योंकि यह अलग-अलग होता है. WebGL, फ़्रेगमेंट शेडर का इस्तेमाल करके हर पिक्सल को ड्रॉ करते समय, वेरटेक्स शेडर में दी गई वैल्यू को इंटरपोलेट करेगा. पिछले सेक्शन के आखिर में मौजूद वर्टिक्स शेडर का इस्तेमाल करके, हमें टेक्सचर कोऑर्डिनेट में पास करने के लिए एक एट्रिब्यूट जोड़ना होगा. इसके बाद, उन्हें फ़्रैगमेंट शेडर पर पास करना होगा.

attribute vec2 a_texCoord;
...
varying vec2 v_texCoord;

void main() {
   ...
   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points
   v_texCoord = a_texCoord;
}

इसके बाद, हम टेक्सचर से रंग देखने के लिए फ़्रैगमेंट शेडर देते हैं.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   // Look up a color from the texture.
   gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>

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

function main() {
  var image = new Image();
  image.src = "http://someimage/on/our/server";  // MUST BE SAME DOMAIN!!!
  image.onload = function() {
    render(image);
  }
}

function render(image) {
  ...
  // all the code we had before.
  ...
  // look up where the texture coordinates need to go.
  var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

  // provide texture coordinates for the rectangle.
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
      0.0,  0.0,
      1.0,  0.0,
      0.0,  1.0,
      0.0,  1.0,
      1.0,  0.0,
      1.0,  1.0]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(texCoordLocation);
  gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we can render any size image.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  ...
}

यह बहुत दिलचस्प नहीं है. इसलिए, आइए इस इमेज में बदलाव करें. लाल और नीले रंग को आपस में बदलने का क्या?

...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...

अगर हमें ऐसी इमेज प्रोसेसिंग करनी है जो दूसरे पिक्सल को देखती है, तो क्या होगा? WebGL, टेक्सचर को टेक्सचर कोऑर्डिनेट में रेफ़र करता है, जो 0.0 से 1.0 तक होते हैं. इसलिए, हम आसान गणित onePixel = 1.0 / textureSize की मदद से यह हिसाब लगा सकते हैं कि एक पिक्सल के लिए कितना आगे बढ़ना है. यहां एक फ़्रेगमेंट शेडर दिया गया है, जो टेक्सचर में मौजूद हर पिक्सल के बाएं और दाएं पिक्सल का औसत निकालता है.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   // compute 1 pixel in texture coordinates.
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;

   // average the left, middle, and right pixels.
   gl_FragColor = (
       texture2D(u_image, v_texCoord) +
       texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
       texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>

इसके बाद, हमें JavaScript से टेक्सचर का साइज़ पास करना होगा.

...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...

अब हम दूसरे पिक्सल का रेफ़रंस देने का तरीका जानते हैं. अब आइए, इमेज प्रोसेसिंग से जुड़े कई सामान्य काम करने के लिए, कन्वोल्यूशन कर्नेल का इस्तेमाल करें. इस मामले में, हम 3x3 के kernel का इस्तेमाल करेंगे. कन्वोल्यूशन कर्नेल सिर्फ़ 3x3 मैट्रिक होता है. इसमें मैट्रिक की हर एंट्री से पता चलता है कि रेंडर किए जा रहे पिक्सल के आस-पास मौजूद आठ पिक्सल को कितना गुणा करना है. इसके बाद, हम नतीजे को कर्नेल के वेट (कर्नेल में सभी वैल्यू का योग) या 1.0 में से जो भी ज़्यादा हो उससे भाग देते हैं. इस बारे में एक अच्छा लेख यहां दिया गया है. अगर आपको C++ में यह कोड खुद लिखना है, तो यहां एक और लेख दिया गया है. हमारे मामले में, हम शेडर में यह काम करेंगे. इसलिए, यहां नया फ़्रैगमेंट शेडर दिया गया है.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
   vec4 colorSum =
     texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
     texture2D(u_image, v_texCoord + onePixel * vec2(-1,  0)) * u_kernel[3] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 0,  0)) * u_kernel[4] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 1,  0)) * u_kernel[5] +
     texture2D(u_image, v_texCoord + onePixel * vec2(-1,  1)) * u_kernel[6] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 0,  1)) * u_kernel[7] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 1,  1)) * u_kernel[8] ;
   float kernelWeight =
     u_kernel[0] +
     u_kernel[1] +
     u_kernel[2] +
     u_kernel[3] +
     u_kernel[4] +
     u_kernel[5] +
     u_kernel[6] +
     u_kernel[7] +
     u_kernel[8] ;

   if (kernelWeight <= 0.0) {
     kernelWeight = 1.0;
   }

   // Divide the sum by the weight but just use rgb
   // we'll set alpha to 1.0
   gl_FragColor = vec4((colorSum / kernelWeight).rgb, 1.0);
}
</script>

JavaScript में, हमें कन्वोल्यूशन कर्नेल की वैल्यू देनी होगी.

...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
...
var edgeDetectKernel = [
    -1, -1, -1,
    -1,  8, -1,
    -1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
...

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

GLSL में वैरिएबल के तौर पर a, u, और v_ प्रीफ़िक्स का क्या मतलब है?

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

एक से ज़्यादा इफ़ेक्ट लागू करना

इमेज प्रोसेसिंग के लिए अगला सबसे आसान सवाल यह है कि एक से ज़्यादा इफ़ेक्ट कैसे लागू करें?

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

Original Image -> [Blur]        -> Texture 1
Texture 1      -> [Sharpen]     -> Texture 2
Texture 2      -> [Edge Detect] -> Texture 1
Texture 1      -> [Blur]        -> Texture 2
Texture 2      -> [Normal]      -> Canvas

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

function createAndSetupTexture(gl) {
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set up texture so we can render any size image and so we are
  // working with pixels.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  return texture;
}

// Create a texture and put the image in it.
var originalImageTexture = createAndSetupTexture(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

अब दो और टेक्सचर बनाने और उन्हें दो फ़्रेमबफ़र से जोड़ने के लिए, उस फ़ंक्शन का इस्तेमाल करें.

// create 2 textures and attach them to framebuffers.
var textures = [];
var framebuffers = [];
for (var ii = 0; ii < 2; ++ii) {
  var texture = createAndSetupTexture(gl);
  textures.push(texture);

  // make the texture the same size as the image
  gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
      gl.RGBA, gl.UNSIGNED_BYTE, null);

  // Create a framebuffer
  var fbo = gl.createFramebuffer();
  framebuffers.push(fbo);
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

  // Attach a texture to it.
  gl.framebufferTexture2D(
      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}

अब आइए, कर्नेल का एक सेट बनाएं और फिर उन्हें लागू करने के लिए सूची बनाएं.

// Define several convolution kernels
var kernels = {
  normal: [
    0, 0, 0,
    0, 1, 0,
    0, 0, 0
  ],
  gaussianBlur: [
    0.045, 0.122, 0.045,
    0.122, 0.332, 0.122,
    0.045, 0.122, 0.045
  ],
  unsharpen: [
    -1, -1, -1,
    -1,  9, -1,
    -1, -1, -1
  ],
  emboss: [
     -2, -1,  0,
     -1,  1,  1,
      0,  1,  2
  ]
};

// List of effects to apply.
var effectsToApply = [
  "gaussianBlur",
  "emboss",
  "gaussianBlur",
  "unsharpen"
];

आखिर में, हर एक को लागू करते हैं और यह भी देखते हैं कि किस टेक्सचर को रेंडर किया जा रहा है

// start with the original image
gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);

// don't y flip images while drawing to the textures
gl.uniform1f(flipYLocation, 1);

// loop through each effect we want to apply.
for (var ii = 0; ii < effectsToApply.length; ++ii) {
  // Setup to draw into one of the framebuffers.
  setFramebuffer(framebuffers[ii % 2], image.width, image.height);

  drawWithKernel(effectsToApply[ii]);

  // for the next draw, use the texture we just rendered to.
  gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
}

// finally draw the result to the canvas.
gl.uniform1f(flipYLocation, -1);  // need to y flip for canvas
setFramebuffer(null, canvas.width, canvas.height);
drawWithKernel("normal");

function setFramebuffer(fbo, width, height) {
  // make this the framebuffer we are rendering to.
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

  // Tell the shader the resolution of the framebuffer.
  gl.uniform2f(resolutionLocation, width, height);

  // Tell webgl the viewport setting needed for framebuffer.
  gl.viewport(0, 0, width, height);
}

function drawWithKernel(name) {
  // set the kernel
  gl.uniform1fv(kernelLocation, kernels[name]);

  // Draw the rectangle.
  gl.drawArrays(gl.TRIANGLES, 0, 6);
}

मुझे कुछ बातें बतानी हैं.

null के साथ gl.bindFramebuffer को कॉल करने से, WebGL को पता चलता है कि आपको अपने किसी फ़्रेमबफ़र के बजाय कैनवस पर रेंडर करना है. WebGL को क्लिपस्पेस से वापस पिक्सल में बदलना होगा. यह gl.viewport की सेटिंग के आधार पर ऐसा करता है. WebGL को शुरू करने पर, gl.viewport की सेटिंग डिफ़ॉल्ट रूप से कैनवस के साइज़ पर सेट हो जाती हैं. हम जिन फ़्रेमबफ़र में रेंडर कर रहे हैं उनका साइज़, कैनवस से अलग है. इसलिए, हमें व्यूपोर्ट को सही तरीके से सेट करना होगा. आखिर में, WebGL के बुनियादी उदाहरणों में, हमने रेंडर करते समय Y निर्देशांक को फ़्लिप किया है. ऐसा इसलिए किया गया है, क्योंकि WebGL कैनवस को 0,0 के साथ सबसे नीचे बाएं कोने में दिखाता है, जबकि 2D के लिए सबसे ऊपर बाएं कोने में दिखाया जाता है. फ़्रेमबफ़र में रेंडर करते समय, ऐसा करने की ज़रूरत नहीं होती. फ़्रेमबफ़र कभी नहीं दिखाया जाता, इसलिए यह मायने नहीं रखता कि कौनसा हिस्सा ऊपर है और कौनसा नीचे. फ़्रेमबफ़र में पिक्सल 0,0 का मतलब, हमारी गिनती में 0,0 से है. इस समस्या को हल करने के लिए, मैंने शेडर में एक और इनपुट जोड़कर, फ़्लिप करने या न करने की सुविधा जोड़ी है.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_flipY;
...

void main() {
   ...
   gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
   ...
}
</script>

इसके बाद, हम इसे तब सेट कर सकते हैं, जब

...
var flipYLocation = gl.getUniformLocation(program, "u_flipY");
...
// don't flip
gl.uniform1f(flipYLocation, 1);
...
// flip
gl.uniform1f(flipYLocation, -1);

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

WebGL और ऐल्फ़ा

मुझे पता चला है कि कुछ OpenGL डेवलपर को, बैकबफ़र (यानी कैनवस) में WebGL के अल्फा के इस्तेमाल से जुड़ी समस्याएं आ रही हैं. इसलिए, मैंने सोचा कि अल्फा से जुड़े WebGL और OpenGL के बीच के कुछ अंतरों के बारे में बताना अच्छा होगा.

OpenGL और WebGL के बीच का सबसे बड़ा अंतर यह है कि OpenGL, किसी ऐसे बैकबफ़र में रेंडर करता है जिसे किसी भी चीज़ के साथ कॉम्पोज़ नहीं किया जाता है. इसके अलावा, ओएस के विंडो मैनेजर के ज़रिए भी इसे किसी भी चीज़ के साथ कॉम्पोज़ नहीं किया जाता है. इसलिए, आपके अल्फा का कोई फ़र्क़ नहीं पड़ता. WebGL को ब्राउज़र, वेब पेज के साथ कॉम्पोज़ करता है. डिफ़ॉल्ट रूप से, ट्रांसपेरेंसी और 2D कैनवस टैग वाले .png <img> टैग की तरह ही, पहले से मल्टीप्लाई किए गए अल्फा का इस्तेमाल किया जाता है. WebGL को OpenGL जैसा बनाने के कई तरीके हैं.

#1) WebGL को बताएं कि आपको इसे बिना प्रीमल्टीप्लाइड किए गए अल्फा के साथ कंपोज करना है

gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});

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

<canvas style="background: red;"></canvas>

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

#2) WebGL को बताएं कि आपको बैकबफ़र में अल्फा नहीं चाहिए

gl = canvas.getContext("experimental-webgl", {alpha: false});

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

#3) रेंडरिंग के आखिर में अल्फा को हटाना

..
renderScene();
..
// Set the backbuffer's alpha to 1.0
gl.clearColor(1, 1, 1, 1);
gl.colorMask(false, false, false, true);
gl.clear(gl.COLOR_BUFFER_BIT);

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

#4) ऐल्फ़ा को एक बार मिटाएं और फिर उसे रेंडर न करें

// At init time. Clear the back buffer.
gl.clearColor(1,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT);

// Turn off rendering to alpha
gl.colorMask(true, true, true, false);

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

#5) इमेज मैनेज करना

साथ ही, अगर अल्फा वाले PNG फ़ाइलों को टेक्सचर में लोड किया जा रहा है, तो डिफ़ॉल्ट रूप से उनका अल्फा पहले से मल्टीप्लाई होता है. आम तौर पर, ज़्यादातर गेम में ऐसा नहीं होता. अगर आपको इस तरह के व्यवहार को रोकना है, तो आपको WebGL को

gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);

#6) पहले से मल्टीप्लाई किए गए अल्फा के साथ काम करने वाले ब्लेंडिंग समीकरण का इस्तेमाल करना

मैंने जिन OpenGL ऐप्लिकेशन को लिखा है या जिन पर काम किया है उनमें से ज़्यादातर में

gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);

यह उन अल्फा टेक्सचर के लिए काम करता है जिन्हें पहले से मल्टीप्लाई नहीं किया गया है. अगर आपको पहले से मल्टीप्लाई किए गए अल्फा टेक्सचर के साथ काम करना है, तो आपको

gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);

मुझे इन तरीकों के बारे में पता है. अगर आपको और जानकारी है, तो कृपया यहां पोस्ट करें.