6.7 使用投影矩阵(Switching to a Projection Matrix)
我们现在开始使用透视投影矩阵,打开AirHockeyRenderer并且移除onSurfaceChanged()方法中除了调用glViewport()的所有代码。添加如下代码:
//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width/ (float) height, 1f, 10f);
这将会创建一个视角为45度的透视投影矩阵,相应的z轴坐标开始于-1终止于-10处。
当导入MatrixHelper类并且正确编译运行后,你会发现桌面不见了!因为我们没有为桌面指定z轴位置,它的默认位置是0,而我们的视锥体开始于z轴的-1处,所以我们是不会看到桌面的,除非我们把桌面移入到视锥体范围内。
这里为了不把z的位置写死,我们在使用投影矩阵之前先用平移矩阵把桌面移入视锥体中,按照约定,我们称这个矩阵为模型矩阵。
6.7.1 使用模型矩阵移动物体(Moving Objects Around with a Model Matrix)
在AirHockeyRenderer类的顶部增加如下定义:
//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
private final float[] modelMatrix = new float[16];
我们将会使用这个矩阵把桌面移入到视锥体范围内,在onSurfaceChanged()函数最后添加如下代码:
//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
setIdentityM(modelMatrix, 0);
translateM(modelMatrix, 0, 0f, 0f, -2f);
上面的代码将会把模型矩阵重置为单位矩阵,然后向z轴平移-2个单位;当我们把桌面的顶点坐标与这个矩阵相乘的时候,这些顶点坐标将会向着z轴负方向平移两个单位;这样就可以将会桌面移入视锥体范围内了。
6.7.2 乘一次VS乘两次(Multiplying Once Versus Multiplying Twice)
我们现在有一个选择:我们仍然需要把这个矩阵与每一个顶点相乘,因此我们的第一个选择是把这个矩阵添加到顶点着色器中。首先每一个顶点与模型矩阵相乘,这样的话每个顶点都会沿着z轴负方向平移两个单位,然后再把每一个顶点与投影矩阵相乘以便OpenGL可以做透视分割并且把顶点转化为规范化设备坐标。
上面的方法可行,但是比较麻烦;这里有一个比较好的方法,我们把把模型矩阵与投影矩阵相乘得到一个新的矩阵,然后把这个新矩阵传递给顶点着色器,这样的话顶点着色器还是只有一个矩阵而我们也不需要修改顶点着色器。
6.7.3 矩阵相乘(Matrix Multiplication)
矩阵、矩阵相乘与矩阵、向量相乘相似,假如我们有如下两个矩阵:
我们把第一个矩阵的第一行元素与第二个矩阵的第一列元素相乘相加得到结果矩阵的第一个元素,如下所示:
然后把第一个矩阵的第二行元素与第二个矩阵的第一列元素相乘相加得到结果矩阵的第二个元素,如下所示:
依此类推计算结果矩阵的每一个元素。
6.7.4 矩阵相乘顺序(Order of Multiplication)
我们现在知道如何将两个矩阵相乘,但是我们需要确保两个矩阵是以正确的顺序相乘的;我们可以将投影矩阵放在左边而将模型矩阵放在右边或者将模型矩阵放在左边而将投影矩阵放在右边。
不像普通的乘法,这个顺序非常重要!如果我们把顺序搞错了,那么结果会很奇怪或者我们把什么看不到!下面的例子是两个矩阵相乘的一种顺序:
下面是两个相同矩阵不同相乘顺序:
由此可知,使用不同的顺序结果也不一样。
6.7.5 选择正确的相乘顺序(Selecting the Appropriate Order)
为了确定正确的相乘顺序,我们来看看仅仅使用投影矩阵时的情况:
代表与投影矩阵相乘前的场景中坐标,一旦我们使用模型矩阵移动桌面,运算将会如下所示:
代表使用模型矩阵将其移动到场景中之前的坐标,将这两个表达式联合起来将得到如下表达式:
为了将这两个矩阵替换为一个矩阵,我们把投影矩阵与模型矩阵相乘,投影矩阵在左边而模型矩阵在右边。
6.7.6 使用单个矩阵实现投影(Updating the Code to Use One Matrix)
在onSurfaceChanged()中 translateM()调用的后面添加如下代码:
//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
final float[] temp = new float[16];
multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
当我们将两个矩阵相乘的时候,我们需要一个临时存储区域存储结果,如果直接将他们相乘,那结果将会是未定义的。
我们首先创建了一个临时一维float类型数组来储存计算结果,然后调用multiplyMM()将投影矩阵与模型矩阵相乘并储存到临时数组中。下一步调用System.arraycopy()把临时结果存储到projectionMatrix中,这时该矩阵包含了模型矩阵与投影矩阵的变换功能了。
现在运行我们的运用,将会看到如下效果:
现在已经把桌面绘制出来了,但却是从一个垂直角度看上去的效果;现在我们简单复习下这一节学了些什么,在下一节中我们将学习如何旋转桌面使得视角有一个角度而不是垂直的。
6.7.7 小节(A Quick Recap)
现在简单复习下在这一节中我们学习了些什么:
1) 在把顶点转递给投影矩阵之前如何使用另外一个矩阵------模型矩阵把桌面移动到视锥体范围内。
2) 两个矩阵如何相乘
3) 如何把模型矩阵与投影矩阵整合起来,这样我们就不需要修改着色器来接收另外一个矩阵(并且避免了着色器运行时两个矩阵都会相乘的计算开销)