在正式的敲代码之前我们需要了解一下相机(也可以认为是观察者眼睛)、视景体和投影之间的关系,不然在渲染时设置调用一些OpenGL ES API可能会一脸懵逼。具体涉及到的一些概念有:
1. 相机位置、观察方向
2. 近平面(我们的手机屏幕)、远平面(超出会被裁剪,不显示)、和近平面、远平面组成视景体的上下左右坐标
3. 透视投影、正交投影
先来两张图我们再解释概念,分别是透视投影和正交投影
现在我们开始解释上面的概念,首先是相机,即观察者(我们)所处的位置,这是定位;然后是观察方向,即观察者做好位置了,但是还没有确定朝哪边看,这是定向。在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为距离相机的距离,一定 >= 0)
float 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
)
//宽高为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)
下面是当前设置的图示
现在将近平面修改为2.1f
发现啥也没有了,这是为什么呢,原因是已经超出了视景体,那怎么知道有没有超出的,这就和你上面的那些数据有关系了。下面说明一下如何计算的。
观察者位置为+3.0f,我们顶点坐标是在+1.0f的位置,为了让顶点能够显示出来,视景体应该将这个顶点包含在它的空间里,所以近平面坐标必须在[1.0f,3.0f]中,换算成距离观察者的距离,就是[0.0f,2.0f]
我们按照上面的再修改一下近平面,比如0.0f(下图一)、1.0f(下图二)
结果说明我们的计算是正确的,同时也体现了正交投影的特性,无论在视景体中距离近平面远近,投影的大小都是相同的。
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)
现在的效果是这样的
我们现在修改近平面,分别为0.1f(下图一)、1.0f(下图二)、2.1f(下图三)
从上面我们也可以看出,当近平面和相机的距离越来越远,因为顶点坐标是固定的,可以理解是在推近和顶点的距离时,我们投影到近平面的直线越来越大。
本篇文章主要是说明了相机位置及朝向的确定,视景体的组成,其中视景体主要是讲解了近、远平面的修改,读者可以自己试下左上右下坐标的修改,同样也能起到类似的效果。下一节开始正式附上Demo代码,感兴趣的可以下下来玩一下。
参考文章
Android OpenGL ES学习笔记之图形变换
OpenGL ES和坐标变换概述