WebGL की मदद से, आपके ब्राउज़र में रीयलटाइम में शानदार 3D ग्राफ़िक दिखाए जा सकते हैं. हालांकि, कई लोगों को यह नहीं पता है कि WebGL असल में 2D एपीआई है, न कि 3D एपीआई. मैं आपको इसके बारे में बताता हूं.
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 कर्नल का इस्तेमाल करेंगे. कॉन्वोल्यूशन कर्नेल, 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});
डिफ़ॉल्ट रूप से, यह true पर सेट होता है. नतीजा अब भी पेज पर कंपोज़िट किया जाएगा. इसके लिए, कैनवस के नीचे मौजूद बैकग्राउंड के रंग का इस्तेमाल किया जाएगा. जैसे, कैनवस के बैकग्राउंड का रंग, कैनवस के कंटेनर के बैकग्राउंड का रंग, पेज के बैकग्राउंड का रंग, कैनवस के पीछे मौजूद चीज़ें, अगर कैनवस का z-इंडेक्स > 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);
मुझे इन तरीकों के बारे में पता है. अगर आपको और भी तरीके पता हैं, तो उन्हें यहां पोस्ट करें.