学习本文前建议先学习前文opengl es相关矩阵知识:
解决android opengl es绘制物体屏幕横竖屏切换物体压扁形变以及矩阵相关知识
在上文中我们知道要把空间物体(下图传给顶点着色器的gl_position坐标)最终显示在二维屏幕需要经过归一化设备坐标(在[-1,1]范围),实际opengl经过了具体过程可以用如下流程图来表示:
即两个变换和三个不同坐标空间。
可见从gl_position到归一化设备坐标之间的透视除法是很关键的,opengl具体是怎么做的呢,见下文?
透视除法:
回到我们的初心,opengl要解决的问题是在二维平面显示三维图像,这怎么做到尼?这其实是利用了人们大脑的幻象。
我们可以把相对较远的物体在二维屏幕上显示的小一点,比较近的物体我们在屏幕上呈现大一点,比如看铁路,近的看上去大一点,远了小一点这样就有空间感。那么具体多远多大才合适尼?在解决android opengl es绘制物体屏幕横竖屏切换物体压扁形变以及矩阵相关知识一文中我们提出了w坐标分量,这个w分量就是来解决这个问题的。比如有两个点(1,1,1,1)和(1,1,1,2)只是在w的分量不同,这样用x,y,z分别除以w得到x,y,z如下新坐标(1,1,1)和(0.5,0.5,0.5)这样有较大的w分量就离中心(0,0,0)更近了,也就在屏幕的中间了,可见越远的物体w越大,离屏幕中心越近。这也符合我们之前铁路越远越小,越在屏幕中间的效果。
视口变换:
视口(viewport)概念我们已经在之前很多文章提过了,不熟悉可以参考
openGL 3D图形和openGL简介和android studio上第一个opengl es程序相关基础文章里面的概念。
在这个过程opengl做坐标映射的时候,会把从(-1,-1,-1)到(1,1,1)范围映射到为显示而预留的二维窗口中,这个范围之外的会被归一化坐标裁剪到,不会显示在屏幕上。
扯远了,回到上文我们怎么精确确定w分量?opengl给出了透视投影矩阵:
在Android中代码层面,在Matrxi类提供了Matrix.frustumM()和Matrix.perspectiveM();方法,但是frustumM有缺陷对某些类型投影,perspectiveM是Android4.x才提出的故有兼容版本问题为此,本文代码给出了自己的实现见MatrixHelper类的perspectiveM方法,值得注意的是透视投影矩阵上的值是按照列坐标映射到float[] m的。代码如下:
public class MatrixHelper {
public static void perspectiveM(float[] m, float yFovInDegrees, float aspect,
float n, float f) {
final float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
final float a = (float) (1.0 / Math.tan(angleInRadians / 2.0));
m[0] = a / aspect;
m[1] = 0f;
m[2] = 0f;
m[3] = 0f;
m[4] = 0f;
m[5] = a;
m[6] = 0f;
m[7] = 0f;
m[8] = 0f;
m[9] = 0f;
m[10] = -((f + n) / (f - n));
m[11] = -1f;
m[12] = 0f;
m[13] = 0f;
m[14] = -((2f * f * n) / (f - n));
m[15] = 0f;
}
}
可以比较上图透视投影矩阵截图和这里的代码,坐标对应关系。
在AirHockeyRenderer类的onSurfaceChanged中可以看到使用了该方法 MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width
/ (float) height, 1f, 10f);即从45度的视野创建一个视景体,视景体的z从-1到-10范围。但是其实此时是看不到的,因为视景体在[-1,-10]而桌子默认z在0不在视景体范围内,为此,我们利用模型矩阵移动物体在这个范围。
模型矩阵:
在 onSurfaceChanged方法中我们接着使用 setIdentityM(modelMatrix, 0);
translateM(modelMatrix, 0, 0f, 0f, -2f);方法,setIdentityM是将模型矩阵modelMatrix初始化为单位矩阵,translateM是将其在沿z轴移动-2单位,接着我们把 投影矩阵和模型矩阵相乘放进临时矩阵final float[] temp,即源码中的final float[] temp = new float[16];
multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0); (这相当于对投影矩阵做了这个z轴-2移动), 然后使用 System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);将临时矩阵里面的值在拷贝到投影矩阵projectionMatrix,这是projectionMatrix包含了原来的投影和现在z轴移动-2单位两个效果,之后把这个矩阵传给顶点着色器,完成投影位移。
同时,代码中 translateM(modelMatrix, 0, 0f, 0f, -2.5f);
rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);
是沿z轴-2.5个单位移动,然后使用旋转函数rotateM沿x轴旋转-60度,即物体在y-z平面发生旋转,这样更加有空间感。
本文代码:
https://github.com/pangrui201/OpenGlesProject/tree/master/OpenGlesProject_lesson5