ওয়েবজিএল মৌলিক বিষয়

গ্রেগ টাভারেস
Gregg Tavares

WebGL আপনার ব্রাউজারে আশ্চর্যজনক রিয়েলটাইম 3D গ্রাফিক্স প্রদর্শন করা সম্ভব করে কিন্তু অনেকেই জানেন না যে WebGL আসলে একটি 2D API, একটি 3D API নয়। আমাকে ব্যাখ্যা করা যাক.

WebGL শুধুমাত্র 2টি জিনিসের যত্ন নেয়। ক্লিপস্পেস 2D এবং রঙে স্থানাঙ্ক। WebGL ব্যবহার করে একজন প্রোগ্রামার হিসাবে আপনার কাজ হল WebGL কে সেই 2টি জিনিস প্রদান করা। আপনি এটি করতে 2 "শেডার" প্রদান করেন। একটি ভার্টেক্স শেডার যা ক্লিপস্পেস স্থানাঙ্ক প্রদান করে এবং একটি ফ্র্যাগমেন্ট শেডার যা রঙ প্রদান করে। আপনার ক্যানভাসের আকার যাই হোক না কেন ক্লিপস্পেস স্থানাঙ্ক সর্বদা -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 shaders

<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 API! 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 গ্রাফিক্স API-এর জন্য ব্যবহৃত আরও ঐতিহ্যবাহী উপরের বাম কোণে পরিণত করতে আমরা শুধু 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 আসলে একটি সুন্দর সহজ API। যদিও এটি 3D করতে আরও জটিল হতে পারে যে জটিলতা আপনি, প্রোগ্রামার, আরও জটিল শেডারের আকারে যোগ করেছেন। WebGL API নিজেই 2D এবং মোটামুটি সহজ।

type="x-shader/x-vertex" এবং type="x-shader/x-fragment" মানে কি?

<script> ট্যাগগুলি জাভাস্ক্রিপ্ট থাকার জন্য ডিফল্ট। আপনি কোন প্রকার দিতে পারেন না বা আপনি type="javascript" বা type="text/javascript" রাখতে পারেন এবং ব্রাউজারটি জাভাস্ক্রিপ্ট হিসাবে বিষয়বস্তুকে ব্যাখ্যা করবে। আপনি অন্য কিছু রাখলে ব্রাউজার স্ক্রিপ্ট ট্যাগের বিষয়বস্তু উপেক্ষা করে।

আমরা স্ক্রিপ্ট ট্যাগে shaders সংরক্ষণ করতে এই বৈশিষ্ট্য ব্যবহার করতে পারেন. আরও ভাল, আমরা আমাদের নিজস্ব টাইপ তৈরি করতে পারি এবং আমাদের জাভাস্ক্রিপ্টে এটি সন্ধান করতে পারি যে শেডারটিকে ভার্টেক্স শেডার বা ফ্র্যাগমেন্ট শেডার হিসাবে কম্পাইল করা হবে কিনা।

এই ক্ষেত্রে createShaderFromScriptElement ফাংশনটি নির্দিষ্ট id সহ একটি স্ক্রিপ্ট খোঁজে এবং তারপর type দেখে সিদ্ধান্ত নেয় কোন ধরনের শেডার তৈরি করতে হবে।

ওয়েবজিএল ইমেজ প্রসেসিং

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>

তারপর আমাদের জাভাস্ক্রিপ্ট থেকে টেক্সচারের আকারে পাস করতে হবে।

...
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>

জাভাস্ক্রিপ্টে আমাদের একটি কনভোলিউশন কার্নেল সরবরাহ করতে হবে।

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

আমি আশা করি এটি আপনাকে বিশ্বাস করেছে যে WebGL-এ চিত্র প্রক্রিয়াকরণ বেশ সহজ। পরবর্তীতে আমি চিত্রটিতে একাধিক প্রভাব কীভাবে প্রয়োগ করতে হয় তা নিয়ে যাব।

জিএলএসএল-এর ভেরিয়েবলের মধ্যে a , u , এবং v_ উপসর্গের সাথে কী আছে?

এটি কেবল একটি নামকরণের রীতি। a_ বৈশিষ্ট্যগুলির জন্য যা বাফার দ্বারা প্রদত্ত ডেটা। u_ ইউনিফর্মের জন্য যা শেডারের জন্য ইনপুট, v_ বিভিন্নতার জন্য যা একটি ভার্টেক্স শেডার থেকে একটি ফ্র্যাগমেন্ট শেডারে স্থানান্তরিত এবং প্রতিটি পিক্সেলের জন্য শীর্ষবিন্দুর মধ্যে ইন্টারপোলেটেড (বা বৈচিত্র্যময়)।

একাধিক প্রভাব প্রয়োগ

ইমেজ প্রক্রিয়াকরণের জন্য পরবর্তী সবচেয়ে সুস্পষ্ট প্রশ্ন হল কিভাবে একাধিক প্রভাব প্রয়োগ করবেন?

ওয়েল, আপনি উড়তে shaders তৈরি করার চেষ্টা করতে পারেন. একটি UI প্রদান করুন যা ব্যবহারকারীকে সে যে প্রভাবগুলি ব্যবহার করতে চায় তা নির্বাচন করতে দেয় তারপর একটি শেডার তৈরি করে যা সমস্ত প্রভাবগুলি করে৷ এটি সবসময় সম্ভব নাও হতে পারে যদিও সেই কৌশলটি প্রায়ই রিয়েল টাইম গ্রাফিক্সের জন্য প্রভাব তৈরি করতে ব্যবহৃত হয়। একটি আরও নমনীয় উপায় হল আরও 2টি টেক্সচার ব্যবহার করা এবং প্রতিটি টেক্সচারে পালাক্রমে রেন্ডার করা, সামনে পিছনে পিং পিং করা এবং প্রতিবার পরবর্তী প্রভাব প্রয়োগ করা।

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-এ, একটি ফ্রেমবাফার আসলে একটি খারাপ নাম। একটি ওয়েবজিএল/ওপেনজিএল ফ্রেমবাফার আসলেই রাষ্ট্রের একটি সংগ্রহ এবং আসলে কোনো ধরনের বাফার নয়। কিন্তু, একটি ফ্রেমবাফারে একটি টেক্সচার সংযুক্ত করে আমরা সেই টেক্সচারে রেন্ডার করতে পারি। প্রথমে পুরানো টেক্সচার তৈরির কোডটিকে একটি ফাংশনে পরিণত করা যাক

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

এবং এখন আসুন সেই ফাংশনটি ব্যবহার করে আরও 2টি টেক্সচার তৈরি করি এবং সেগুলিকে 2টি ফ্রেমবাফারের সাথে সংযুক্ত করি।

// 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 আলফাকে ব্যবহার করে তা নিয়ে সমস্যা রয়েছে, তাই আমি ভেবেছিলাম আলফার সাথে সম্পর্কিত WebGL এবং OpenGL এর মধ্যে কিছু পার্থক্য করা ভাল হতে পারে।

OpenGL এবং WebGL-এর মধ্যে সবচেয়ে বড় পার্থক্য হল OpenGL একটি ব্যাকবাফারে রেন্ডার করে যা কিছুর সাথে সংমিশ্রিত হয় না, বা কার্যকরভাবে OS এর উইন্ডো ম্যানেজার দ্বারা কোন কিছুর সাথে সংমিশ্রিত হয় না, তাই আপনার আলফা কী তা বিবেচ্য নয়। WebGL ব্রাউজার দ্বারা ওয়েব পৃষ্ঠার সাথে সংমিশ্রিত হয় এবং ডিফল্ট হল .png <img> ট্যাগ এবং 2d ক্যানভাস ট্যাগগুলির মতো প্রাক-গুনিত আলফা ব্যবহার করা। এটিকে ওপেনজিএল-এর মতো করে তোলার জন্য ওয়েবজিএল-এর বিভিন্ন উপায় রয়েছে।

#1) WebGL কে বলুন যে আপনি এটিকে অ-প্রিম-মাল্টিপ্লাইড আলফা দিয়ে সংমিশ্রিত করতে চান৷

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

ডিফল্ট সত্য. অবশ্যই ফলাফলটি এখনও পৃষ্ঠার উপরে কম্পোজ করা হবে যেটি ক্যানভাসের নীচে থাকা পটভূমির রঙের সাথে শেষ হবে (ক্যানভাসের পটভূমির রঙ, ক্যানভাসের ধারকটির পটভূমির রঙ, পৃষ্ঠার পটভূমির রঙ, ক্যানভাসের পিছনে থাকা জিনিসগুলি যদি ক্যানভাসের একটি z-সূচক থাকে) > 0, ইত্যাদি...) অন্য কথায়, রঙ CSS ওয়েবপৃষ্ঠার সেই এলাকার জন্য সংজ্ঞায়িত করে। আপনার কোন আলফা সমস্যা আছে কিনা তা খুঁজে বের করার জন্য আমি সত্যিই ভাল উপায় হল ক্যানভাসের ব্যাকগ্রাউন্ডকে লালের মতো উজ্জ্বল রঙে সেট করা। আপনি অবিলম্বে কি ঘটছে দেখতে পাবেন.

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

আপনি এটিকে কালোতেও সেট করতে পারেন যা আপনার আলফা সমস্যাগুলি লুকিয়ে রাখবে।

#2) WebGL কে বলুন আপনি ব্যাকবাফারে আলফা চান না

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

এটি এটিকে ওপেনজিএল-এর মতো কাজ করবে কারণ ব্যাকবাফারে শুধুমাত্র আরজিবি থাকবে। এটি সম্ভবত সেরা বিকল্প কারণ একটি ভাল ব্রাউজার দেখতে পারে যে আপনার কোন আলফা নেই এবং আসলে 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);

যে পদ্ধতি আমি সচেতন নই. আপনি যদি আরো জানেন নিচে তাদের পোস্ট করুন.