3D应用程序开发中,一项很重要的工作就是对场景中的物体进行各种投影与变换。相比于OpenGL ES 1.x的封闭模式,OpenGL ES 2.0在变换方面采取了开放模式,其API中不再提供完成各种变换的方法,变换所有的矩阵都由开发人员直接提供给渲染管线。进行投影与变换时,需要一些线性代数的知识。
OpenGL映屏坐标系
在Android系统中,普通View的坐标系是把屏幕的左上角当作坐标原点,向右是x的正方向,向下是y的正方向。OpenGL映屏坐标系是标准的数学坐标系(笛卡尔坐标系),坐标原点是屏幕的左下方。
向右是x的正方向,向上是y的正方向。如下图:
OpenGL统一视图
OpenGL使用一个规格化正方形来表示统一视图,其实就是一个(-1.0,-1.0,1.0,1.0)的Rect.
所有图像都是首先映射到这个统一视图中,然后等比例投影到手机的屏幕上。当然也可以理解,所有屏的大小就是一个规格化的(-1.0,-1.0,1.0,1.0)的Rect.
摄像机的设置
在真实的世界,我们用眼睛看物体,眼睛的位置、姿态的不同,就算是对同一场景进行观察,我们所看到景像也是不一样的。在OpenGL虚拟世界中,会虚拟一个摄像机,来表示人眼观察世界。摄像机的设置来模拟人眼的位置、姿态等信息。
摄像机的设置主要需要给出3方面的信息,包括摄像机的位置、观察的方向以及up方向。
摄像机的位置很容易理解,可以用3D空间的坐标表示。
摄像机的观察的方向可以理解为摄像机镜头的指向,可以用摄像机的位置和观察目标点确定的向量表示。
摄像机的up方向可以理解摄像机为了确定视角的向量。
人眼观察现实世界,也是这样的,如下图:
从上图可以看出,OpenGL虚拟世界模拟现实世界时,摄像机的位置、朝向、up方向可以有很多不同的组合。同样的位置可以有不同的朝向、不同的up方向;不同的位置也可以具有相同的朝向,相同的up方向等。
OpenGL可以通过调用Matrix类的setLookAtM方法来完成对摄像机的设置,代码如下:
Matrix.setLookAtM (
mVMatrix, // 摄像机矩阵
0, // 起始偏移量
cx,cy,cz, // 摄像机的位置 x、y、z的坐标
tx,ty,tz, // 观察目标点 x、y、z的坐标
upx,upy,upz // up向量在x、y、z轴上的分量
);
投影方式
在《OpenGL ES基础》的介绍中,我们知道在图元装配之后光栅化阶段前,首先需要将虚拟3D世界中的物体投影到二维平面上。OpenGL ES 2.0中常用的投影模式有两种,分别为正交投影与透视投影。
投影概念
OpenGL中,根据应用程序提供的投影矩阵,管线会确定一个可视空间区域,称之为视景体。视景体是由6个平面确定的,这6个平面分别为:上平面(up)、下平面(down)、左平面(left) 、右平面(right)、远平面(far)、近平面(near)。
场景中处于视景体内的的物体会被投影到近平面上,视景体外面的物体将被裁剪掉。然后再将近平面的上投影出内容映射到屏幕上的视口中。
正交投影
正交投影是平行投影的一种,其投影线(物体的顶点与近平面上投影点的连线)是平行的,因此它的视景体为长方体,投影到近平面上的图形就是物体的平面镜像。
正交投影的视景体如下图:
正交投影的图形效果如下图:
设置正交投影的代码:
Matrix.orthoM (
mProjectMatrix, // 投影矩阵
0, // 起始偏移量
left,right, // near平面的left、right
bottom,top, // near平面的bottom、top
near,far // near平面、far平面与视点的距离
);
透视投影
现实世界中人眼观察物体会有“近大远小”的效果,因此要想开发出更加真实的场景,仅使用正交投影是远远不够的,这时可以采用透视投影。透视投影的投影线是不平行的,他们相交于视点。通过透视投影,可以产生现实世界中“近大远小”的效果,大部分3D图形采用的都是透视投影。
透视投影的视景体是锥形体。上面介绍投影概念时,使用的就是透视投影。视景体不再复画,投影效果如下:
透视投影的设置代码:
Matrix.frustumM (
mProjectionMatrix, // 投影矩阵
0 , // 起始偏移量
left,right, // near平面的left、right
bottom,top, // near平面的bottom、�top
near,far // near平面、far平面与视点的矩离
) ;
Matrix变换
在《OpenGL ES基础》中说过,数字图像的本质就是一个像素矩阵。因此对数字图像的处理,可以理解为是对矩阵的各种变换。Matrix变换是非常重要的。
Matrix变换的数学知识
Matrix变换都是通过将表示点坐标的向量与特定的变换矩阵相乘完成的,进行变换时,三维空间中点的位置需要表示成齐次坐标形式。所谓齐次坐标形式也就是在x、y、z 3个坐标值后面增加第四个量w,未变换时w值一般为1。这样设计是为了计算方便。
例如:点P(Px,Py,Pz,1)与一个矩阵相乘即可完成一次变换,得到变换后的点Q(Qx,Qy,Qz,1)。
当矩阵M中的元素取适当的值时,等式Q=MP就会有其特殊的几何意义,例如,可以将三维空间中的点P平移、旋转、缩放到点Q。这些变换的具体信息就存放在矩阵M中,因此通常矩阵M为变换矩阵。当对一个图像进行变换时,让图像的所有像素点乘以一个变换矩阵,就是可以得到变换后的另一个图像。
平移变换
缩放变换
放转变换
介绍旋转变换的矩阵格式前,首先需要知道绕坐标轴或任意轴旋转的一些规则。OpenGL中,旋转角度的正负可以用右手螺旋定则来确定。螺旋定则:右手握住旋转轴,使大姆指指向旋转轴的正方向,4指环绕的方向即为旋转的正方向,也就是旋转角度为正值。下图为绕任意轴r的旋转Φ的矩阵:
具体推导过程可以参考:
http://www.cppblog.com/lovedday/archive/2008/01/12/41031.html
一般情况下,对绕任意轴的旋转变化,可以拆分为绕x、y、z轴的旋转变化的组合。
操作代码
/**
* Translates matrix m by x, y, and z in place.
*
* @param m matrix
* @param mOffset index into m where the matrix starts
* @param x translation factor x
* @param y translation factor y
* @param z translation factor z
*/
public static void translateM(
float[] m, int mOffset,
float x, float y, float z) {
for (int i=0 ; i<4 ; i++) {
int mi = mOffset + i;
m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
}
}
/**
* Scales matrix m in place by sx, sy, and sz.
*
* @param m matrix to scale
* @param mOffset index into m where the matrix starts
* @param x scale factor x
* @param y scale factor y
* @param z scale factor z
*/
public static void scaleM(float[] m, int mOffset,
float x, float y, float z) {
for (int i=0 ; i<4 ; i++) {
int mi = mOffset + i;
m[ mi] *= x;
m[ 4 + mi] *= y;
m[ 8 + mi] *= z;
}
}
/**
* Rotates matrix m in place by angle a (in degrees)
* around the axis (x, y, z).
*
* @param m source matrix
* @param mOffset index into m where the matrix starts
* @param a angle to rotate in degrees
* @param x X axis component
* @param y Y axis component
* @param z Z axis component
*/
public static void rotateM(float[] m, int mOffset,
float a, float x, float y, float z) {
synchronized(sTemp) {
setRotateM(sTemp, 0, a, x, y, z);
multiplyMM(sTemp, 16, m, mOffset, sTemp, 0);
System.arraycopy(sTemp, 16, m, mOffset, 16);
}
}