opengl
3D-wiskunde
Zoeken…
Inleiding tot matrices
Wanneer je programmeert in OpenGL of een andere grafische API, zul je tegen een muur aanlopen als je niet zo goed bent in wiskunde. Hier zal ik met voorbeeldcode uitleggen hoe je beweging / schaling en vele andere coole dingen met je 3D-object kunt bereiken.
Laten we een real-life case nemen ... Je hebt een geweldige (driedimensionale) kubus gemaakt in OpenGL en je wilt deze in elke richting verplaatsen.
glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
In game-engines zoals Unity3d zou dit eenvoudig zijn. Je zou gewoon transform.Translate () aanroepen en er klaar mee zijn, maar OpenGL bevat geen wiskundebibliotheek.
Een goede wiskundebibliotheek is glm, maar om mijn punt duidelijk te maken zal ik alle (belangrijke) wiskundige methoden voor je coderen.
Eerst moeten we begrijpen dat een 3D-object in OpenGL veel informatie bevat, er zijn veel variabelen die van elkaar afhankelijk zijn. Een slimme manier om al deze variabelen te beheren, is door matrices te gebruiken.
Een matrix is een verzameling variabelen geschreven in kolommen en rijen. Een matrix kan 1x1, 2x4 of een willekeurig nummer zijn.
[1|2|3]
[4|5|6]
[7|8|9] //A 3x3 matrix
Je kunt echt coole dingen met ze doen ... maar hoe kunnen ze me helpen met het verplaatsen van mijn kubus? Om dit echt te begrijpen, moeten we eerst verschillende dingen weten.
- Hoe maak je een matrix vanuit een positie?
- Hoe vertaal je een matrix?
- Hoe geef je het door aan OpenGL?
Laten we een klasse maken met al onze belangrijke matrixgegevens en -methoden (geschreven in 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
};
}
}
Eerst zien we iets heel bijzonders in de standaardconstructor van een 4 bij 4 matrix. Wanneer het wordt aangeroepen, begint het niet allemaal op nul, maar als:
[1|0|0|0]
[0|1|0|0]
[0|0|1|0]
[0|0|0|1] //A identity 4 by 4 matrix
Alle matrices moeten beginnen met die op de diagonaal. (alleen omdat>. <)
Oké, dus laten we bij onze epische kubus een 4 bij 4 matrix verklaren.
glUseProgram(cubeProgram)
Matrix4x4<float> position;
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, cubeData);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
Nu we eigenlijk al onze variabelen hebben, kunnen we eindelijk wat wiskunde gaan doen! Laten we vertalen. Als u in Unity3d hebt geprogrammeerd, herinnert u zich misschien de functie Transform.Translate. Laten we het in onze eigen matrixklasse implementeren
/*!
* 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;
}
Dit is alle wiskunde die nodig is om de kubus te verplaatsen (niet roteren of schalen hoor). Het werkt onder alle hoeken. Laten we dit in ons echte scenario implementeren
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)
Onze shader moet onze prachtige matrix gebruiken
#version 410 core
uniform mat4 mv_matrix;
layout(location = 0) in vec4 position;
void main(void){
gl_Position = v_matrix * position;
}
En het zou moeten werken .... maar het lijkt erop dat we al een bug in ons programma hebben. Wanneer je langs de z-as beweegt, lijkt je object in de lucht te verdwijnen. Dit komt omdat we geen projectiematrix hebben. Om deze bug op te lossen, moeten we twee dingen weten:
- Hoe ziet een projectiematrix eruit?
- Hoe kunnen we dit combineren met onze positiematrix?
Welnu, we kunnen een perspectief maken (we gebruiken tenslotte drie dimensies) matrix De 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));
}
Het ziet er eng uit, maar deze methode berekent eigenlijk een matrix van hoe ver je in de verte (en hoe dichtbij) en je gezichtsveld wilt kijken.
Nu hebben we een projectiematrix en een positiematrix. Maar hoe combineren we ze? Het leuke is dat we eigenlijk twee matrices met elkaar kunnen vermenigvuldigen.
/*!
* 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 ... dat is veel code die er eigenlijk enger uitziet dan het er eigenlijk uitziet. Het kan in een for-lus worden gedaan, maar ik dacht (waarschijnlijk ten onrechte) dat dit duidelijker zou zijn voor mensen die nooit met matrices hebben gewerkt.
Bekijk de code en zie een herhalend patroon. Vermenigvuldig de kolom met de rij, voeg deze toe en ga door. (Dit is hetzelfde voor elke groottematrix)
* Merk op dat vermenigvuldiging met matrices niet zoals normale vermenigvuldiging is. AXB! = B x A *
Nu weten we hoe we dit moeten projecteren en toevoegen aan onze positiematrix. Onze real-life code ziet er waarschijnlijk uit als:
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)
Nu is onze bug platgedrukt en ziet onze kubus er in de verte behoorlijk episch uit. Als u uw kubus wilt schalen, is de formule deze:
/*!
* 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;
}
U hoeft alleen de diagonale variabelen aan te passen.
Voor rotatie moet je Quaternions nader bekijken.