WebGL ऑर्थोग्राफ़िक 3D

Gregg Tavares
Gregg Tavares

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, MakerRotation, और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 2 त्रिभुज x हर आयत x 3 शीर्ष 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 का मतलब समझने के लिए है, वहां से किसी त्रिभुज को घड़ी की दिशा में या घड़ी की उलटी दिशा में जाना चाहिए या नहीं, यह क्लिपस्पेस में उस त्रिभुज के शीर्ष पर निर्भर करता है. दूसरे शब्दों में, जब आप वर्टेक्स शेडर के वर्टेक्स पर गणित लागू करते हैं, तब WebGL यह पता लगाता है कि वह त्रिभुज सामने है या पीछे. उदाहरण के लिए, इसका मतलब है कि घड़ी की सुई की दिशा में जिस त्रिभुज को X x 1 से बढ़ाया गया है वह घड़ी की उलटी दिशा वाला त्रिभुज बन जाता है. इसी तरह, X या Y ऐक्सिस पर 180 डिग्री घूमा जाने वाला त्रिकोण, घड़ी की उलटी दिशा वाला त्रिभुज बन जाता है. क्योंकि हमने CULL_FACE को बंद किया हुआ था, इसलिए हम घड़ी की दिशा में(आगे का हिस्सा) और घड़ी की उलटी दिशा में(पीछे की ओर) दोनों त्रिभुजों को देख सकते हैं. हमने इसे चालू कर लिया है, तो जब भी सामने की ओर वाला त्रिभुज, स्केलिंग या रोटेशन के कारण या किसी भी कारण से इधर-उधर पलता है, WebGL उसे नहीं बनाएगा. यह एक अच्छी बात है, क्योंकि जब आप किसी चीज़ को 3D में घुमाते हैं, तो आम तौर पर आप चाहते हैं कि किन त्रिभुजों को आपकी ओर से सामना किया जाए.

नमस्ते! सारे त्रिकोण कहां गए? ऐसा लगता है कि उनमें से कई लोग गलत रास्ते का सामना कर रहे हैं. उसे घुमाएं और आपको दूसरी ओर देखने पर वे दिखेंगे. अच्छी बात यह है कि इसे ठीक करना आसान है. हम बस इस पर ध्यान देते हैं कि कौन से पीछे मौजूद हैं और अपने 2 वर्टेक्स की अदला-बदली करते हैं. उदाहरण के लिए, अगर एक पीछे वाला त्रिकोण है

1,   2,   3,
40,  50,  60,
700, 800, 900,

इसे आगे बढ़ाने के लिए, हमने बस पिछले दो वर्टेक्स को पलट दिया है.

1,   2,   3,
700, 800, 900,
40,  50,  60,

वह करीब है, लेकिन एक और समस्या अब भी है. यहाँ तक कि सभी त्रिभुज भी सही दिशा में हैं और पिछले हिस्से को छांटा गया है, फिर भी हमारे पास ऐसी जगहें हैं जहाँ पीछे की ओर बने त्रिभुजों को उन त्रिभुजों पर बनाया जा रहा है जो सामने होने चाहिए. DEPTH बफ़र डालें. डेप्थ बफ़र को कभी-कभी ज़ेड-बफ़र भी कहा जाता है. यह 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.VERexAttribPointer साइज़ 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' का मतलब है कि हर एट्रिब्यूट में सिर्फ़ तीन वैल्यू निकाली जा सकती हैं. यह काम करता है, क्योंकि वर्टेक्स शेडर में WebGL उन्हें डिफ़ॉल्ट तौर पर सेट करता है जिन्हें आप सप्लाई नहीं करते. डिफ़ॉल्ट वैल्यू 0, 0, 0, 1 होती हैं, जहां x = 0, y = 0, z = 0, और w = 1 होगा. यही वजह है कि हमारे पुराने 2D वर्टेक्स शेडर में, हमें 1. हम x और y में पास कर रहे थे और z के लिए 1 की ज़रूरत थी, लेकिन z के लिए डिफ़ॉल्ट वैल्यू 0 है, इसलिए हमें साफ़ तौर पर 1 देना पड़ा. 3D के लिए भले ही हम 'w' नहीं देते, लेकिन यह डिफ़ॉल्ट रूप से 1 होता है जिसकी ज़रूरत हमें मैट्रिक्स गणित के काम करने के लिए होती है.