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

Gregg Tavares
Gregg Tavares

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

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

WebGL सिर्फ़ दो चीज़ों पर ध्यान देता है. 2D और रंगों में क्लिपस्पेस के निर्देशांक. WebGL का इस्तेमाल करने वाले प्रोग्रामर के तौर पर, आपको WebGL को ये दो चीज़ें देनी होती हैं. इसके लिए, आपको दो "शेडर" देने होंगे. क्लिपस्पेस कोऑर्डिनेट देने वाला वर्टिक्स शेडर और कलर देने वाला फ़्रैगमेंट शेडर. आपके कैनवस का आकार चाहे जो भी हो, Clipspace निर्देशांक हमेशा -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 के बीच होते हैं. भले ही, टेक्स्चर के डाइमेंशन कुछ भी हों. चूंकि हम केवल एक आयत (अच्छी तरह से, 2 त्रिभुज) बना रहे हैं, इसलिए हमें 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 कर्नेल का इस्तेमाल करेंगे. कॉन्वोल्यूशन कर्नेल सिर्फ़ 3x3 का मैट्रिक्स होता है, जहां मैट्रिक्स की हर एंट्री से पता चलता है कि हम रेंडर किए जा रहे पिक्सल के आस-पास कितने 8 पिक्सल को गुणा करना चाहते हैं. इसके बाद, हम नतीजे को कर्नेल के वेट (कर्नेल में सभी वैल्यू का योग) या 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> टैग की तरह ही, पहले से मल्टीप्लाई किए गए अल्फा का इस्तेमाल किया जाता है. इसे OpenGL की तरह बनाने के लिए WebGL के कई तरीके हैं.

#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);

आम तौर पर, डेटा मिटाने की प्रोसेस बहुत तेज़ होती है, क्योंकि ज़्यादातर हार्डवेयर में इसके लिए एक खास सुविधा होती है. मैंने अपने ज़्यादातर डेमो में ऐसा किया. अगर मैं स्मार्ट होता, तो मुझे ऊपर दिए गए तरीके #2 पर स्विच करना होता. शायद मैं इसे पोस्ट करने के तुरंत बाद ऐसा करूं. ऐसा लगता है कि ज़्यादातर 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);

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