Suche…
Einführung in Matrizen
Wenn Sie in OpenGL oder einer anderen Grafik-API programmieren, stoßen Sie auf eine Mauer, wenn Sie nicht so gut in Mathe sind. Hier erkläre ich mit Beispielcode, wie Sie mit Ihrem 3D-Objekt Bewegung / Skalierung und viele andere coole Dinge erreichen können.
Nehmen wir einen Fall aus dem wirklichen Leben ... Sie haben in OpenGL einen beeindruckenden (dreidimensionalen) Würfel erstellt und möchten ihn in eine beliebige Richtung verschieben.
glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
In Spiel-Engines wie Unity3d wäre das einfach. Sie würden einfach transform.Translate () aufrufen und damit fertig sein, OpenGL enthält jedoch keine Mathematikbibliothek.
Eine gute Mathematikbibliothek ist glm, aber um meine Meinung zu vermitteln, werde ich alle (wichtigen) mathematischen Methoden für Sie ausarbeiten.
Zunächst müssen wir verstehen, dass ein 3D-Objekt in OpenGL viele Informationen enthält. Es gibt viele Variablen, die voneinander abhängig sind. Eine intelligente Methode zum Verwalten all dieser Variablen ist die Verwendung von Matrizen.
Eine Matrix ist eine Sammlung von Variablen, die in Spalten und Zeilen geschrieben sind. Eine Matrix kann 1x1, 2x4 oder eine beliebige Zahl sein.
[1|2|3]
[4|5|6]
[7|8|9] //A 3x3 matrix
Sie können wirklich coole Sachen mit ihnen machen ... aber wie können sie mir helfen, meinen Würfel zu bewegen? Um dies wirklich zu verstehen, müssen wir zunächst einige Dinge kennen.
- Wie macht man aus einer Position eine Matrix?
- Wie übersetzt man eine Matrix?
- Wie übergeben Sie es an OpenGL?
Lassen Sie uns eine Klasse erstellen, die alle unsere wichtigen Matrixdaten und -methoden enthält (in C ++ geschrieben).
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
};
}
}
Zunächst fällt uns im Default-Konstruktor einer 4 x 4-Matrix etwas sehr Besonderes auf. Wenn es aufgerufen wird, fängt es nicht alles bei Null an, sondern wie:
[1|0|0|0]
[0|1|0|0]
[0|0|1|0]
[0|0|0|1] //A identity 4 by 4 matrix
Alle Matrizen sollten mit Diagonalen beginnen. (nur weil>. <)
Okay, lassen Sie uns in unserem epischen Würfel eine 4 x 4-Matrix erklären.
glUseProgram(cubeProgram)
Matrix4x4<float> position;
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, cubeData);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
Jetzt haben wir alle unsere Variablen, und wir können endlich anfangen, etwas zu rechnen! Lassen Sie uns die Übersetzung machen. Wenn Sie in Unity3d programmiert haben, erinnern Sie sich möglicherweise an eine Transform.Translate-Funktion. Lassen Sie uns es in unserer eigenen Matrixklasse implementieren
/*!
* 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;
}
Dies ist alles, was zum Bewegen des Würfels erforderlich ist (keine Drehung oder Skalierung). Es funktioniert in allen Winkeln. Lassen Sie uns dies in unserem realen Szenario implementieren
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)
Unser Shader muss unsere wunderbare Matrix verwenden
#version 410 core
uniform mat4 mv_matrix;
layout(location = 0) in vec4 position;
void main(void){
gl_Position = v_matrix * position;
}
Und es sollte funktionieren .... aber es scheint, dass wir bereits einen Fehler in unserem Programm haben. Wenn Sie sich entlang der z-Achse bewegen, scheint Ihr Objekt in Luft zu verschwinden. Das liegt daran, dass wir keine Projektionsmatrix haben. Um diesen Fehler zu beheben, müssen wir zwei Dinge wissen:
- Wie sieht eine Projektionsmatrix aus?
- Wie können wir es mit unserer Positionsmatrix kombinieren?
Nun, wir können eine Perspektive erstellen (wir verwenden immerhin drei Dimensionen). Matrix Der Code
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));
}
Es sieht beängstigend aus, aber diese Methode berechnet tatsächlich eine Matrix, wie weit Sie in die Entfernung (und wie nahe) und in Ihr Sichtfeld blicken möchten.
Jetzt haben wir eine Projektionsmatrix und eine Positionsmatrix. Wie kombinieren wir sie? Nun, es ist eine tolle Sache, dass wir tatsächlich zwei Matrizen miteinander multiplizieren können.
/*!
* 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 .. das ist eine Menge Code, der tatsächlich gruseliger aussieht als er aussieht. Es kann in einer for-Schleife durchgeführt werden, aber ich dachte (wahrscheinlich irrtümlich), dass dies für Menschen klarer wäre, die niemals mit Matrizen gearbeitet haben.
Schauen Sie sich den Code an und bemerken Sie ein sich wiederholendes Muster. Multiplizieren Sie die Spalte mit der Zeile, fügen Sie sie hinzu und fahren Sie fort (dies ist bei jeder Größenmatrix gleich).
* Beachten Sie, dass die Multiplikation mit Matrizen nicht der normalen Multiplikation entspricht. AXB! = B x A *
Jetzt wissen wir, wie man diese Position projiziert und in unsere Positionsmatrix einfügt. Unser realer Lebenscode wird wahrscheinlich so aussehen:
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)
Jetzt ist unser Bug gequetscht und unser Würfel sieht in der Ferne ziemlich episch aus. Wenn Sie Ihren Würfel skalieren möchten, lautet die Formel:
/*!
* 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;
}
Sie müssen nur die diagonalen Variablen anpassen.
Für die Rotation müssen Sie Quaternions näher betrachten.