OpenGL ES 3.0(四)矩阵变换

1、概述

前面几篇关于OpenGLES的文章:

OpenGL ES 2.0 显示图形(上)

OpenGL ES 2.0 显示图形(下)

OpenGL ES 3.0(一)综述

OpenGL ES 3.0(二)GLSL与着色器

OpenGL ES 3.0(三)纹理

有讨论到关于图像的变换和移动,前面这些变换是图像基于每一帧改变物体的顶点并且重配置缓冲区从而使它们移动,但这太繁琐了,而且会消耗很多的处理时间。现在有一个更好的解决方案,使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体。

矩阵是一种非常有用的数学工具,如果上过线性代数这门课的话应该都会有所了解。这篇主要讨论一下在图像处理中一些最简单的矩阵变换,并且在OpenGL ES中进行实践。

2、向量的乘

要讨论矩阵,一定绕不开向量。向量可以说是最简单的矩阵,可以把它理解为1n的矩阵或者是n1的矩阵,关于向量的加减运算和长度计算等实在太过基础这边不再讨论。这边关于向量的运算主要讨论其乘法相关的运算。

2.1 点乘

向量的点乘一般用 v · k 这样表示,两个向量的点乘结果等于它们的数乘结果再乘两个向量之间夹角的余弦值。公式如下:

点乘公式

等式右边||v|| 和 ||k|| 分别代表两个向量的长度。通过上面的公式也可以反推出两个向量之间的夹角。而对于具体的两个向量,其点乘就是对应位置上的数字相乘再进行累加。

向量点乘

2.2 叉乘

叉乘只在3D空间中有定义,它需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量。如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量。下图展示了3D空间中叉乘的样子:

3D空间中的叉乘

叉乘公式如下:

向量叉乘

3、矩阵

上面讨论的向量其实也是矩阵的一种,矩阵就是数字、符号或表达式数组。矩阵中每一项叫做矩阵的元素(Element)。下面是一个2×3矩阵的例子:

2*3矩阵

矩阵可以通过(i, j)进行索引,i是行,j是列,这就是上面的矩阵叫做2×3矩阵的原因。获取上面4的索引是(2, 1)(第二行,第一列)(注:如果是图像索引应该是(1, 2),先算列,再算行)。

下面讨论一下一些矩阵的基本运算。

3.1 矩阵加减

矩阵的加减是矩阵最简单的操作,矩阵和标量进行加减其标量值要加减到矩阵每一个值当中。矩阵与矩阵之间的加减就是两个矩阵对应元素的加减运算,不过在相同索引下的元素才能进行运算。这也就是说加法和减法只对同维度的矩阵才是有定义的。

矩阵与标量加法
矩阵之间减法

3.2 矩阵数乘

矩阵与标量相乘和矩阵与标量的加减一样,会对每个数据进行乘。所以一般也用矩阵的数乘来缩放矩阵。

矩阵数乘

3.3 矩阵之间相乘

矩阵之间相乘会复杂许多,并且会有一些限制:

**① **只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。

**② **矩阵相乘不遵守交换律,也就是说A ·B 和 B·A 并不相等。

矩阵相乘时是将左边矩阵的i行和右边矩阵的j列分别对应相乘并相加得到新矩阵i行j列位置上的值。结果矩阵的维度是(n, m),n等于左侧矩阵的行数,m等于右侧矩阵的列数。

矩阵之间相乘

4、矩阵变换

前面已经讨论了矩阵和向量的一些基本操作,接下来讨论通过这些操作来对矩阵进行一些常见的变换。

4.1 缩放

对一个向量进行缩放(Scaling)就是对向量的长度进行缩放,而保持它的方向不变。由于进行的是2维或3维操作,可以分别定义一个有2或3个缩放变量的向量,每个变量缩放一个轴(x、y或z)。

先来尝试缩放向量v = (3,2)。可以把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;将沿着y轴把向量的高度缩放为原来的两倍。最终可得到如下向量s = (1.5,4):

向量缩放

OpenGL ES通常是在3D空间进行操作的,对于2D的情况可以把z轴缩放1倍,就相当于不缩放z轴。上图的缩放操作是不均匀缩放,因为每个轴的缩放因子都不一样。如果每个轴的缩放因子都一样那么就叫均匀缩放。

下面构造一个变换矩阵来提供缩放功能。从单位矩阵(矩阵正斜对角线为1,其余为0的矩阵),每个对角线元素会分别与向量的对应元素相乘,而不会干扰其他维度上的向量值。对于需要将任意向量(x,y,x)分别缩放(S1,S2,S3)倍时,可以用这样的特性来构造缩放矩阵。如下:

缩放矩阵

第四个缩放向量仍然是1,因为在3D空间中缩放w分量是无意义的。w分量另有其他用途,会在后面讨论。

4.2 位移

位移是在原始向量的基础上加上另一个向量从而获得一个在不同位置的新向量的过程,从而在位移向量基础上移动了原始向量。和缩放矩阵一样,在4×4矩阵上有几个特别的位置用来执行特定的操作,对于位移来说它们是第四列最上面的3个值。如果我们把位移向量表示为(Tx,Ty,Tz),就能把位移矩阵定义为:

位移矩阵

因为所有的位移值都要乘以向量的w行,所以位移值会加到向量的原始值上。而如果用3x3矩阵位移值就没地方放也没地方乘了,所以是不行的。这也是为什么3维的向量要用四维变换矩阵进行。

这个向量的w分量也叫齐次坐标分量。想要从齐次向量得到3维向量,可以把x、y和z坐标分别除以w坐标。通常不会注意这个问题,因为w分量通常是1.0。使用齐次坐标的好处在于它允许在3D向量上进行位移(如果没有w分量是不能位移向量的)。如果一个向量的齐次坐标分量值是0,这个坐标就是方向向量,因为w坐标是0,这个向量就不能位移。有了位移矩阵就可以在3个方向(x、y、z)上移动物体。

4.3 旋转

上面几个的变换内容相对容易理解,在二维或三维空间中也容易表示出来,但旋转稍复杂些。

首先来定义一个向量的旋转到底是什么。二维或三维空间中的旋转用角来表示。角可以是角度制或弧度制的,周角是360角度或2 PI弧度。下图中展示的二维向量v是由k向右旋转72度所得的:

二维向量的旋转

在三维空间中旋转需要定义一个角和一个旋转轴。物体会沿着给定的旋转轴旋转特定角度。当二维向量在三维空间中旋转时,一般把旋转轴设为z轴。

给定一个角度,可以把一个向量变换为一个经过旋转的新向量。这通常是使用一系列正弦和余弦函数的各种巧妙的组合得到的。旋转矩阵在三维空间中每个单位轴都有不同定义,旋转角度用θ表示:

沿x轴旋转:

沿x轴的旋转矩阵

沿y轴旋转:

沿y轴的旋转矩阵

沿z轴旋转:

沿z轴的旋转矩阵

利用旋转矩阵可以把任意位置向量沿一个单位旋转轴进行旋转。也可以将多个矩阵复合,比如先沿着x轴旋转再沿着y轴旋转。但是这会很快导致一个问题——万向节死锁。对于三维空间中的旋转,一个更好的模型是沿着任意的一个轴旋转。这样的一个旋转矩阵是存在的,但其非常复杂这边不进行展开讨论。

4.4 组合变换

使用矩阵进行变换的真正厉害之处在于,根据矩阵之间的乘法,可以把多个变换组合到一个矩阵中。假设有一个顶点(x, y, z),希望将其缩放2倍,然后位移(1, 2, 3)个单位。需要一个位移和缩放矩阵来完成这些变换。其最终的组合变换矩阵如下:

组合变换矩阵

当矩阵相乘时先写位移再写缩放变换。矩阵乘法是不遵守交换律的,这意味着它们的顺序很重要。当矩阵相乘时,在最右边的矩阵是第一个与向量相乘的,所以应该从右向左读这个乘法。建议在设计组合矩阵时,先进行缩放操作,然后是旋转,最后才是位移,否则它们会互相影响。比如,如果先位移再缩放,位移的向量也会同样被缩放。

用最终的变换矩阵左乘我们的向量会得到以下结果:

组合变换矩阵效果

如上所示,其目标向量确实放大来两倍并进行了位移。

5、OpenGLES中的矩阵

前面已经讨论完了矩阵的基本知识及操作,接下来讨论下关于OpenGLES 中矩阵的应用。OpenGLES中有一个类是专门用来进行矩阵处理的android.opengl.Matrix。这个类里面包括了对于矩阵的各种前面提到的处理变换操作。

**① **Matrix.setIdentityM() :用来创建一个单位矩阵。其中第一个参数是创建出来的单位矩阵存储的地方,是一个float类型的一维数组。第二个参数是存储的数据位置的偏移量,也就是说从哪里开始存储。生成的结果先按照列优先存储的,也就是说先存放第一列的数据,再存放第二列的数据,以此类推。前面理论部分已经提到,所有变换都是基于单位矩阵的基础上进行的,所以第一步创建单位矩阵是必须的。

**② **Matrix.rotateM() :用来进行旋转变换的。第一个参数是需要变换的矩阵;第二参数是偏移量;第三个参数是旋转角度,这边是以角度制,也就是说是0-360这个范围;第四、五、六个参数分别代表旋转轴向量的x,y,z值。如果x=0,y=0,z = 1 就相当于以z轴为旋转轴进行旋转,其他类似。

旋转90度效果

**③ **Matrix.translateM() :用来进行图像的位移,第一个参数是需要变换的矩阵;第二个参数是偏移量;第三、四、五个参数分别对应x,y,z 方向的位移量。其以图像自身x,y,z方向为单位,也就是说当x方向位移量为0.5时,相当于向右移动0.5个身位,其他类似。

偏移x=0.5 y=0.5效果

**④ **Matrix.scaleM():用来进行图像的缩放,第一个参数是需要变换的矩阵;第三、四、五个参数分别对应x,y,z 方向的缩放比例,当x方向缩放为0.5时,相当于向x方向缩放为原来的0.5倍,其他类似。

缩放x=2 y=0.5 效果

前面说过这些变换是可以进行组合运算的,其组合代码如下:


Triangle.kt

init{

    ...

    Matrix.setIdentityM(mTransMatrix, 0)

    Matrix.scaleM(mTransMatrix,0,2f,0.5f,1f)

    Matrix.rotateM(mTransMatrix, 0, mAngle, 0f, 0f, 1.0f)

    Matrix.translateM(mTransMatrix, 0, 0.5f, 0.5f, 0f)

    ...

}

之间用上面的操作相当于生成了一个组合矩阵,其效果如下图:

组合矩阵效果

光生成了变换矩阵还不能完成上述操作,还需要将变换矩阵数据传入到顶点着色器上:


// Triangle.kt

private val vertexShaderCode =

                    ...

                    "uniform mat4 transform;" +

                    "void main() {" +

                    " gl_Position = transform * vec4(aPos, 1.0);" +

                    ...

                    "}"

fun draw() {

...

        val transformLoc = GLES30.glGetUniformLocation(mProgram, "transform")

        GLES30.glUniformMatrix4fv(transformLoc, 1, false, mTransMatrix, 0)

...

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 版本记录 前言 OpenGL 图形库项目中一直也没用过,最近也想学着使用这个图形库,感觉还是很有意思,也就自然想着...
    刀客传奇阅读 4,995评论 0 3
  • 1 前言 OpenGL渲染3D模型离不开空间几何的数学理论知识,而本篇文章的目的就是对空间几何进行简单的介绍,并对...
    RichardJieChen阅读 6,690评论 1 11
  • 变换(Transformations) 我们可以尝试着在每一帧改变物体的顶点并且重设缓冲区从而使他们移动,但这太繁...
    IceMJ阅读 3,971评论 0 1
  • “静”应该是冬季独有的特点!站在空旷的山间,远远的眺望着山的那端,朦胧中连绵起伏,一切都静的出奇。山静静地...
    偷闲躲静阅读 476评论 0 2
  • 清邁這幾日 總在暴曬的大熱大悶後 再下場無所顧及的雨 所以白天我基本窩在旅館擼擼貓 寫寫資料 出門 是需要勇氣加持...
    嗚嚶阅读 227评论 0 0