从2.0开始,opengl es不再提供glRotate()等函数,因此MVP矩阵需要我们自己计算,并赋值给GLSL。
1) 先来看下opengl所用的矩阵的基本知识:
Opengl 使用的是列矩阵,即顶点向量等是用列向量的齐次坐标表示的。
另外其矩阵存储方式是“列主序(column-major order)/列优先”
线性代数意义的同一个矩阵,在d3d 和 opengl 中却有不同的存储顺序
01
02
03
04
05
06
07
08
09
10
11
12
|
线代:a11,a12,a13,a14
a21,a22,a23,a24
a31,a32,a33,a34
a41,a42,a43,a44
d3d : a11,a12,a13,a14
a21,a22,a23,a24
a31,a32,a33,a34
a41,a42,a43,a44
gl: a11,a21,a31,a41
a12,a22,a32,a42
a13,a23,a33,a43
a14,a24,a34,a44
|
矩阵x顶点(记住顺序!!矩阵左乘顶点,顶点用列向量表示)= 变换后的顶点
这里有粗略的介绍
下面来分析一下:
如上图,3个元素集(m0, m1, m2),(m4, m5, m6)和(m8, m9, m10) 是用作欧拉变换和仿射变换,例如1.0中提供的函数glRotate(),缩放glScalef().
注意这三个元素集实际上指得是3个正交坐标系:
(m0, m1, m2): +X 轴,向左的向量(left vector)(相对屏幕自己),默认为(1,0,0)
(m4, m5, m6) : +Y轴,向上的向量(up vector),默认为(0,1,0)
(m8, m9, m10): +Z轴,向前的向量,默认为(0,0,1).
因为使用的是左乘,所以变换的顺序是相对于乘法是逆向的,即最后的变换出现在矩阵相乘之前,最先的变换在最后出现。:
TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;
这行代码最先执行缩放,接着旋转,最后才是平移。
用GLSL表示:
mat4 transform = projectionMat* viewMat * modeMat;
vec4 out_vec = transform * in_vec;
因为model矩阵变换比较简单。所以在这里不再讲解,主要讲解View(又叫相机)矩阵和投影(projection)矩阵的构建
2)View 矩阵(相机矩阵)
先来看下1.0和1.1里面提供的函数
函数原型
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
该函数定义一个视图矩阵,并与当前矩阵相乘。
第一组eyex, eyey,eyez 相机在世界坐标的位置
第二组centerx,centery,centerz 相机镜头对准的物体在世界坐标的位置
第三组upx,upy,upz 相机向上的方向在世界坐标中的方向
你把相机想象成为你自己的脑袋:
第一组数据就是脑袋的位置
第二组数据就是眼睛看的物体的位置
第三组就是头顶朝向的方向(因为你可以歪着头看同一个物体)。
如图,P1为点(centerx,centery,centerz),P2为点(eyex,eyey,eyez),u’为给定的向上向量(upx,upy,upz).
先求出朝向向量(Lookat Vertor)为P2 – P1.用伪代码表示(下同)
Vertor3 lookat = (eyex-centerx,eyey-centery,eyez-centerz);
朝向坐标(Forward axis)只要将朝向向量转为单位向量(Vectors Normalizing)
Vertex3 forward = lookat.normalize();
左坐标由给定的向上向量与朝向向量的叉乘计算得出。
Vertex3 left = (upx,upy,upz)xforward;
向上坐标由朝向坐标与左坐标的叉乘计算得出:
Vertex3 up = forward x left;
注意left,up计算后都需要进行归一化计算;
我们要把一个世界坐标系点K(Kx, Ky, Kz),表示成(U,V,N)坐标系的点(假设此时,已经经过平移操作,摄像机在世界坐标系的原点),则其公式为:
Px Ux*kx + Uy* ky + Uz*Kz
py = Vx*kx + Vy* ky + Vz*Kz
px Nx*kx + Ny* ky + Nz*Kz
于是我们构建出矩阵
|leftx lefty leftx|
|upx upy upy |
|forwardx forwardy forwardz|
加上eye的平移:
3,投影矩阵 Projection Matrix
投影变换矩阵用来完成从眼睛坐标系向裁剪坐标系的变换,然后裁剪坐标系再除以系数w,变换到单位换设备坐标系(NDC)
值得注意的是 在裁剪坐标中, xc, ycand zc 与wc比较. 如果改坐标 小于-wc, 或大于wc, 则该顶点将被抛弃.
1)透视矩阵
在透视投影情况下,视景体空间是眼睛坐标系下形成的一个台柱,这个台柱最终映射到单位化的NDC坐标系上,即:x坐标由[l,r]到[-1,1],y坐标由[b, t]到[-1,1],x坐标由[n, f]到[-1,1]。
同时我们应该注意一点:视觉坐标系是右手坐标系,但是NDC坐标系是左手坐标系。那么,相机是位于原点,向Z轴负方向观察(在视觉空间中),但是,在NDC空间中变成了Z轴正方向。
在OpenGL中,三维视觉空间中一点(xe,ye,ze)被投影在近平面上(xp, yp, zp)上,如下图。
从截锥体上方看, 视野空间(eye space)里的X轴xe 被映射到 xp,可利用相似三角形比例算出:
同理
可见xp和yp的计算都依赖于ze,他们都反比-ze。这是构建投影矩阵的第一条线索。在视野坐标通过右乘投影矩阵的变换后(Projection Matrix),裁剪空间仍然是齐次坐标,它最后通过除以自己的-w变成NDC坐标
,
所以我们将w变量设置为-ze。于是GL_PROJECTION矩阵的第四行就是(0,0, -1, 0)。
接着,将裁剪空间中的xp和yp轴线性关系映射到NDC坐标系, [l, r] ⇒ [-1, 1] and [b, t] ⇒ [-1, 1]。可以推导出:
根据两点求直线方程
两点式:(x-x1)/(x2-x1)=(y-y1)/(y2-y1) 求解。举X计算为例:
加(r,1)(l,-1)带入上式
(Xp-r)/(r-l)=(Xn -1)/2
Xn = 2(Xp-r)/(r-l) +1
Xn = (2Xp-r-l)/(r-l)
然后将裁剪空间中的xp和yp使用视觉空间中的xe和ye代替:
由前面我们将w设置为-ze可知道,上面两个方程的分母就是裁剪空间中的坐标xc和yc。于是有下面的透视矩阵:
至此,我们只需要求解第三行了,求解Zn和Ze变换关系和求解x、y略有不同,因为视觉空间中的ze总是会投影到-n的近裁剪平面上。但是,我们需要的z值必须满足唯一性,以便为了裁剪和深度测试,此外还必须满足可逆变换条件。因为很显然,我们知道z的变换,不依赖x和y,所以,我们将GL_PROJECT矩阵假设为如下表示:
在视觉坐标系中We=1,于是:
为了计算A和B,我们使用(ze, zn) 、(-near, -1)和 (-far, 1)的关系, 带入上面方程:
求出AB,于是:ze 和zn的方程变成
于是完整的透视投影矩阵就出来了:
通常我们所设置的视景体空间都是对称的:l =-r, b=-t。所以上面的投影矩阵就简化为下面的对称投影变换矩阵
最后,我们再看看ze和zn的关系式:
,可见他俩并非是线性关系,就是说在近平面处精确度高,在远平面处精度低。所以如果[-n,-f]设置过大就会面临精确度问题。ze在远平面的小变化无法影响Zn的值 ,所以n 和f之间的距离尽量小以减少这种深度缓冲精确度问题(the depth buffer precision problem).
2)正交矩阵
因为w对应正交投影无意义,所以第四行保持(0,0,0,1),因此矩阵为
如果r=-l,t=-b,将进一步简化矩阵:
备注:
向量归一化分两步:
1 计算出向量的模,即长度
2 将它的各个分量(xy or xyz)除以它的模
|a| = sqrt((ax * ax) + (ay * ay) + (az * az))
x = ax/|a|
y = ay/|a|
z = az/|a|
两个向量a和b的叉积写作a×b(有时也被写成a∧b,避免和字母x混淆)。向量积可以被定义为:
|向量a×向量b|=|a||b|sinθ在这里θ表示两向量之间的角夹角(0° ≤ θ ≤ 180°),它位于这两个矢量所定义的平面上。
这个定义有一个问题,就是同时有两个单位向量都垂直于和:若满足垂直的条件,那么也满足。
一个简单的确定满足“右手定则”的结果向量的方向的方法是这样的:若坐标系是满足右手定则的,当右手的四指从a以不超过180度的转角转向b时,竖起的大拇指指向是c的方向。
向量积|c|=|a×b|=|a| |b|sin
即c的长度在数值上等于以a,b,夹角为θ组成的平行四边形的面积。
c的方向垂直于a与b所决定的平面,c的指向按右手规则从a转向b来确定。
a×b=(aybz-azby)i+(azbx-axbz)j+(axby-aybx)k,为了帮助记忆,利用三阶行列式,写成
b×a= -a×b右手规则