矩阵简介
当你使用 OpenGL 或任何其他图形 API 进行编程时,如果你的数学不是很好,那么你将会遇到困难。在这里,我将用示例代码解释如何使用 3d 对象实现移动/缩放和许多其他很酷的东西。
让我们来看一个真实的案例……你在 OpenGL 中制作了一个很棒的(三维)立方体,你想把它移到任何方向。
glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)
在像 Unity3d 这样的游戏引擎中,这很容易。你只需调用 transform.Translate()
并完成它,但 OpenGL 不包含数学库。
一个好的数学库是 glm 但是为了得到我的观点,我会为你编写所有(重要的)数学方法。
首先,我们必须了解 OpenGL 中的 3d 对象包含大量信息,有许多变量相互依赖。管理所有这些变量的一种聪明方法是使用矩阵。
矩阵是用列和行写的变量的集合。矩阵可以是 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)
现在我们实际上已经拥有了所有变量,我们终于可以开始做一些数学了! 我们做翻译吧。如果你已经在 Unity3d 中编程,你可能还记得 Transform.Translate 函数。让我们在我们自己的矩阵类中实现它
/*!
* 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 轴移动时,你的物体似乎消失在空气中。这是因为我们没有投影矩阵。要解决这个 bug,我们需要知道两件事:
- 投影矩阵如何?
- 我们如何将它与我们的位置矩阵结合起来?
那么我们可以制作一个视角(我们毕竟使用三个维度)矩阵代码
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 ..这是很多代码,实际上看起来更加可怕。它可以在 for 循环中完成,但我(可能是错误地)认为这对于从未使用矩阵的人来说会更清楚。
查看代码并注意重复模式。将列与行相乘,将其添加并继续。(对于任何大小的矩阵,这都是相同的)
*请注意,与矩阵的乘法与正常乘法不同。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;
}
你只需要调整对角线变量。
如需轮换,你需要仔细查看四元数。