说起OpenGL的矩阵变换,我是之前在我们的项目天天P图、布丁相机中开发3D效果时才比较深入地研究了其中的原理,一直想写这篇文章,由于很忙(lǎn),拖了很久,再不写我自己也要忘了。 一开始时,也只是知道怎么去用这些矩阵,却不知道这些矩阵是怎么得来的,当出现一些莫名其妙的问题时,如果不了解其中的原理,就不知道如何解决,于是想彻底搞懂其中的原理,还好自己对数学挺有兴趣,于是从头到尾把推导过程研究了一遍,总算掌握了其中的奥秘,不得不佩服OpengGL的设计者,其中的数学变换过程令人陶醉,下面我们一起来看看。 这些矩阵当中最重要的就是模型矩阵(Model Matrix)、视图矩阵(View Matrix)、投影矩阵(Projection Matrix),本文也只分析这3个矩阵的数学推导过程。这三个矩阵的计算OpenGL的API都为我们封装好了,我们在实际开发时,只需要给API传对应的参数就能得到这些矩阵,下面带大家来看看究竟是怎样计算得到的。
什么是OpenGL的矩阵变换
我们先来看一张经典图:
这张图相信很多同学在学习OpenGL的过程中都看到过,它比较直观地展示了OpenGL矩阵变换的过程,下面我详解一下其中的含义:
完成投影变换就需要靠投影矩阵,即图中的PROJECTION MATRIX
以上就是一个完整的矩阵变换过程,里面最重要的就是MVP三个矩阵,M即模型矩阵(Model Matrix),V即视图矩阵(View Matrix),P即投影矩阵(Projection Matrix),本文将针对这三个矩阵的来由详解其中的数学推导,其中投影矩阵只讲解透视投影矩阵,因此它比较常用且其推导过程比正交投影矩阵复杂得多。
模型矩阵(Model Matrix)推导
相信大家在数学中都学过平移、缩放、旋转三种基本变换,将模型放到世界坐标系中就是利用这三种变换的组合来实现的,我们来看一下平移、缩放、旋转三种变换对应的矩阵:
1)绕x轴旋转
2)绕y轴旋转
3)绕z轴旋转
大家可以看到旋转变换有三个矩阵?为什么不写成一个,注意绕轴旋转的先后顺序不同,最终的结果可能是不一样的,因此有三个独立的矩阵,根据实际情况组合。
模型矩阵相对来说简单一些,相信大家还能回忆起来之前学数学时的知识,就是通过将平移、缩放、旋转三种矩阵的组合实现将模型以某种姿态、某种大小放到世界坐标系的某个地方。
视图矩阵(View Matrix)推导
前面提到过,视图矩阵对应Camera的位置、朝向的点坐标、以及Camera的上方向向量,我们先来看一张图:
下面我们来看看怎样通过Camera的位置、朝向的点坐标、以及Camera的上方向向量得到对应的View Matrix,首先给Camera定一个坐标系:
NUV这三个向量是怎么来的呢?我们将Camera的坐标记为eye,朝向的点坐标记为lookat,上方向向量记为up,那么:
N向量: eye - lookat
U向量:up X N并归一化
V向量:N X U并归一化
我们要把Camera以某种姿态放在世界坐标系中的某个地方,这个放的过程就是对应Camera的旋转和平移,这里表示为TR,其中T表示平稳变换矩阵,R表示旋转变换矩阵。
我们虽然设置的是Camera,但最终动的是点坐标,因为Camera压根就不存在,是一个假想的东西。假设我们不动摄像机,动坐标点,那么对坐标点的变换就应该是对相机变换的逆变换T^-1R^-1
(就是对TR整体求逆矩阵),注意,这里的T^-1R^-1
看起来貌不惊人,实际上就是我们要求的View Matrix。
根据前面的知识,我们能很容易得到T^-1:
这个直观上也好理解,比如本来是平移Tx
,逆过来就是平移-Tx
,依此类推。
再回顾一下我们的目标T^-1R^-1
,现在还差R^-1
,现在再次回到我们假想的Camera,前面说要对它做TR,当做完R后,Camera会旋转至某个姿态:
XYZ和UVN都可以看成是一组基,根据线性代数公式可将一个点在XYZ基下的坐标转成在UVN基下的坐标,R就相当于是把基XYZ变换成UVN的变换矩阵,其中:
假设:
则有:
于是:
由于R是正交矩阵,有性质:R^-1=R^T
(R^T
代表R的转置),为什么R是正交矩阵?Tips:方阵A正交的充要条件是A的行(列) 向量组是单位正交向量组。
于是:
现在我们T^-1
和R^-1
都有了,T^-1R^-1
也就是最终的View Matrix可以很容易地计算出来了,因为OpenGL中坐标是4维的,所以这里将矩阵写成4*4的:
下面是投影矩阵的推导,是最为复杂的一个矩阵,前面提到,投影矩阵是由视野决定的,而视野又是由近平面、远平面和视角决定的,我们把视野在坐标系中画出来,请看下图:
简单起见,我们不妨把Camera摆在原点,让它朝z轴负方向来讨论问题。
h表示近平面高度
w表示近平面宽度
n表示Camera到近平面的距离
f表示Camera到远平面的距离
P代表视野中的一个点
那么接下来要求的投影矩阵,就是能将P点正确地投影到近平面上,设P(x0, y0, z0)
,我们从y轴正向往负向看,即看xoz平面,看到的画面是这样的:
假设投影后的x坐标为x1
,由三角形相似原理则易得:
同理有:
设l和r分别为近平面左、右边框的x坐标,则有l=-w/2,r=w/2
,投影归一化后坐标范围为-1~1,最左边是-1,最右边是1,l和r归一化至-1~1是线性变换,于是列一个kx+b
类型的方程组并解得k和b:
令xn
表示点P的x坐标投影归一化后的值,代入kx+b
得:
同理可得点P的y坐标投影归一化后的值yn:
下面我们来构造带有未知数的投影矩阵然后求解它们,设待投影点为(x0,y0,z0,1)
,我们先来构造投影矩阵的第一第二行:
这里强调一个细节,投影矩阵仅帮我们完成投影变换,不会归一化,上面的x2、y2、z2
指的是投影后归一化前的值,还记得前面计算的xn
和yn
吗?我们用一个括号把其中一个部分括了起来,外面乘了一个因子(-1/z0)
,后面会说这个因子是什么东西,现在只需要知道,x2、y2
实际上就是前面括号里那堆东西,所以上面投影矩阵的第一行和第二行就自然能轻松地构造出来。
接下来就构造第三第四行,我们先看第四行,第四行计算的结果是投影后的第四维坐标,也就是w,前面提到了归一化,而OpenGL的归一化操作就是通过将坐标除以其对应的w值来完成的,再回头看我们前面计算的xn
和yn
,它们是归一化后的值。
还记得括号外面乘了一个因子(-1/z0)
吗?乘(-1/z0)
可以看成是除以-z0
,因此希望w
就是-z0
,于是构造第四行让w的计算结果为-z0
:
接下来就是最复杂的第三行,如何去构造第三行?第三行有4个值,现在都不知道是什么,我们需要构造4个未知数吗?对于解方程来说,在能解决问题的情况下,未知数能少就尽量少,不然只会徒增烦恼。
这里其实不需要4个未知数,为什么呢?那就要理解z2
这个值是什么东西,它就是投影之后未归一化的深度值,而深度和x0、y0
没有关系,这个如何理解?就是说我把一个东西放在左,上边,还是右边,不影响它的深度,要改变深度需要前后移动。
既然z2
和x0、y0
没有关系,那么x0、y0
不管是什么值,都不会影响z2
的值,因此用0去乘x0、y0
,即第三行的第一第二个元素是0。
再看第三行的第三第四个元素,我们假设第三个元素是0,会发生是什么?那么z2就等于B,而B最后求出来放到矩阵中肯定是一个定值,这就意味着z2也是定值,于是z2就无法表示不同的点的不同深度,这不是我们想要的结果,因此第三个元素不能是0,是一个待求的未知数。同理,我们假设第四个元素是0会发生什么?这样投影矩阵第四列全是0,根据线性代数的知识,这个矩阵行列式等0,它必定不可逆,而我们希望投影矩阵是可逆的,这样我们可以对坐标做一些逆变换来实现一些特殊的功能,因此第四个元素也不能是0,于是设它为一个未知数。
这样,我们就构造出了一个包含未知数A和B的投影矩阵:
下面就是求解A和B:
我们将z0
为-f
和-n
代进去,-f
就是远平面,-n
就是近平面,求归一化后的坐标,-f
最远,深度最深,归一化后是1,反之,-n
代进去后是-1,注意,深度是值越大越深,于是有:
可解得:
于是投影矩阵为:
至此,我们就完成了模型矩阵(Model Matrix)、视图矩阵(View Matrix)和投影矩阵(Projection Matrix)的数学推导,可以看到里面的变换还是很精彩的,原来神秘的矩阵变换过程已经清晰可见,希望能对大家有帮助!谢谢!
文章后记 天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习!
加入我们 天天P图技术团队长期招聘: (1) 深度学习(图像处理)研发工程师(上海) 工作职责
工作要求
(2) AND / iOS 开发工程师 (3) 图像处理算法工程师 期待对我们感兴趣或者有推荐的技术牛人加入我们(base 上海)!联系方式:ttpic_dev@qq.com