खोज…
मेट्रिसेस का परिचय
जब आप OpenGL या किसी अन्य ग्राफिक्स में प्रोग्रामिंग कर रहे हैं, तो आप एक ईंट की दीवार से टकराएंगे जब आप गणित में अच्छे नहीं होंगे। यहां मैं उदाहरण कोड के साथ समझाऊंगा कि आप अपने 3 डी ऑब्जेक्ट के साथ आंदोलन / स्केलिंग और कई अन्य शांत सामान कैसे प्राप्त कर सकते हैं।
चलो एक वास्तविक जीवन का मामला लेते हैं ... आपने ओपनजीएल में एक भयानक (तीन आयामी) क्यूब बनाया है और आप इसे किसी भी दिशा में ले जाना चाहते हैं।
glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
यूनिटी 3 डी जैसे गेम इंजन में यह आसान होगा। आप बस ट्रांसफॉर्मेट कहेंगे। ट्रान्सलेट () और इसके साथ किया जाएगा, लेकिन ओपनजीएल में एक गणित पुस्तकालय शामिल नहीं है।
एक अच्छा गणित पुस्तकालय glm है, लेकिन मैं अपनी बात पाने के लिए आप के लिए सभी (महत्वपूर्ण) गणितीय तरीकों को कोड करूँगा।
पहले हमें यह समझना चाहिए कि ओपनजीएल में एक 3 डी ऑब्जेक्ट में बहुत सारी जानकारी होती है, कई चर हैं जो एक दूसरे पर निर्भर करते हैं। इन सभी चरों को प्रबंधित करने का एक स्मार्ट तरीका मैट्रिस का उपयोग करना है।
एक मैट्रिक्स स्तंभों और पंक्तियों में लिखे गए चर का संग्रह है। एक मैट्रिक्स 1x1, 2x4 या किसी भी मनमानी संख्या हो सकती है।
[1|2|3]
[4|5|6]
[7|8|9] //A 3x3 matrix
आप उनके साथ वास्तव में अच्छा कर सकते हैं ... लेकिन वे मेरी घन को स्थानांतरित करने में मेरी मदद कैसे कर सकते हैं? वास्तव में इसे समझने के लिए हमें पहले कई चीजों को जानना होगा।
- आप एक स्थिति से एक मैट्रिक्स कैसे बनाते हैं?
- आप मैट्रिक्स का अनुवाद कैसे करते हैं?
- आप इसे OpenGL से कैसे पास करते हैं?
आइए एक वर्ग बनाते हैं जिसमें हमारे सभी महत्वपूर्ण मैट्रिक्स डेटा और विधियाँ हैं (c ++ में लिखा गया है)
template<typename T>
//Very simple vector containing 4 variables
struct Vector4{
T x, y, z, w;
Vector4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w){}
Vector4(){}
Vector4<T>& operator=(Vector4<T> other){
this->x = other.x;
this->y = other.y;
this->z = other.z;
this->w = other.w;
return *this;
}
}
template<typename T>
struct Matrix4x4{
/*!
* You see there are columns and rows like this
*/
Vector4<T> row1,row2,row3,row4;
/*!
* Initializes the matrix with a identity matrix. (all zeroes except the ones diagonal)
*/
Matrix4x4(){
row1 = Vector4<T>(1,0,0,0);
row2 = Vector4<T>(0,1,0,0);
row3 = Vector4<T>(0,0,1,0);
row4 = Vector4<T>(0,0,0,1);
}
static Matrix4x4<T> identityMatrix(){
return Matrix4x4<T>(
Vector4<T>(1,0,0,0),
Vector4<T>(0,1,0,0),
Vector4<T>(0,0,1,0),
Vector4<T>(0,0,0,1));
}
Matrix4x4(const Matrix4x4<T>& other){
this->row1 = other.row1;
this->row2 = other.row2;
this->row3 = other.row3;
this->row4 = other.row4;
}
Matrix4x4(Vector4<T> r1, Vector4<T> r2, Vector4<T> r3, Vector4<T> r4){
this->row1 = r1;
this->row2 = r2;
this->row3 = r3;
this->row4 = r4;
}
/*!
* Get all the data in an Vector
* @return rawData The vector with all the row data
*/
std::vector<T> getRawData() const{
return{
row1.x,row1.y,row1.z,row1.w,
row2.x,row2.y,row2.z,row2.w,
row3.x,row3.y,row3.z,row3.w,
row4.x,row4.y,row4.z,row4.w
};
}
}
पहले हम 4 के 4 मैट्रिक्स के डिफॉल्ट कंस्ट्रक्टर में एक बहुत ही अजीब चीज को नोटिस करते हैं। जब यह कहा जाता है तो यह शून्य पर शुरू नहीं होता है, लेकिन जैसे:
[1|0|0|0]
[0|1|0|0]
[0|0|1|0]
[0|0|0|1] //A identity 4 by 4 matrix
सभी मैट्रिक्स विकर्ण पर लोगों के साथ शुरू होनी चाहिए। (सिर्फ इसलिए> <।)
ठीक है तो चलो हमारे महाकाव्य घन 4 ए 4 मैट्रिक्स द्वारा घोषित करते हैं।
glUseProgram(cubeProgram)
Matrix4x4<float> position;
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, cubeData);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
अब हमारे पास वास्तव में हमारे सभी चर हैं जो हम अंततः कुछ गणित करना शुरू कर सकते हैं! अनुवाद करते हैं। अगर आपने यूनिटी 3 डी में प्रोग्राम किया है तो आपको एक ट्रांसफॉर्मेट ट्रांसलेट फ़ंक्शन याद हो सकता है। आइए इसे अपने स्वयं के मैट्रिक्स वर्ग में लागू करें
/*!
* Translates the matrix to
* @param vector, The vector you wish to translate to
*/
static Matrix4x4<T> translate(Matrix4x4<T> mat, T x, T y, T z){
Matrix4x4<T> result(mat);
result.row1.w += x;
result.row2.w += y;
result.row3.w += z;
return result;
}
यह क्यूब को चारों ओर ले जाने के लिए आवश्यक सभी गणित है (आप रोटेशन या स्केलिंग माइंड नहीं) यह सभी कोणों पर काम करता है। आइए इसे हमारे वास्तविक जीवन परिदृश्य में लागू करें
glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
हमारे शेडर को हमारे अद्भुत मैट्रिक्स का उपयोग करने की आवश्यकता है
#version 410 core
uniform mat4 mv_matrix;
layout(location = 0) in vec4 position;
void main(void){
gl_Position = v_matrix * position;
}
और यह काम करना चाहिए .... लेकिन ऐसा लगता है कि हमारे कार्यक्रम में पहले से ही एक बग है। जब आप z अक्ष के साथ आगे बढ़ते हैं तो आपकी वस्तु सही पतली हवा में गायब हो जाती है। इसका कारण यह है कि हमारे पास एक प्रक्षेपण मैट्रिक्स नहीं है। इस बग को हल करने के लिए हमें दो बातें जानने की जरूरत है:
- एक प्रक्षेपण मैट्रिक्स कैसा दिखता है?
- हम इसे अपनी स्थिति मैट्रिक्स के साथ कैसे जोड़ सकते हैं?
खैर हम एक परिप्रेक्ष्य बना सकते हैं (हम तीन आयामों का उपयोग कर रहे हैं) मैट्रिक्स कोड
template<typename T>
Matrix4x4<T> perspective(T fovy, T aspect, T near, T far){
T q = 1.0f / tan((0.5f * fovy) * (3.14 / 180));
T A = q / aspect;
T B = (near + far) / (near - far);
T C = (2.0f * near * far) / (near - far);
return Matrix4x4<T>(
Vector4<T>(A,0,0,0),
Vector4<T>(0,q,0,0),
Vector4<T>(0,0,B,-1),
Vector4<T>(0,0,C,0));
}
यह डरावना लग रहा है, लेकिन यह विधि वास्तव में एक मैट्रिक्स की गणना करेगी कि आप कितनी दूरी (और कितना करीब) और अपने क्षेत्र को देखना चाहते हैं।
अब हमारे पास एक प्रक्षेपण मैट्रिक्स और एक स्थिति मैट्रिक्स है .. लेकिन हम उन्हें कैसे जोड़ते हैं? अच्छी बात यह है कि हम वास्तव में एक दूसरे के साथ दो मैट्रिसेस को गुणा कर सकते हैं।
/*!
* Multiplies a matrix with an other matrix
* @param other, the matrix you wish to multiply with
*/
static Matrix4x4<T> multiply(const Matrix4x4<T>& first,const Matrix4x4<T>& other){
//generate temporary matrix
Matrix4x4<T> result;
//Row 1
result.row1.x = first.row1.x * other.row1.x + first.row1.y * other.row2.x + first.row1.z * other.row3.x + first.row1.w * other.row4.x;
result.row1.y = first.row1.x * other.row1.y + first.row1.y * other.row2.y + first.row1.z * other.row3.y + first.row1.w * other.row4.y;
result.row1.z = first.row1.x * other.row1.z + first.row1.y * other.row2.z + first.row1.z * other.row3.z + first.row1.w * other.row4.z;
result.row1.w = first.row1.x * other.row1.w + first.row1.y * other.row2.w + first.row1.z * other.row3.w + first.row1.w * other.row4.w;
//Row2
result.row2.x = first.row2.x * other.row1.x + first.row2.y * other.row2.x + first.row2.z * other.row3.x + first.row2.w * other.row4.x;
result.row2.y = first.row2.x * other.row1.y + first.row2.y * other.row2.y + first.row2.z * other.row3.y + first.row2.w * other.row4.y;
result.row2.z = first.row2.x * other.row1.z + first.row2.y * other.row2.z + first.row2.z * other.row3.z + first.row2.w * other.row4.z;
result.row2.w = first.row2.x * other.row1.w + first.row2.y * other.row2.w + first.row2.z * other.row3.w + first.row2.w * other.row4.w;
//Row3
result.row3.x = first.row3.x * other.row1.x + first.row3.y * other.row2.x + first.row3.z * other.row3.x + first.row3.w * other.row4.x;
result.row3.y = first.row3.x * other.row1.y + first.row3.y * other.row2.y + first.row3.z * other.row3.y + first.row3.w * other.row4.y;
result.row3.z = first.row3.x * other.row1.z + first.row3.y * other.row2.z + first.row3.z * other.row3.z + first.row3.w * other.row4.z;
result.row3.w = first.row3.x * other.row1.w + first.row3.y * other.row2.w + first.row3.z * other.row3.w + first.row3.w * other.row4.w;
//Row4
result.row4.x = first.row4.x * other.row1.x + first.row4.y * other.row2.x + first.row4.z * other.row3.x + first.row4.w * other.row4.x;
result.row4.y = first.row4.x * other.row1.y + first.row4.y * other.row2.y + first.row4.z * other.row3.y + first.row4.w * other.row4.y;
result.row4.z = first.row4.x * other.row1.z + first.row4.y * other.row2.z + first.row4.z * other.row3.z + first.row4.w * other.row4.z;
result.row4.w = first.row4.x * other.row1.w + first.row4.y * other.row2.w + first.row4.z * other.row3.w + first.row4.w * other.row4.w;
return result;
}
Ooef .. यह एक बहुत अधिक कोड है जो वास्तव में अधिक डरावना लगता है फिर यह वास्तव में दिखता है। यह लूप के लिए किया जा सकता है लेकिन मैंने (शायद गलती से) सोचा था कि यह उन लोगों के लिए स्पष्ट होगा जिन्होंने कभी मेट्रिसेस के साथ काम नहीं किया।
कोड को देखें और दोहराए जाने वाले पैटर्न पर ध्यान दें। पंक्ति के साथ कॉलम को गुणा करें और इसे जारी रखें। (यह किसी भी आकार के मैट्रिक्स के लिए समान है)
* ध्यान दें कि मैट्रिस के साथ गुणा सामान्य गुणन की तरह नहीं है। AXB! = B x A *
अब हम जानते हैं कि कैसे प्रोजेक्ट करें और इसे हमारे स्थिति मैट्रिक्स में जोड़ें हमारा वास्तविक जीवन कोड संभवतः जैसा दिखेगा:
glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
position = Matrix4x4<float>::multiply(Matrix<float>::perspective<float>(50, 1 , 0.1f, 100000.0f), position);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
अब हमारी बग को खत्म कर दिया गया है और हमारी घन दूरी में काफी महाकाव्य दिखाई देता है। यदि आप अपने घन को स्केल करना चाहते हैं, तो सूत्र यह है:
/*!
* Scales the matrix with given vector
* @param s The vector you wish to scale with
*/
static Matrix4x4<T> scale(const Matrix4x4<T>& mat, T x, T y, T z){
Matrix4x4<T> tmp(mat);
tmp.row1.x *= x;
tmp.row2.y *= y;
tmp.row3.z *= z;
return tmp;
}
आपको केवल विकर्ण चर को समायोजित करने की आवश्यकता है।
रोटेशन के लिए आपको Quaternions पर एक करीब से देखने की जरूरत है।