OpenGL ES系列四--相机、视景体、投影之间的关系

相机(观察者眼睛)、视景体、投影之间的关系

在正式的敲代码之前我们需要了解一下相机(也可以认为是观察者眼睛)、视景体和投影之间的关系,不然在渲染时设置调用一些OpenGL ES API可能会一脸懵逼。具体涉及到的一些概念有:

1. 相机位置、观察方向
2. 近平面(我们的手机屏幕)、远平面(超出会被裁剪,不显示)、和近平面、远平面组成视景体的上下左右坐标
3. 透视投影、正交投影

先来两张图我们再解释概念,分别是透视投影和正交投影

OpenGL ES系列四--相机、视景体、投影之间的关系_第1张图片

OpenGL ES系列四--相机、视景体、投影之间的关系_第2张图片

现在我们开始解释上面的概念,首先是相机,即观察者(我们)所处的位置,这是定位;然后是观察方向,即观察者做好位置了,但是还没有确定朝哪边看,这是定向。在Android Java层,我们会调用Matrix.setLookAtM()函数来设置相机和观察方向,具体如下:


public static void setLookAtM(
        //m为接收相机变化的矩阵,后面要传递到着色器中进行坐标系的变换.偏移量mOffset写0即可
        float[] rm, int rmOffset,
        //eye理解为照相机或者你的眼睛,默认位置在原点(0,0,0)
        float eyeX, float eyeY, float eyeZ,
        //center是观察物体的坐标,从eye到center的向量就是观察的方向
        float centerX, float centerY, float centerZ,
        //up默认为Y轴正方向,可以理解为眼睛到头顶的方向,比如(0,-1,0)那就是倒立着观察了,(0,1,0)就是正立观察
        float upX, float upY,float upZ)

然后是和视景体相关的近、远平面,上下左右坐标,这六个平面会一起组成一个视景体,从图一,图二就可以看出来了,不过这里根据不同的投影有不同的设置方法,分别是正交投影和透视投影,两个函数如下:

//正交投影
public static void orthoM(
        //m为接收投影变化的矩阵,后面要传递到着色器中进行坐标系的变换.偏移量mOffset写0即可
        float[] m, int mOffset,
        //视锥体的左上右下坐标(以近平面为标准)
        float left, float right, float bottom, float top,
        //近平面、远平面(这里的near、far为距离相机的距离,一定 >= 0float near, float far)

  //透视投影(参数含义和正交投影是一样的)
  public static void frustumM(float[] m, int offset,
            float left, float right, float bottom, float top,
            float near, float far) 

这里先说明一下正交投影和透视投影的区别,然后贴几张参数不同的效果图来展示区别。

正交投影:简单理解就是无论投影物体离你是远是近,只要在视景体之内就能呈现,并且大小不会发生变换

透视投影:透视投影的话,也是在视景体之内才能呈现,不过效果确实近大远小

下面贴一下代码和效果图我们这里以绘制两条直线为例,直线的顶点坐标是固定的,如下:

 //顶点坐标数组(每三个一组,共四组,对应四个顶点坐标)
val vertexIndexArray = floatArrayOf(
    //x,y,z
    -1.0f, 1.0f, 1f,
    1.0f, 1.0f, 1f,
    1.0f, -1.0f, 1f,
    -1.0f, -1.0f, 1f
)
  1. 正交投影
//宽高为GLSurface.Render实现类,重写onSurfaceChanged函数中的宽高
//计算宽高比
val r = width.toFloat() / height

//设置相机
//eye理解为照相机或者你的眼睛,默认位置在原点(0,0,0)
//center是观察物体的坐标,从eye到center的向量就是观察的方向
//up默认为Y轴正方向,可以理解为眼睛到头顶的方向,比如改成(0,-1,0)那就是倒立着观察了
Matrix.setLookAtM(mVMatrix, 0,
                //相机坐标,默认是(0,0,0)即手机屏幕正中央,这里我们往z轴正方向移动3个单元距离。因为center还是(0,0,0),所以可以理解为面向观察者移动
                0f, 0f, 3f,
                0f, 0f, 0f,
                0f, 1f, 0f)

//正交投影(组成视景体的上下左右坐标,以及近平面、远平面)
Matrix.orthoM(mProjMatrix,0,
              //设置左右坐标
              -2*r, 2*r,
              //设置上下坐标
              -2f, 2f,
              //设置近平面、远平面(为离相机的距离)。
              0.5f, 10f)

下面是当前设置的图示

OpenGL ES系列四--相机、视景体、投影之间的关系_第3张图片

现在将近平面修改为2.1f

OpenGL ES系列四--相机、视景体、投影之间的关系_第4张图片

发现啥也没有了,这是为什么呢,原因是已经超出了视景体,那怎么知道有没有超出的,这就和你上面的那些数据有关系了。下面说明一下如何计算的。

观察者位置为+3.0f,我们顶点坐标是在+1.0f的位置,为了让顶点能够显示出来,视景体应该将这个顶点包含在它的空间里,所以近平面坐标必须在[1.0f,3.0f]中,换算成距离观察者的距离,就是[0.0f,2.0f]

我们按照上面的再修改一下近平面,比如0.0f(下图一)、1.0f(下图二)

OpenGL ES系列四--相机、视景体、投影之间的关系_第5张图片

OpenGL ES系列四--相机、视景体、投影之间的关系_第6张图片

结果说明我们的计算是正确的,同时也体现了正交投影的特性,无论在视景体中距离近平面远近,投影的大小都是相同的。

2.透视投影

//宽高为GLSurface.Render实现类,重写onSurfaceChanged函数中的宽高
//计算宽高比
val r = width.toFloat() / height

//设置相机
//eye理解为照相机或者你的眼睛,默认位置在原点(0,0,0)
//center是观察物体的坐标,从eye到center的向量就是观察的方向
//up默认为Y轴正方向,可以理解为眼睛到头顶的方向,比如改成(0,-1,0)那就是倒立着观察了
Matrix.setLookAtM(mVMatrix, 0,
                //相机坐标,默认是(0,0,0)即手机屏幕正中央,这里我们往z轴正方向移动3个单元距离。因为center还是(0,0,0),所以可以理解为面向观察者移动
                0f, 0f, 3f,
                0f, 0f, 0f,
                0f, 1f, 0f)

//透视投影(组成视景体的上下左右坐标,以及近平面、远平面)
Matrix.frustumM(mProjMatrix,0,
              //设置左右坐标
              -2*r, 2*r,
              //设置上下坐标
              -2f, 2f,
              //设置近平面、远平面(为离相机的距离),不过透视投影近平面不能 <= 0。
              0.5f, 10f)

现在的效果是这样的

OpenGL ES系列四--相机、视景体、投影之间的关系_第7张图片

我们现在修改近平面,分别为0.1f(下图一)、1.0f(下图二)、2.1f(下图三)

OpenGL ES系列四--相机、视景体、投影之间的关系_第8张图片

OpenGL ES系列四--相机、视景体、投影之间的关系_第9张图片

OpenGL ES系列四--相机、视景体、投影之间的关系_第10张图片

从上面我们也可以看出,当近平面和相机的距离越来越远,因为顶点坐标是固定的,可以理解是在推近和顶点的距离时,我们投影到近平面的直线越来越大。

本篇文章主要是说明了相机位置及朝向的确定,视景体的组成,其中视景体主要是讲解了近、远平面的修改,读者可以自己试下左上右下坐标的修改,同样也能起到类似的效果。下一节开始正式附上Demo代码,感兴趣的可以下下来玩一下。

参考文章

Android OpenGL ES学习笔记之图形变换

OpenGL ES和坐标变换概述

你可能感兴趣的:(OpenGL-ES)