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

Gregg Tavares
Gregg Tavares

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

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

WebGL सिर्फ़ दो चीज़ों की परवाह करता है. क्लिपस्पेस, 2D और कलर में कोऑर्डिनेट करता है. WebGL का इस्तेमाल करने वाले प्रोग्रामर के तौर पर, आपका काम WebGL को दो चीज़ें उपलब्ध कराना है. ऐसा करने के लिए, आपको दो "शेडर" देने होते हैं. एक Vertex शेडर, जो क्लिपस्पेस कोऑर्डिनेट और रंग देने वाला फ़्रैगमेंट शेडर देता है. 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);

ये रहे 2 शेडर

<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 IS A 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-VERex" और type="x-shader/x-फ़्रैगमेंट" का क्या मतलब है?

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

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

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

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

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

WebGL में चित्र बनाने के लिए, हमें बनावटों का उपयोग करने की आवश्यकता होती है. उसी तरह, जिस तरह WebGL के बजाय, रेंडरिंग के समय क्लिपस्पेस कोऑर्डिनेट की उम्मीद करता है, WebGL किसी टेक्सचर को पढ़ते समय टेक्सचर कोऑर्डिनेट की उम्मीद करता है. बनावट निर्देशांक 0.0 से 1.0 तक जाते हैं, चाहे बनावट के आयाम कुछ भी हों. चूंकि हम केवल एक आयत (अच्छी तरह से 2 त्रिकोण) बना रहे हैं, इसलिए हमें यह बताना होगा कि आयत का हर बिंदु बनावट में किस स्थान से मेल खाता है. हम इस जानकारी को वर्टेक्स शेडर से फ़्रैगमेंट शेडर में पास करेंगे. इसके लिए, एक खास वैरिएबल का इस्तेमाल किया जाएगा, जिसे 'वैयिंग' कहा जाता है. इसे विंगिंग कहा जाता है, क्योंकि यह अलग-अलग होता है. फ़्रैगमेंट शेडर का इस्तेमाल करके, हर पिक्सल ड्रॉ करता है और 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 की मदद से यह हिसाब लगा सकते हैं कि 1 पिक्सल में कितना हिलना है. यहां एक फ़्रैगमेंट शेडर दिया गया है जो टेक्सचर में हर पिक्सल के बाएं और दाएं पिक्सल का औसत दिखाता है.

<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 में इमेज प्रोसेस करना बहुत आसान है. अब मैं आपको इमेज पर एक से ज़्यादा इफ़ेक्ट लागू करने का तरीका बताऊँगा.

जीएलएसएल में वैरिएबल में से, 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 Framebuffer असल में स्टेट का सिर्फ़ एक संग्रह है और असल में किसी भी तरह का बफ़र नहीं है. लेकिन, फ़्रेमबफ़र में एक टेक्स्चर जोड़कर हम उस टेक्सचर में रेंडर कर सकते हैं. सबसे पहले पुराने टेक्सचर बनाने वाले कोड को फ़ंक्शन में बदलें

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

कुछ चीज़ें, जिनका मुझे इस्तेमाल करना चाहिए.

gl.bindFramebuffer को null से कॉल करने पर, WebGL को यह पता चलता है कि आपको अपने किसी फ़्रेमबफ़र के बजाय, कैनवस पर रेंडर करना है. WebGL को क्लिपस्पेस से वापस पिक्सल में बदलना होगा. यह gl.viewport की सेटिंग के आधार पर ऐसा करती है. जब हम WebGL शुरू करते हैं, तो gl.viewport की सेटिंग कैनवस के आकार पर डिफ़ॉल्ट होती है. हम जिन फ़्रेमबफ़र में रेंडर कर रहे हैं वे अलग साइज़ के हैं. इसलिए, हमें व्यूपोर्ट को सही तरीके से सेट करने के लिए कैनवस की ज़रूरत होती है. आखिर में, WebGL के बुनियादी उदाहरणों में, हमने रेंडर करते समय Y निर्देशांक को फ़्लिप कर दिया, क्योंकि WebGL कैनवस को सबसे नीचे बाएं कोने की तरह दिखाता है, जो 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);

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

WebGL और ऐल्फ़ा

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

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

#1) WebGL को बताएं कि आप इसे गैर-प्रति-गुणित अल्फ़ा के साथ संयुक्त करना चाहते हैं

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

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

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

आप इसे काले रंग पर सेट कर सकते हैं, जिससे आपकी ऐल्फ़ा से जुड़ी कोई भी समस्या छिप जाएगी.

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

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

यह OpenGL की तरह ही काम करेगा, क्योंकि बैकबफ़र में सिर्फ़ आरजीबी होगा. यह शायद सबसे अच्छा विकल्प है, क्योंकि एक अच्छा ब्राउज़र देख सकता है कि आपके पास कोई ऐल्फ़ा वर्शन नहीं है. साथ ही, वह 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);

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