WebGL ऑर्थोग्राफ़िक 3D
यह पोस्ट, WebGL के बारे में पोस्ट की सीरीज़ का हिस्सा है. पहले लेख में बुनियादी बातों से शुरू किया गया था और पिछले लेख में 2D मैट्रिक्स के बारे में बताया गया था. अगर आपने उन्हें नहीं पढ़ा है, तो कृपया पहले उन्हें पढ़ें. पिछली पोस्ट में, हमने बताया था कि 2D मैट्रिक कैसे काम करती हैं. हमने ट्रांसलेशन, रोटेशन, स्केलिंग के बारे में बात की. साथ ही, पिक्सल से क्लिप स्पेस में प्रोजेक्ट करने के बारे में भी बताया. ये सभी काम, एक मैट्रिक और मैट्रिक के कुछ जादुई गणित से किए जा सकते हैं. इसके बाद, 3D में वीडियो बनाने में काफ़ी आसानी होती है. हमारे पिछले 2D उदाहरणों में, हमारे पास 2D पॉइंट (x, y) थे, जिन्हें हमने 3x3 मैट्रिक्स से गुणा किया था. 3D इमेज बनाने के लिए, हमें 3D पॉइंट (x, y, z) और 4x4 मैट्रिक्स की ज़रूरत होती है. आखिरी उदाहरण को 3D में बदलते हैं. हम फिर से F का इस्तेमाल करेंगे, लेकिन इस बार 3D 'F' का. सबसे पहले, हमें वर्टिक्स शेडर को 3D में बदलना होगा. यहां पुराना शेडर है.
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform mat3 u_matrix;
void main() {
// Multiply the position by the matrix.
gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>
और यह नया वर्शन है
<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
uniform mat4 u_matrix;
void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
}
</script>
यह और भी आसान हो गया है! इसके बाद, हमें 3D डेटा देना होगा.
...
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
...
// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// left column
0, 0, 0,
30, 0, 0,
0, 150, 0,
0, 150, 0,
30, 0, 0,
30, 150, 0,
// top rung
30, 0, 0,
100, 0, 0,
30, 30, 0,
30, 30, 0,
100, 0, 0,
100, 30, 0,
// middle rung
30, 60, 0,
67, 60, 0,
30, 90, 0,
30, 90, 0,
67, 60, 0,
67, 90, 0]),
gl.STATIC_DRAW);
}
इसके बाद, हमें सभी मैट्रिक फ़ंक्शन को 2D से 3D में बदलना होगा यहां makeTranslation, makeRotation, और makeScale के 2D (पहले) वर्शन दिए गए हैं
function makeTranslation(tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1
];
}
function makeRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c,-s, 0,
s, c, 0,
0, 0, 1
];
}
function makeScale(sx, sy) {
return [
sx, 0, 0,
0, sy, 0,
0, 0, 1
];
}
यहां अपडेट किए गए 3D वर्शन दिए गए हैं.
function makeTranslation(tx, ty, tz) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1
];
}
function makeXRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
];
};
function makeYRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
];
};
function makeZRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
];
}
function makeScale(sx, sy, sz) {
return [
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 0,
0, 0, 0, 1,
];
}
ध्यान दें कि अब हमारे पास तीन रोटेशन फ़ंक्शन हैं. हमें सिर्फ़ 2D में एक इमेज की ज़रूरत थी, क्योंकि हम सिर्फ़ Z ऐक्सिस के आस-पास घुमाने की कोशिश कर रहे थे. हालांकि, 3D में हम x-ऐक्सिस और y-ऐक्सिस के आस-पास भी घुमाना चाहते हैं. इनमें से हर वीडियो एक-दूसरे से काफ़ी मिलता-जुलता है. अगर हम इन समस्याओं को हल कर पाते हैं, तो आपको पहले की तरह ही आसानी से ये सुविधाएं मिलेंगी
Z रोटेशन
newX = x * c + y * s;
newY = x * -s + y * c;
Y रोटेशन
newX = x * c + z * s;
newZ = x * -s + z * c;
X रोटेशन
newY = y * c + z * s;
newZ = y * -s + z * c;
हमें प्रोजेक्शन फ़ंक्शन को भी अपडेट करना होगा. यहां पुराना लिंक दिया गया है
function make2DProjection(width, height) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
2 / width, 0, 0,
0, -2 / height, 0,
-1, 1, 1
];
}
पिक्सल से क्लिप के स्टोरेज में बदला जाता है. इसे 3D में दिखाने के लिए, आइए
function make2DProjection(width, height, depth) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 2 / depth, 0,
-1, 1, 0, 1,
];
}
ठीक उसी तरह जैसे हमें x और y के लिए पिक्सल से क्लिपस्पेस में बदलना पड़ा था, वैसे ही हमें z के लिए भी यही करना होगा. इस मामले में, मैं Z स्पेस पिक्सल यूनिट भी बना रहा हूं. मैं डेप्थ के लिए width
जैसी कोई वैल्यू पास करूंगा, ताकि हमारा स्पेस 0 से चौड़ाई पिक्सल तक, 0 से ऊंचाई पिक्सल तक हो, लेकिन डेप्थ के लिए यह -डेप्थ / 2 से +डेप्थ / 2 तक होगा.
आखिर में, हमें मैट्रिक का हिसाब लगाने वाले कोड को अपडेट करना होगा.
// Compute the matrices
var projectionMatrix =
make2DProjection(canvas.width, canvas.height, canvas.width);
var translationMatrix =
makeTranslation(translation[0], translation[1], translation[2]);
var rotationXMatrix = makeXRotation(rotation[0]);
var rotationYMatrix = makeYRotation(rotation[1]);
var rotationZMatrix = makeZRotation(rotation[2]);
var scaleMatrix = makeScale(scale[0], scale[1], scale[2]);
// Multiply the matrices.
var matrix = matrixMultiply(scaleMatrix, rotationZMatrix);
matrix = matrixMultiply(matrix, rotationYMatrix);
matrix = matrixMultiply(matrix, rotationXMatrix);
matrix = matrixMultiply(matrix, translationMatrix);
matrix = matrixMultiply(matrix, projectionMatrix);
// Set the matrix.
gl.uniformMatrix4fv(matrixLocation, false, matrix);
पहली समस्या यह है कि हमारी ज्यामिति एक फ़्लैट F है, जिससे किसी भी 3D को देखना मुश्किल हो जाता है. इसे ठीक करने के लिए, ज्यामिति को 3D में बड़ा करें. हमारा मौजूदा F, तीन रेक्टैंगल और दो त्रिकोणों से बना है. इसे 3D में बदलने के लिए, कुल 16 रेक्टैंगल की ज़रूरत होगी. यहां इन सभी के बारे में बताना मुश्किल है. 16 रेक्टैंगल x हर रेक्टैंगल में दो त्रिकोण x हर त्रिकोण में तीन वर्टिसेस = 96 वर्टिसेस. अगर आपको सभी को देखना है, तो सैंपल पर सोर्स देखें. हमें ज़्यादा वर्टिसेस बनाने होंगे, ताकि
// Draw the geometry.
gl.drawArrays(gl.TRIANGLES, 0, 16 * 6);
स्लाइडर को मूव करने पर, यह पता लगाना मुश्किल है कि यह 3D है. आइए, हर रेक्टैंगल को अलग-अलग रंग में रंगने की कोशिश करते हैं. इसके लिए, हम अपने वर्टिक्स शेडर में एक और एट्रिब्यूट जोड़ेंगे. साथ ही, वर्टिक्स शेडर से फ़्रैगमेंट शेडर में पास करने के लिए, वैरिएशन जोड़ेंगे. नया वर्टिक्स शेडर
<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;
uniform mat4 u_matrix;
varying vec4 v_color;
void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
// Pass the color to the fragment shader.
v_color = a_color;
}
</script>
और हमें उस रंग का इस्तेमाल फ़्रैगमेंट शेडर में करना होगा
<script id="3d-vertex-shader" type="x-shader/x-fragment">
precision mediump float;
// Passed in from the vertex shader.
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
</script>
रंगों की जानकारी देने के लिए, हमें जगह की जानकारी देखनी होगी. इसके बाद, रंगों की जानकारी देने के लिए, एक और बफ़र और एट्रिब्यूट सेट अप करना होगा.
...
var colorLocation = gl.getAttribLocation(program, "a_color");
...
// Create a buffer for colors.
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(colorLocation);
// We'll supply RGB as bytes.
gl.vertexAttribPointer(colorLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);
// Set Colors.
setColors(gl);
...
// Fill the buffer with colors for the 'F'.
function setColors(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Uint8Array([
// left column front
200, 70, 120,
200, 70, 120,
200, 70, 120,
200, 70, 120,
200, 70, 120,
200, 70, 120,
// top rung front
200, 70, 120,
200, 70, 120,
...
...
gl.STATIC_DRAW);
}
ओह, यह क्या गड़बड़ी है? ऐसा होता है कि 3D 'F' के सभी हिस्से, जैसे कि सामने, पीछे, किनारे वगैरह उसी क्रम में खींचे जाते हैं जिस क्रम में वे हमारी ज्यामिति में दिखते हैं. इससे हमें मनमुताबिक नतीजे नहीं मिलते, क्योंकि कभी-कभी पीछे के आइटम, आगे के आइटम के बाद दिखते हैं. WebGL में त्रिभुजों के लिए, सामने और पीछे के हिस्से का कॉन्सेप्ट होता है. सामने वाले ट्रैंगल के वर्टिसेस, घड़ी की दिशा में चलते हैं. पीछे की ओर मुंह किए हुए त्रिभुज के कोने, घड़ी की उल्टी दिशा में होते हैं.
WebGL में, सिर्फ़ सामने या पीछे वाले ट्राएंगल को ड्रॉ किया जा सकता है. हम इस सुविधा को चालू कर सकते हैं
gl.enable(gl.CULL_FACE);
हम ऐसा सिर्फ़ एक बार, अपने प्रोग्राम की शुरुआत में करते हैं. इस सुविधा के चालू होने पर, WebGL डिफ़ॉल्ट रूप से पीछे की ओर वाले त्रिभुजों को "क्यूलिंग" करता है. इस मामले में, "न खींचना" को "काटना" कहा जाता है. ध्यान दें कि WebGL के हिसाब से, किसी त्रिभुज को घड़ी की सुई के घूमने की दिशा में या उसके उलट दिशा में घूमने वाला माना जाएगा या नहीं, यह क्लिपस्पेस में उस त्रिभुज के वर्टिसेस पर निर्भर करता है. दूसरे शब्दों में, वेबजीएल यह तय करता है कि कोई त्रिकोण आगे है या पीछे, इसके बाद ही आपने वर्टिक्स शेडर में वर्टिक्स पर मैथ लागू किया है. इसका मतलब है कि उदाहरण के लिए, घड़ी की सुई की दिशा में घूमने वाला ऐसा त्रिभुज जिसे X में -1 से स्केल किया गया है वह घड़ी की सुई की दिशा में घूमने वाला त्रिभुज बन जाता है. इसके अलावा, घड़ी की सुई की दिशा में घूमने वाला ऐसा त्रिभुज जिसे X या Y ऐक्सिस के चारों ओर 180 डिग्री घुमाया गया है वह घड़ी की सुई की दिशा में घूमने वाला त्रिभुज बन जाता है. हमने CULL_FACE को बंद किया हुआ था, इसलिए हमें घड़ी की सुई के घूमने की दिशा में(सामने) और घड़ी की सुई के घूमने की दिशा के उलट(पीछे) दोनों तरह के ट्राएंगल दिख रहे हैं. अब हमने इसे चालू कर दिया है. इसलिए, जब भी कोई सामने वाला त्रिकोण स्केलिंग या रोटेशन या किसी भी वजह से फ़्लिप होगा, तो WebGL उसे नहीं दिखाएगा. यह एक अच्छी बात है, क्योंकि 3D में किसी चीज़ को घुमाने पर, आम तौर पर आपको यह तय करना होता है कि सामने वाले ट्राएंगल को सामने वाला माना जाए.
नमस्ते! सभी ट्राएंगल कहां चले गए? ऐसा पता चला है कि इनमें से कई आइकॉन, गलत दिशा में हैं. इसे घुमाएं और दूसरी तरफ़ देखने पर, आपको ये दिखेंगे. अच्छी बात यह है कि इसे ठीक करना आसान है. हम सिर्फ़ यह देखते हैं कि कौनसे बिंदु पीछे हैं और उनके दो वर्टिसेस को आपस में बदल देते हैं. उदाहरण के लिए, अगर एक बैकवर्ड ट्राएंगल
1, 2, 3,
40, 50, 60,
700, 800, 900,
हम इसे आगे की ओर करने के लिए, आखिरी दो वर्टिसेस को फ़्लिप कर देते हैं.
1, 2, 3,
700, 800, 900,
40, 50, 60,
यह सही है, लेकिन अब भी एक समस्या है. सभी त्रिभुज सही दिशा में होने और पीछे वाले त्रिभुजों को हटाने के बावजूद, अब भी ऐसी जगहें हैं जहां पीछे वाले त्रिभुज, सामने वाले त्रिभुजों के ऊपर खींचे जा रहे हैं.
DEPTH BUFFER डालें.
डेप्थ बफ़र को कभी-कभी Z-बफ़र भी कहा जाता है. यह depth
पिक्सल का एक रेक्टैंगल होता है. इमेज बनाने के लिए, हर कलर पिक्सल के लिए एक डेप्थ पिक्सल का इस्तेमाल किया जाता है. WebGL, हर कलर पिक्सल के साथ-साथ डेप्थ पिक्सल भी बना सकता है. यह ऐसा Z के लिए वर्टिक्स शेडर से मिलने वाली वैल्यू के आधार पर करता है. ठीक उसी तरह जैसे हमें X और Y के लिए क्लिप स्पेस में बदलना पड़ा था, वैसे ही Z को क्लिप स्पेस में बदलना पड़ता है या (-1 से +1). इसके बाद, उस वैल्यू को डेप्थ स्पेस वैल्यू (0 से +1) में बदल दिया जाता है.
WebGL, कलर पिक्सल को ड्रॉ करने से पहले, उससे जुड़े डेप्थ पिक्सल की जांच करेगा. अगर जिस पिक्सल को ड्रॉ किया जा रहा है उसकी डेप्थ वैल्यू, उससे जुड़े डेप्थ पिक्सल की वैल्यू से ज़्यादा है, तो WebGL नया कलर पिक्सल नहीं ड्रॉ करता. अगर ऐसा नहीं होता है, तो यह आपके फ़्रेगमेंट शेडर से मिले रंग के साथ नया कलर पिक्सल और नई डेप्थ वैल्यू के साथ डेप्थ पिक्सल, दोनों को ड्रॉ करता है. इसका मतलब है कि दूसरे पिक्सल के पीछे मौजूद पिक्सल नहीं दिखेंगे.
हम इस सुविधा को उतनी ही आसानी से चालू कर सकते हैं जितनी आसानी से हमने
gl.enable(gl.DEPTH_TEST);
ड्रॉइंग शुरू करने से पहले, हमें डेप्थ बफ़र को फिर से 1.0 पर सेट करना होगा.
// Draw the scene.
function drawScene() {
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
...
अगली पोस्ट में, मैं आपको बताऊंगा कि इसकी पर्सपेक्टिव कैसे बनाई जाती है.
एट्रिब्यूट vec4 है, लेकिन gl.vertexAttribPointer का साइज़ 3 क्यों है
ज़्यादा जानकारी पाने में दिलचस्पी रखने वाले लोगों ने शायद यह देखा हो कि हमने अपने दो एट्रिब्यूट को इस तरह से तय किया है
attribute vec4 a_position;
attribute vec4 a_color;
दोनों 'vec4' हैं, लेकिन जब हम WebGL को बताते हैं कि बफ़र से डेटा कैसे निकाला जाए, तो हमने
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribPointer(colorLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);
इनमें से हर एट्रिब्यूट के लिए '3' का मतलब है कि हर एट्रिब्यूट के लिए सिर्फ़ तीन वैल्यू निकाली जाएं. ऐसा इसलिए होता है, क्योंकि वेबजीएल, वर्टिक्स शेडर में उन वैल्यू के लिए डिफ़ॉल्ट वैल्यू उपलब्ध कराता है जिन्हें आपने नहीं दिया है. डिफ़ॉल्ट तौर पर, ये वैल्यू 0, 0, 0, 1 होती हैं. यहां x = 0, y = 0, z = 0 और w = 1 है. इसलिए, हमें अपने पुराने 2D वर्टिक्स शेडर में, 1 को साफ़ तौर पर डालना पड़ता था. हम x और y को पास कर रहे थे और हमें z के लिए 1 की ज़रूरत थी, लेकिन z के लिए डिफ़ॉल्ट वैल्यू 0 होने की वजह से, हमें साफ़ तौर पर 1 देना पड़ा. हालांकि, 3D के लिए, हम 'w' की वैल्यू नहीं देते. यह डिफ़ॉल्ट रूप से 1 पर सेट होती है. मैट्रिक्स के हिसाब से गणना करने के लिए, हमें इस वैल्यू की ज़रूरत होती है.