一.前言
想象一下,你在游戏厅和朋友玩空气曲棍球游戏,从你的视角看,空气曲棍球桌是什么样的?你的那一端桌子会显得较大,因为你是从一个角度向下看桌子的,而不是俯视桌子,我们在上一篇文章中所写的程序就是俯视视角下的,在这片文章中,我们将走进三维,让绘制的桌子更符合实际的视角。
二.透视除法
在一个顶点坐标成为归一化设备坐标之前,其实还进行了一个额外的步骤,它被称为透视除法。还记得我们在之前的文章中提过一下顶点坐标的w分量吧,它就是用于作透视除法用的。为了在屏幕上创建三维的幻象,OpenGL会把每个gl_Position的x,y,z分量除以w分量,当w分量表示距离的时候,就使得较远处的物体被移动到离渲染区域中心更近的地方,这个中心的作用就相当于一个消失点。
举个例子,有两个坐标(1,1,1,1),(1,1,1,2),在OpenGL将他们转化为归一化坐标之前,会先进行透视除法,每个分量都除以w分量,这两个坐标整除后变为(1/1,1/1,1/1)和(1/2,1/2,1/2),这个除法之后,归一化设备坐标就是(1,1,1)和(0.5,0.5,0.5)。有较大的w值的坐标被移动到离(0,0,0)更近的位置,(0,0,0)就是归一化设备坐标里渲染区域的中心。
三.添加w分量创建三维图
如果我们实际添加一下w分量,可以更直观的发现它所产生的影响。因为我们现在要指定一个位置的x,y,z和w分量,所以我们先要更新一下代码中的POSITION_COMPONENT_COUNT变量,将值改为4。下一步,我们需要更新顶点数组,修改后的值如下:
val tableVertices=floatArrayOf(
//Triangle fan
0f,0f,0f,1.5f,1f,1f,1f,
-0.5f,-0.8f,0f,1f,0.7f,0.7f,0.7f,
0.5f,-0.8f,0f,1f,0.7f,0.7f,0.7f,
0.5f,0.8f,0f,2f,0.7f,0.7f,0.7f,
-0.5f,0.8f,0f,2f,0.7f,0.7f,0.7f,
-0.5f,-0.8f,0f,1f,0.7f,0.7f,0.7f,
//Mid Line
-0.5f,0f,0f,1.5f,1f,0f,0f,
0.5f,0f,0f,1.5f,1f,0f,0f,
//Mallets
0f,-0.4f,0f,1.25f,0f,0f,1f,
0f,0.4f,0f,1.75f,1f,0f,0f
)
我们给每个顶点都加入了w分量,靠近屏幕底部的w的值为1,而靠近屏幕顶部的w的值为2,其他的顶点的w的值也从底部到顶部逐渐增加。而对于z值,我们全部设为零即可,产生的效果应该是桌子的底部看起来更宽些,桌子的顶部看起来更窄些,就像我们从远处观看一样。可以运行程序看看效果,是否和我们所预想的那样。
四.使用透视投影
我们加入w分量后,桌子看上去更像三维了。然而,如果我们希望这些物体更加动态,比如改变桌子的角度,放大或缩小,该怎么办呢?那么我们就不能指定w的值,我们要用矩阵来生成这些值。把我们刚刚做的改动都去掉,接下来我们将用矩阵来生成同样的效果。
现在我们可以将onSurfaceChanged函数中glViewport()后的代码全部删除,然后使用Matrix类当中的perspectiveM()函数生成一个透视投影矩阵,这个函数的定义如下:
public static void perspectiveM(
float[] m,//存储透视投影矩阵
int offset,//数组开始存储投影矩阵的偏移值
float fovy, //视场垂直角度
float aspect, //宽高比
float zNear,//近裁剪面距离
float zFar//远裁剪面距离
)
我们需要加入的代码如下:Matrix.perspectiveM(projectionMatrix,0,45f,width.toFloat()/height.toFloat(),1f,10f)
此时运行程序时,我们什么也看不到,因为我们没有指定z值,那么z值就默认为0,而我们刚刚通过perspectiveM函数将z值的范围指定为了[-10,-1],所以什么也看不到。此时,我们可以利用模型矩阵将桌子移动到这个范围内,首先我们得在MyRenderer内中定义一个变量用于存储模型矩阵:private val modelMatrix:FloatArray=FloatArray(16)//存储模型矩阵
然后,加入如下代码:
//生成模型矩阵
Matrix.setIdentityM(modelMatrix,0)//设置为单位矩阵
Matrix.translateM(modelMatrix,0,0f,0f,-2f)//将z值平移到可见范围内
val temp:FloatArray=FloatArray(16)//存储矩阵相乘的结果
Matrix.multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0)
System.arraycopy(temp,0,projectionMatrix,0,temp.size)//将temp复制到projectionMatrix
现在,我们运行这个应用程序,可以看到桌子了,但是是以俯视的视角看的,我们需要旋转一下桌子,从而以正常的视角看向桌子,并且因为旋转之后,桌子的底部会离我们更近,我们可以让桌子离我们稍微远一些,这样的效果更好,修改之后的代码如下:
//生成模型矩阵
Matrix.setIdentityM(modelMatrix,0)//设置为单位矩阵
Matrix.translateM(modelMatrix,0,0f,0f,-3.5f)//将z值偏移-3.5
Matrix.rotateM(modelMatrix,0,-60f,1f,0f,0f)//绕x轴旋转-60度
val temp:FloatArray=FloatArray(16)//存储矩阵相乘的结果
Matrix.multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0)
System.arraycopy(temp,0,projectionMatrix,0,temp.size)//将temp复制到projectionMatrix
现在,运行程序,就可以看到三维场景下的空气曲棍球桌子了。