OpenGL之矩阵浅讲

1,366 阅读23分钟

引言

图形开发中会遇到一个拦路虎,那就是矩阵,刚开始似懂非懂,怎么写都不对,网上的文章一般大多是矩阵的公式,初步入门的人很容易看的云里雾里,而在实际操作过程又会因为认知不清楚,很容易导致出错,所以特地写这篇文章把自己对矩阵的认知过程记录下来,方便后来人快速明白矩阵是什么,用来干什么,怎么写矩阵才能达到预期的效果。

结论

为了方便阅读,防止大段大段的内容影响你的阅读,我把认知结论放在前面,可以带着结论阅读,更舒适。如果觉得理论难理解可以直接看后面的实战。 这里推荐两个矩阵可视化网站

harry7557558.github.io/tools/matri…

shad.io/MatVis/

  1. 图形开发中良好的解释比大段大段的公式更重要,公式计算可以交给计算机。

  2. 矩阵是方程的一种表示方式,在图形开发中作为变换坐标系的函数,当然也可以认为坐标系不变,它只是改变坐标点的位置

  3. 齐次矩阵是为了统一矩阵运算都为乘法,并且把w分量作为近大远小的透视分量

  4. 矩阵有行矩阵和列矩阵的区分,其实是同一个的矩阵,只是存储顺序不同

  5. 矩阵影响坐标系其实是影响坐标系的基向量

  6. 逆矩阵就是为了计算坐标系的一个点在另外一个坐标系的表示,可以理解成矩阵的除法,其实就是解方程

  7. 矩阵运算的顺序很重要,可以理解成M3*M2构成的坐标系里面按M1矩阵移动V这个点。

  8. 投影矩阵虽然叫投影矩阵,但是其实是把长方体的物体变成正方体,反着理解才是把正方体的物体投影到长方体的空间中。这就是要理解矩阵乘法顺序的意义。

矩阵操作时候要注意操作物体的坐标系是什么,以下所有在Opengl的问题都是因为坐标系的问题。当然不只Opengl中有这些问题,其他环境的矩阵操作图形的时候都要注意这些问题。

  1. Opengl虽然是右手坐标系,但是如果不加投影矩阵,其实内部的NDC是左手坐标系,也就是其实z轴为-1的物体会把z轴为1的物体遮住,当然加了投影矩阵以后就没这个问题,会逆转z轴

  2. Opengl中矩阵的乘法是左乘,但是它的矩阵其实是列矩阵,也就是它的坐标系的原点是在下方的

  3. Opengl中用矩阵操作纹理坐标的时候,要先把0,1的纹理坐标转换成-1,1,然后再把矩阵逆转,不然你以为的放大物体,反而缩小物体了。

  4. Opengl中要注意模型矩阵的顺序,正确顺序是mat4 modelMat = offsetMatrotateMatscaleMat;opengl是从右边写到左边的,其实就是先放大后旋转再移动

  5. Opengl中矩阵如果放大旋转物体的时候,要先把物体移动到坐标系的原点,然后放大旋转,操作完以后再移动回去,不然旋转会变形

  6. Opengl中矩阵旋转物体的时候要先把物体比例调整成1:1,然后旋转完以后,再把物体比例调整回去。

矩阵是什么

坐标系

三维空间中无数的点放在一起形成了三维坐标系,根据z轴的方向不同,又分为左手坐标系,右手坐标系

按x到y轴方向握拳,大拇指的指向就是z轴的方向,这种方式比网上常见的用三个手指的指向更适合手残党记忆

坐标和矩阵

坐标系一个点的位置就是表示为

坐标系的中点叫原点

一个坐标到另外一个坐标代表的变化叫向量,所以坐标A到原点的向量刚好和坐标A本身长的一样

假设通过函数把变成另外一个点

简化书写就变成了

而其中的就叫做矩阵。

矩阵的用途

矩阵听起来会很奇怪不好记忆?矩就是矩形,阵就是排列,所以矩阵就是行列矩形的一种数字排列。

那这种排列有什么用呢?可以用来干什么?既然是行列排列为什么不叫做行列式。

我的理解矩阵就是一种变换坐标的函数(程序员叫方法),把坐标系的一个点变成另外一个点,图形开发中可用来操作图片每一个像素的位置,移动、旋转、放大、缩小。那么为什么不叫做行列式呢,因为行列式这个名字已经被用掉了,行列式是一个数字,在3维坐标系里代表了坐标变换时对应体积的变换。矩阵英文是matrix,而matrix有母体的意思,矩阵的命名者西尔维斯特认为矩阵是生成各种行列式的母体,这也是为什么科幻电影黑客帝国又叫matrix的原因。

齐次矩阵

上面的矩阵是33的,做过3D图形开发的人会发现矩阵是44的,那为什么会用4*4的矩阵呢?

齐次坐标

如果想放大图像矩阵该怎么操作呢,从方程中就是放大每个点的坐标

转换成矩阵就是

如果想移动图像矩阵该怎么操作呢,从方程中就是移动每个点的坐标

用方程表示就是转化成矩阵就是

放大、位移矩阵使用了加法、乘法两种不同的运算,我们希望能统一运算方便处理,而低维解决不了的事情可以用高维去解决。

三维空间坐标转换成四维空间坐标表示,w分量可以是任意的,

四维空间坐标转换成三维空间坐标,就是把w分量去除

放大方程用四维坐标表示就是

转换成矩阵就是

转换成矩阵就是

原先的n维坐标变成了n+1维坐标,这就是齐次坐标,原先的nn矩阵变成了n+1n+1矩阵,这就是齐次矩阵。齐次矩阵把矩阵运算都统一成了乘法运算。

回过头来再来看看齐次这个名词,齐次听起来很奇怪,齐是指相同,齐次就是未知数的次数相同,

不用齐次坐标的位移方程,每一行的前3项未知数次数都是1,而第4项未知数次数是0,

用了齐次坐标位移方程变成了,每一行的前3项未知数次数是1,第一行的第四项里面的w一般我们会用1这个常数,所以第4项未知数次数也是1,n维坐标变成n+1维坐标以后就齐次了。

W分量

齐次矩阵除了把运算都统一到了乘法的好处,还有一个好处就是实现近大远小的透视效果。

不做透视坐标转换成3维坐标的只是简单的把w去掉,但是做透视会除以w分量。

有些人可能会有疑问了,透视不就是把三维物体投影到二维空间上,那为什么不除以z分量?是的,z分量也可以做透视投影,但是z分量作为透视分量后除以z分量,z分量本身会丢失掉,我们希望把z分量和透视分量分离,w分量几何意思就是投影起始点和物体点的距离。

最终4*4的齐次矩阵把坐标变换成齐次坐标,齐次坐标除以w分量就带有透视效果了。

我的理解:4*4的齐次矩阵就是为了解决图形开发中的计算问题而出现

  1. 统一矩阵运算都用乘法
  2. 解决透视问题

转置矩阵

同一个点的坐标写成,而坐标也就是起点为原点的向量,所以左边竖着的叫列向量,右边横着叫行向量

前面提到的,转化的矩阵就是这样,,这种按行排列的矩阵叫行矩阵,矩阵在左边的乘法叫左乘。

而同样的方程还可以写,转换出来的矩阵就是,这种按列排列的矩阵叫列矩阵,矩阵在右边的乘法叫右乘。

这两种表示方式是等价的,行矩阵列向量= 行向量列矩阵,行列方向置换的矩阵就叫做转置矩阵。

在实际OpenGL开发中会要求使用左乘,但是用顶点=行矩阵列向量得出的效果不对,这个时候转置一下矩阵就对了,那么为什么OpenGL中会是顶点=列矩阵列向量?因为刚开始opengl其实使用的是右乘,顶点= 列矩阵行向量,但是后续opengl认为左乘更合适,为了兼容老版本,矩阵内部的顺序不能变,所以最终opengl使用的是顶点=列矩阵列向量,opengl内部做了兼容,运算时候其实会把列矩阵转换成行矩阵去运算。

我的理解: 置换矩阵就是把矩阵的行列方向置换, 我个人觉得在图形开发中转置矩阵可以用来提升矩阵的运算速度

  1. cpu缓存行带来的影响是数组按行取比按列取运算快,而转置矩阵转换行列,所以转置矩阵可以加速矩阵运算。juejin.cn/post/694275…,在opengl中做了优化,两种效率一样的。
  2. 矩阵是正交矩阵的时候,转置矩阵等于逆矩阵,转置矩阵比逆矩阵运算快很多。

基变换

i向量表示

j向量表示

k向量表示

这三个向量就是基向量。

坐标系里面的点都可以用基向量表示,矩阵改变坐标系里面的点其实就是就是改变基向量,这就是基变换。

行矩阵的基向量要竖着看

而Opengl中矩阵是用列矩阵表示的,所以Opengl其实是这样的

列矩阵的基向量要横着看,简单记忆就是OpenGL的原点是在下面的。

举个例子,如果我想将中间的图形变换到右边的图形,矩阵该怎么写呢?

  1. 变换后的原点是(4,4,4)
  2. 变换后红色的i向量是(3,1,0)
  3. 变换后绿色的j向量是(-1,4,0)

所以可推出在opengl中该矩阵是

这里推荐两个矩阵可视化网站

harry7557558.github.io/tools/matri…

shad.io/MatVis/来验证基变换。

我的理解: 基变换就是改变坐标系的基向量和原点来达到改变坐标系每个点的一种函数。

逆矩阵

如果想把右边的图形变成中间的图形,这个矩阵就是中间图形变成右边图形的逆运算,这个矩阵就叫逆矩阵,一般矩阵上面加上标-1表示逆矩阵。

如果用基变换也可以,中间图形的原点和基向量在右边图形的坐标系里面的表示就可以推出逆矩阵,但是直接看坐标系会比较难直接看出来,一般会用高斯消元法。

**我的理解: 逆矩阵就是为了计算一个坐标系的点在另外一个坐标系里面的表示而出现的反向计算。 **

矩阵顺序

一般在opengl中矩阵是左乘,越右边的矩阵越先运算,越先改变物体大小

V代表了每个点的位置,M代表了矩阵

如果从右往左看可以理解成坐标系不变,改变物体

如果从左往右看可以理解成物体不变,改变坐标系

可以理解成M3*M2构成的坐标系里面按M1矩阵移动V这个点

假如我想让一个在A坐标系的物体,按B坐标系运动该如何操作

先把V向量变换到B坐标系,然后按M矩阵运动,然后在用逆矩阵把坐标系变换回来。

我的理解: 矩阵本身是改变坐标系里面的点,改变了点也就改变坐标系了。

常用矩阵

以下矩阵都是列矩阵,在opengl中可直接使用。

位移矩阵

dx、dy、dz代表位移距离

根据基变换,其实就是改变原点的位置

缩放矩阵

Sx、Sy、Sz代表缩放倍数

根据基变换,其实就是改变3个基向量的倍数

绕z轴逆时针旋转矩阵

旋转矩阵里面的角度是按逆时针旋转的,注意如果要操作2D的物体旋转是旋转z轴。

旋转以后i基向量变成了(cosθ,sinθ,0),j基向量变成了(-sinθ,cosθ,0),放到基变换中就推理出旋转矩阵了

绕x轴逆时针旋转矩阵

绕y轴逆时针旋转矩阵

注意这里y轴旋转和z轴,x轴旋转参数有点不一样,y轴旋转的基向量是(cosθ,0,-sinθ),为什么是-sinθ,是因为在右手坐标系里按逆时钟旋转,x轴旋转到z轴的负半轴方向去了。

视图矩阵

eye代表摄像头的坐标

at代表摄像头看向的点坐标

up代表视摄像头朝上的向量

www.cnblogs.com/mikewolf200…

视图矩阵的主要思想就是从摄像头看到的物体和摄像头不动物体反向移动的效果一样,所以就是求视图矩阵就是求物体变换的逆矩阵

正交投影矩阵

left代表变换前物体的左边界

right代表变换前物体的右边界

top代表变换前物体的上边界

bottom代表变换前物体的下边界

near代表变换前物体靠近用户的一面

far代表变换前物体远离用户的一面

正交投影矩阵就是把坐标为[left,right][bottom,top][near,far]的长方体变成[-1,1][-1,1][-1,1]的正方体。

这里有个注意点,靠近用户的一面near,变成最后的坐标是-1,但是在右手坐标系中靠近用户的一面难道不是1吗?之所以做了z轴的反向,是因为其实opengl的空间坐标系是右手坐标系,但是opengl的标准化设备坐标系(NDC)是左手坐标系,所以世界坐标系转换成NDC时候需要反向,我做了一下验证不加任何矩阵,z为-1的点为红色,z为1的点为蓝色,最终显示的是红色,可以证明opengl的NDC坐标系其实是左手坐标系。

主要思想就是先把长方体的中心点移动到坐标系原点,然后再把长方体缩放成单位1的正方体

长方体的中心点

移动到原点需要乘以-1,所以移动矩阵是

原先的x轴上长方体的大小是(right-left),现在是2,所以x轴的缩放比例是2/(right-left),y轴z轴一样,所以缩放矩阵是

所以最终正交投影矩阵就是移动矩阵缩放矩阵,注意这里是移动矩阵缩放矩阵,不是缩放矩阵*移动矩阵,是因为现在用列矩阵表示

透视投影矩阵

left代表变换前物体靠近用户的一面的左边界

right代表变换前物体靠近用户的一面的右边界

top代表变换前物体靠近用户的一面的上边界

bottom代表变换前物体靠近用户的一面的下边界

near代表变换前物体靠近用户的一面

far代表变换前物体远离用户的一面

透视投影矩阵就是把near平面和far平面的四锥体转换成单位长度为1的正方体。

yemi.me/2018/09/09/…

实战

1. 在OpenGL中如何正确显示图片?

如果直接放进去,不做矩阵变换是拉伸的,拉伸是因为显示区域和图像的比例不一样导致的,这个时候可以使用投影矩阵来解决这个问题,当然也可以简单的缩放比例来解决。

为了讨论方便,后续用的图片缩放模式是居中填充模式。

居中填充模式网上的方案是通过计算图像相对于显示区域的边哪个比较大,保持大边不变,按比例缩小小边,但是这样的方案不太容易理解小边的缩放比例为什么是下述图中的比例。

我的方案是这样,把设置物体大小然后把物体投影到屏幕大小的空间中,除以2是因为原来的坐标系-1->1大小是2。

现在我想把物体逆时针旋转30,你发现如果把缩放矩阵现在最右边,旋转的图形拉伸了。

这是因为这个时候物体的大小是textureWidth,textureHeight,大小的比例不等于1,旋转的时候物体比例必须是1,而rotateMat加在sizeMat左边的时候物体其实是-1,1的正方体,当然也可以手动改变比例,然后旋转,然后在把比例改回去,只是下面的做法更简单一点。

这个时候如果想把图片的中心移动到屏幕的右上角,该把位移矩阵写在哪里?

你会发现这个位移矩阵是屏幕大小的绝对值,因为是在屏幕大小的空间坐标系中的是相对于viewWidth和viewHeight,那如果我想相对于屏幕大小,相对于-1,只有放大投影矩阵的左边,这边的坐标系是-1,1

合并所有位移矩阵

如果我把放大矩阵写在旋转矩阵左边,你会发现如果宽高放大比例不一样,图片旋转变形了,这是这个时候时候的物体比例变成0.5了,物体旋转就会变形。

放大矩阵只能写在旋转矩阵的右边,这样尽管图片拉伸了,但是图片本身还是直角,没有变形

在加上camera矩阵就组成常见的mvp矩阵,m指模型矩阵,v指视图矩阵,p指投影矩阵,注意在模型矩阵中offsetMatrotateMatscaleMat顺序不能变,这是因为旋转和缩放物体物体必须位于坐标系的原点

最后想要居中填充显示,需要选择屏幕和物体的宽高比例的较小值进行缩放来保证物体一定是位于屏幕里面的。

要注意各个矩阵的先后顺序,我们来梳理一下矩阵最终为什么这样写?

如果右往左看,矩阵是sizeMatrotateMatoffsetMat*orthoMat,坐标系从来没变过,只有物体大小在发生变化

如果从矩阵从左往右看是这样的,矩阵是orthoMatoffsetMatrotateMat*sizeMat,物体从来没变过,坐标系在不断发生变换

2. 如何对纹理坐标应用矩阵

先把纹理坐标0,1转换成-1,1,然后就可以应用矩阵了,最后在把坐标转换成0,1就可以,但是你会发现放大的矩阵怎么反而缩小了?

因为通过放大纹理坐标,显示区域里面纹理坐标从0-1变成了-0.5-1.5,只有0-1的纹理坐标才有内容,所以看起来反而容易变小了,那为什么放大顶点坐标没有这个问题,因为顶点坐标会有裁剪空间把-1,1之外的内容裁剪掉,显示区域只显示-1,1的内部内容。

从现象看对纹理的操作结果是反的,所以加个逆矩阵可以解决。

3.如果物体不旋转,我在viewoffsetMat平移矩阵里面dx传入-2就刚好能全部移动到外面,但是如果旋转就会有个角留下来,该怎么解决呢?

其实就是求出旋转后物体的红色边框,然后移动屏幕大小的一半再移动红色边框的一半就刚好移动出去。

当然如果仅仅是2d物体的旋转可以简单通过角度算出变宽,但是我希望的是求出一个3D物体通过组合矩阵运算以后物体的边界是什么?

这个在图形学里面叫求物体的包围盒,包围体是为了快速检查物体之间碰撞的一种手段。包围体类型包括球体、轴对齐包围盒(AABB)、有向包围盒(OBB)。我们现在这种是方体并且与坐标系平行的包围盒就是AABB。

求aabb其实就是求原先立方体的八个顶点经过变换以后的最大值和最小值构成的立方体。

zhuanlan.zhihu.com/p/116051685…

所以最终viewOffset传入-1,boxOffset传入-1,相对于先把物体的中心移动到屏幕左边,然后在移动物体包围盒的一半,就刚好把物体移动到屏幕外。

有人可能想拿代码来验证想法,我把代码贴出来 顶点着生器代码 `

precision mediump float;
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
uniform vec2 inputTextureSize;
uniform vec2 viewSize;
uniform float time;


mat4 translate(vec3 t)
{
    return mat4(1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    t.x, t.y, t.z, 1.0);
}


mat4 ortho(float l, float r, float b, float t, float n, float f)
{
    return mat4(
    2.0/(r-l), 0.0, 0.0, 0.0,
    0.0, 2.0/(t-b), 0.0, 0.0,
    0.0, 0.0, -2.0/(f-n), 0.0,
    -(r+l)/(r-l), -(t+b)/(t-b), -(f+n)/(f-n), 1);
}

mat4 scale(vec3 v)
{
    return mat4(v.x, 0, 0, 0,
    0, v.y, 0, 0,
    0, 0, v.z, 0,
    0, 0, 0, 1);
}

mat4 rotate2d(float degree)
{
    float radian = radians(degree);
    return mat4(cos(radian), sin(radian), 0.0, 0.0,
    -sin(radian), cos(radian), 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    0.0, 0.0, 0.0, 1.0);
}

mat4 inverse_mat4(mat4 m)
{
    float Coef00 = m[2][2] * m[3][3] - m[3][2] * m[2][3];
    float Coef02 = m[1][2] * m[3][3] - m[3][2] * m[1][3];
    float Coef03 = m[1][2] * m[2][3] - m[2][2] * m[1][3];

    float Coef04 = m[2][1] * m[3][3] - m[3][1] * m[2][3];
    float Coef06 = m[1][1] * m[3][3] - m[3][1] * m[1][3];
    float Coef07 = m[1][1] * m[2][3] - m[2][1] * m[1][3];

    float Coef08 = m[2][1] * m[3][2] - m[3][1] * m[2][2];
    float Coef10 = m[1][1] * m[3][2] - m[3][1] * m[1][2];
    float Coef11 = m[1][1] * m[2][2] - m[2][1] * m[1][2];

    float Coef12 = m[2][0] * m[3][3] - m[3][0] * m[2][3];
    float Coef14 = m[1][0] * m[3][3] - m[3][0] * m[1][3];
    float Coef15 = m[1][0] * m[2][3] - m[2][0] * m[1][3];

    float Coef16 = m[2][0] * m[3][2] - m[3][0] * m[2][2];
    float Coef18 = m[1][0] * m[3][2] - m[3][0] * m[1][2];
    float Coef19 = m[1][0] * m[2][2] - m[2][0] * m[1][2];

    float Coef20 = m[2][0] * m[3][1] - m[3][0] * m[2][1];
    float Coef22 = m[1][0] * m[3][1] - m[3][0] * m[1][1];
    float Coef23 = m[1][0] * m[2][1] - m[2][0] * m[1][1];

    const vec4 SignA = vec4(1.0, -1.0, 1.0, -1.0);
    const vec4 SignB = vec4(-1.0, 1.0, -1.0, 1.0);

    vec4 Fac0 = vec4(Coef00, Coef00, Coef02, Coef03);
    vec4 Fac1 = vec4(Coef04, Coef04, Coef06, Coef07);
    vec4 Fac2 = vec4(Coef08, Coef08, Coef10, Coef11);
    vec4 Fac3 = vec4(Coef12, Coef12, Coef14, Coef15);
    vec4 Fac4 = vec4(Coef16, Coef16, Coef18, Coef19);
    vec4 Fac5 = vec4(Coef20, Coef20, Coef22, Coef23);

    vec4 Vec0 = vec4(m[1][0], m[0][0], m[0][0], m[0][0]);
    vec4 Vec1 = vec4(m[1][1], m[0][1], m[0][1], m[0][1]);
    vec4 Vec2 = vec4(m[1][2], m[0][2], m[0][2], m[0][2]);
    vec4 Vec3 = vec4(m[1][3], m[0][3], m[0][3], m[0][3]);

    vec4 Inv0 = SignA * (Vec1 * Fac0 - Vec2 * Fac1 + Vec3 * Fac2);
    vec4 Inv1 = SignB * (Vec0 * Fac0 - Vec2 * Fac3 + Vec3 * Fac4);
    vec4 Inv2 = SignA * (Vec0 * Fac1 - Vec1 * Fac3 + Vec3 * Fac5);
    vec4 Inv3 = SignB * (Vec0 * Fac2 - Vec1 * Fac4 + Vec2 * Fac5);

    mat4 Inverse = mat4(Inv0, Inv1, Inv2, Inv3);

    vec4 Row0 = vec4(Inverse[0][0], Inverse[1][0], Inverse[2][0], Inverse[3][0]);

    float Determinant = dot(m[0], Row0);

    Inverse /= Determinant;

    return Inverse;
}
mat4 transpose(mat4 m) {
    return mat4(m[0][0], m[1][0], m[2][0], m[3][0],
    m[0][1], m[1][1], m[2][1], m[3][1],
    m[0][2], m[1][2], m[2][2], m[3][2],
    m[0][3], m[1][3], m[2][3], m[3][3]);
}
mat4 inverse(mat4 m) {
    float
    a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
    a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
    a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
    a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],

    b00 = a00 * a11 - a01 * a10,
    b01 = a00 * a12 - a02 * a10,
    b02 = a00 * a13 - a03 * a10,
    b03 = a01 * a12 - a02 * a11,
    b04 = a01 * a13 - a03 * a11,
    b05 = a02 * a13 - a03 * a12,
    b06 = a20 * a31 - a21 * a30,
    b07 = a20 * a32 - a22 * a30,
    b08 = a20 * a33 - a23 * a30,
    b09 = a21 * a32 - a22 * a31,
    b10 = a21 * a33 - a23 * a31,
    b11 = a22 * a33 - a23 * a32,

    det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;

    return mat4(
    a11 * b11 - a12 * b10 + a13 * b09,
    a02 * b10 - a01 * b11 - a03 * b09,
    a31 * b05 - a32 * b04 + a33 * b03,
    a22 * b04 - a21 * b05 - a23 * b03,
    a12 * b08 - a10 * b11 - a13 * b07,
    a00 * b11 - a02 * b08 + a03 * b07,
    a32 * b02 - a30 * b05 - a33 * b01,
    a20 * b05 - a22 * b02 + a23 * b01,
    a10 * b10 - a11 * b08 + a13 * b06,
    a01 * b08 - a00 * b10 - a03 * b06,
    a30 * b04 - a31 * b02 + a33 * b00,
    a21 * b02 - a20 * b04 - a23 * b00,
    a11 * b07 - a10 * b09 - a12 * b06,
    a00 * b09 - a01 * b07 + a02 * b06,
    a31 * b01 - a30 * b03 - a32 * b00,
    a20 * b03 - a21 * b01 + a22 * b00) / det;
}

mat4 lookat(vec3 eye, vec3 at, vec3 up)
{
    vec3 zaxis = normalize(at - eye);
    vec3 xaxis = normalize(cross(zaxis, up));
    vec3 yaxis = cross(xaxis, zaxis);
    zaxis = -1.0*zaxis;
    mat4 viewMatrix =  mat4(
    vec4(xaxis.x, yaxis.x, zaxis.x, 0.0),
    vec4(xaxis.y, yaxis.y, zaxis.y, 0.0),
    vec4(xaxis.z, yaxis.z, zaxis.z, 0.0),
    vec4(-dot(xaxis, eye), -dot(yaxis, eye), -dot(zaxis, eye), 1.0));
    return viewMatrix;
}

vec3 boxSize(mat4 m, vec3 pmin, vec3 pmax){
    vec4 xa = m[0]*pmin.x;
    vec4 xb = m[0]*pmax.x;
    vec4 ya = m[1]*pmin.y;
    vec4 yb = m[1]*pmax.y;
    vec4 za = m[2]*pmin.z;
    vec4 zb = m[2]*pmax.z;
    float w = m[3][3];
    vec3 vmin =((min(xa, xb)+min(ya, yb)+min(za, zb)+w)/w).xyz;
    vec3 vmax =((max(xa, xb)+max(ya, yb)+max(za, zb)+w)/w).xyz;
    return (vmax -vmin);
}



void main()
{

    float viewWidth = viewSize.x;
    float viewHeight = viewSize.y;
    float textureWidth = inputTextureSize.x;
    float textureHeight = inputTextureSize.y;
    mat4 orthoMat = ortho(-viewWidth/2.0, viewWidth/2.0, -viewHeight/2.0, viewHeight/2.0, -1.0, 1.0);
    float radian = radians(mod(time, 10000.0)/10000.0*360.0);
    mat4 cameraMat = lookat(vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 0.0), vec3(sin(radian), cos(radian), 0.0));
    mat4 sizeMat = scale(vec3(textureWidth/2.0, textureHeight/2.0, 1.0));
    mat4 offsetMat = translate(vec3(0.0));
    mat4 rotateMat = rotate2d(30.0);
    mat4 scaleMat = scale(vec3(0.5, 0.5, 1.0));
    float widthRatio = viewWidth/textureWidth;
    float heightRatio = viewHeight/textureHeight;
    float ratio = min(widthRatio, heightRatio);
    mat4 scaleTypeMat = scale(vec3(ratio, ratio, 1.0));
    mat4 modelMat = offsetMat*rotateMat*scaleMat*sizeMat*scaleTypeMat;
    mat4 mvpMat = orthoMat*cameraMat*modelMat;
    vec3 boxSize = boxSize(mvpMat, vec3(-1.0), vec3(1.0));
    mat4 viewOffsetMat = translate(vec3(sin(radian), cos(radian), 0.0));
    mat4 boxOffsetMat = translate(vec3(0.0, 0.0, 0.0)*boxSize/2.0);
    mat4 transformMat = viewOffsetMat*boxOffsetMat*mvpMat;
    gl_Position =transformMat *position;
    textureCoordinate = inputTextureCoordinate.xy;
    textureCoordinate.y= 1.0 - textureCoordinate.y;
    


    //    gl_Position =position;
    //    textureCoordinate =inputTextureCoordinate.xy*2.0-1.0;
    //    textureCoordinate = (inverse(scale(vec3(1.0, 1.0, 1.0)))*vec4(textureCoordinate, 0.0, 1.0)).xy;
    //    textureCoordinate =(textureCoordinate+1.0)/2.0;
    //    textureCoordinate.y= 1.0 - textureCoordinate.y;

}

`

片元着色器代码

precision mediump float;
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform vec2 inputTextureSize;

void main()
{
    vec4 color  = texture2D(inputImageTexture, textureCoordinate);
    if(textureCoordinate.x<0.0 || textureCoordinate.x>1.0 || textureCoordinate.y<0.0 || textureCoordinate.y>1.0 ){
        gl_FragColor = vec4(0.0,0.0,0.0,0.0);
    }else{
        gl_FragColor = color;
    }

}