到目前为止,咱们依然仅仅写一些静态的作用,比如画三角形,制作纹路等,但假如想要制作一些动态的作用呢?那就需求用到矩阵了。
矩阵的应用范围很广,咱们能够凭借矩阵进行缩放、旋转、位移等操作。咱们输入的极点坐标是向量,但也能够看作是N1的矩阵,经过矩阵改变就能改动极点坐标值,这样图形天然就改换了
1、向量
向量有两个最基本的元素:
- 巨细(或许称为长度):假定向量在二维坐标系上的终点坐标为(x,y),那么巨细为x2+y2\sqrt{x^2 + y^2}
- 方向:从原点指向(x、y)点
上图中的v和w向量,能够认为是同一个向量,由于它们的巨细及方向均相同。
1.1 向量与标量运算
标量(Scalar)仅仅一个数字(或许说是仅有一个重量的向量)。当把一个向量加/减/乘/除一个标量,咱们能够简略的把向量的每个重量分别进行该运算。关于加法来说会像这样:
1.2 向量取反
对一个向量取反(Negate)会将其方向逆转。一个指向东北的向量取反后就指向西南方向了。咱们在一个向量的每个重量前加负号就能够完成取反了(或许说用-1数乘该向量):
1.3 向量加减
向量的加法能够被界说为是重量的(Component-wise)相加,即将一个向量中的每一个重量加上另一个向量的对应重量:
向量v = (4, 2)
和k = (1, 2)
能够直观地表示为:
向量相减和相加相似,如下图所示:
1.4 向量乘法
向量乘法有两种,点乘和叉乘。
- 点乘:两个向量的点乘等于它们的数乘结果乘以两个向量之间夹角的余弦值。或许听起来有点隐晦,咱们来看一下公式:
- 叉乘:叉乘只在3D空间中有界说,它需求两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量。假如输入的两个向量也是正交的,那么叉乘之后将会发生3个互相正交的向量。接下来的教程中这会十分有用。下面的图片展现了3D空间中叉乘的姿态:
2、矩阵
矩阵的基础加减法以及乘法,这儿不再细说了,线性代数应该都还没有忘掉。
2.1、矩阵和向量相乘
向量,前文咱们就已经说过了,能够认为是一个N1的矩阵。这样,矩阵和向量就能够相乘了。可有同学会问了,为什么向量是N1的矩阵,而不是1N的矩阵,这个问题问得好,由于我也不知道。。。
不过,咱们一定要记住这个定论,由于矩阵相乘不支持交换率,向量的方法决议了矩阵相乘时的方位。
例如,现在要进行矩阵改变,改变矩阵有两个,分别是T和M,它们都是44的矩阵,需求是先进行T改换,再进行M改换,那咱们怎么来写呢?
M T Vec4
为什么要这么写呢?由于向量是N1的矩阵,所以只能把向量放在改换乘号的右边(假如向量放乘号左面,这两个矩阵无法相乘),如此则意味着,向量会先乘T,再乘M,如此则完成了先T改换,再M改换(改换的本质次序要从右往左看)。
别的,进行矩阵改换时,一般咱们用单位矩阵来做改换,为什么呢?
单位矩阵乘向量,向量完全没有改变,能够把单位矩阵理解为矩阵国际里的单位1。
2.1、缩放
咱们从单位矩阵了解到,每个对角线元素会分别与向量的对应元素相乘。假如咱们把1变为3会怎样?这姿态的话,咱们就把向量的每个元素乘以3了,这事实上就把向量缩放3倍。假如咱们把缩放变量表示为(S1,S2,S3)(S1,S2,S3)咱们能够为恣意向量(x,y,z)(x,y,z)界说一个缩放矩阵:
注意,第四个缩放向量依然是1,由于在3D空间中缩放w重量是无意义的。w重量还有其他用处,在后面咱们会看到。
2.2、位移
和缩放矩阵相同,在44矩阵上有几个特别的方位用来履行特定的操作,关于位移来说它们是第四列最上面的3个值。假如咱们把位移向量表示为(Tx,Ty,Tz)(Tx,Ty,Tz),咱们就能把位移矩阵界说为:
2.3、旋转
旋转的公式难以理解。在旋转时一般要指定两个参数,一是旋转的视点,另一个便是旋转轴。旋转视点特别好理解,旋转轴是什么?事实上咱们一直触摸的是二维坐标系,没有触摸过三维坐标,假如在3D空间中就能理解旋转轴是什么了。简略来说,二维坐标系上,旋转轴都是Z轴
公式这儿就不贴了,咱们能够自行研究。
虽然公式没有贴,但咱们有愈加人性化的api接口能够贴,现在一般用glm库进行矩阵改换,改换的接口为:
glm::rotate(trans, glm::radians((float)degree), glm::vec3(0.0f, 0.0f, 1.0f));
3、实践
glm三大改换矩阵接口为:
glm::mat4 trans(1.0f);
//位移
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
//旋转
trans = glm::rotate(trans, glm::radians((float)mDegree), glm::vec3(0.0f, 0.0f, 1.0f));
//缩放
trans = glm::scale(mat, glm::vec3(0.8, 0.8, 0.8));
现在我想要完成“不停地旋转一张图片,然后位移图片到左下角”,该怎么办呢?
在上文中提到了,改换矩阵组合的次序问题,先旋转再位移,矩阵次序就得这么来: 位移 旋转 向量
参见上一篇文章中对uniform变量的阐明,能够让GlSurfaceView不停地履行onDraw办法,然后在draw办法中不停地对uniform变量赋值。
本例中的片段着色器能够不必更改,只需求改极点着色器:
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
uniform mat4 transform;
void main() {
//极点着色器这儿也是矩阵乘以向量,向量在乘号右边
gl_Position = transform * a_position;
v_texCoord = a_texCoord;
}
咱们再看看draw办法是怎么写的?
void TransformSample::draw() {
if (m_ProgramObj == 0) {
return;
}
glClearColor(1.0f, 1.0f, 1.0f, 1);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(m_ProgramObj);
glBindVertexArray(mVaoId);
//指定片段着色器中名为lyfId的变量,运用序号为0的纹路
GLUtils::setUniformValue1i(m_ProgramObj, "lyfId", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mLyfTextureId);
//测验代码,位移
// glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
// glm::mat4 trans(1.0f);
// trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
// vec = trans * vec;
// LOGI("x = %f, y = %f, z = %f", vec.x, vec.y, vec.z);
//测验代码,旋转缩放
// glm::mat4 trans(1.0f);
// trans = glm::rotate(mat, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0f));
// trans = glm::scale(mat, glm::vec3(0.8, 0.8, 0.8));
mDegree += 1.0f;
glm::mat4 trans(1.0f);
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::rotate(trans, glm::radians((float)mDegree), glm::vec3(0.0f, 0.0f, 1.0f));
unsigned int transformLoc = glGetUniformLocation(m_ProgramObj, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void*)0);
}
请注意,这儿原本是想用时刻戳来转float,但有两个问题:
- 时刻戳是long,直接转float会丢失精度,导致值改变得很慢,一秒钟才能变一次
- 假如用求余的方法缩小原值,再转float,这样改变慢的问题解决了,但有别的一个问题,视点改变太快了,由于GLSurfaceView的改写时刻真的快,视点改变略微快一点,就不太好看了
基于以上两个原因,所以本人这儿用了一个成员变量,每次制作时主动加1,这样作用较好。
最终,咱们看看作用: