OpenGL 3D世界

3D应用程序开发中,一项很重要的工作就是对场景中的物体进行各种投影与变换。相比于OpenGL ES 1.x的封闭模式,OpenGL ES 2.0在变换方面采取了开放模式,其API中不再提供完成各种变换的方法,变换所有的矩阵都由开发人员直接提供给渲染管线。进行投影与变换时,需要一些线性代数的知识。

OpenGL映屏坐标系

在Android系统中,普通View的坐标系是把屏幕的左上角当作坐标原点,向右是x的正方向,向下是y的正方向。OpenGL映屏坐标系是标准的数学坐标系(笛卡尔坐标系),坐标原点是屏幕的左下方。
向右是x的正方向,向上是y的正方向。如下图:

坐标对比.png

OpenGL统一视图

OpenGL使用一个规格化正方形来表示统一视图,其实就是一个(-1.0,-1.0,1.0,1.0)的Rect.


Screen Shot 2018-12-16 at 4.23.08 PM.png

所有图像都是首先映射到这个统一视图中,然后等比例投影到手机的屏幕上。当然也可以理解,所有屏的大小就是一个规格化的(-1.0,-1.0,1.0,1.0)的Rect.

摄像机的设置

在真实的世界,我们用眼睛看物体,眼睛的位置、姿态的不同,就算是对同一场景进行观察,我们所看到景像也是不一样的。在OpenGL虚拟世界中,会虚拟一个摄像机,来表示人眼观察世界。摄像机的设置来模拟人眼的位置、姿态等信息。

摄像机的设置主要需要给出3方面的信息,包括摄像机的位置、观察的方向以及up方向。

Screen Shot 2018-12-16 at 4.51.06 PM.png
  • 摄像机的位置很容易理解,可以用3D空间的坐标表示。

  • 摄像机的观察的方向可以理解为摄像机镜头的指向,可以用摄像机的位置和观察目标点确定的向量表示。

  • 摄像机的up方向可以理解摄像机为了确定视角的向量。

人眼观察现实世界,也是这样的,如下图:

Screen Shot 2018-12-16 at 10.53.38 PM.png

从上图可以看出,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)。

Screen Shot 2018-12-16 at 11.24.39 PM.png

场景中处于视景体内的的物体会被投影到近平面上,视景体外面的物体将被裁剪掉。然后再将近平面的上投影出内容映射到屏幕上的视口中。

视景体_20190103203905.jpg

正交投影

正交投影是平行投影的一种,其投影线(物体的顶点与近平面上投影点的连线)是平行的,因此它的视景体为长方体,投影到近平面上的图形就是物体的平面镜像。

正交投影的视景体如下图:

Screen Shot 2018-12-16 at 11.46.08 PM.png

正交投影的图形效果如下图:

Screen Shot 2018-12-16 at 11.47.45 PM.png

设置正交投影的代码:

Matrix.orthoM (
mProjectMatrix,           // 投影矩阵
0,                              // 起始偏移量
left,right,                      // near平面的left、right
bottom,top,                  // near平面的bottom、top
near,far                       // near平面、far平面与视点的距离
);

透视投影

现实世界中人眼观察物体会有“近大远小”的效果,因此要想开发出更加真实的场景,仅使用正交投影是远远不够的,这时可以采用透视投影。透视投影的投影线是不平行的,他们相交于视点。通过透视投影,可以产生现实世界中“近大远小”的效果,大部分3D图形采用的都是透视投影。

透视投影的视景体是锥形体。上面介绍投影概念时,使用的就是透视投影。视景体不再复画,投影效果如下:

透视投影效果.png

透视投影的设置代码:

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)。

矩阵变换.png

当矩阵M中的元素取适当的值时,等式Q=MP就会有其特殊的几何意义,例如,可以将三维空间中的点P平移、旋转、缩放到点Q。这些变换的具体信息就存放在矩阵M中,因此通常矩阵M为变换矩阵。当对一个图像进行变换时,让图像的所有像素点乘以一个变换矩阵,就是可以得到变换后的另一个图像。

平移变换

平移变换.png

缩放变换

缩放变换.png

放转变换

介绍旋转变换的矩阵格式前,首先需要知道绕坐标轴或任意轴旋转的一些规则。OpenGL中,旋转角度的正负可以用右手螺旋定则来确定。螺旋定则:右手握住旋转轴,使大姆指指向旋转轴的正方向,4指环绕的方向即为旋转的正方向,也就是旋转角度为正值。下图为绕任意轴r的旋转Φ的矩阵:

1342760515_3105.jpg

具体推导过程可以参考:

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);
        }
    }

你可能感兴趣的:(OpenGL 3D世界)