8.6 代码集成(Integrating Our Changes)

这一章节最困难的部分已经完成,我们学习了如何使用基本的几何体组合成一些复杂的形体,同时相应的着色器也已经更新了。下一步是把前面的修改集成到AirHockeyRenderer中,同时我们还会学习如何通过添加view矩阵以反映OpenGL中一个比较重要的概念Camera。
    但是为什么我们还需要添加另外一个矩阵呢?我们刚刚开始曲棍球项目的时候并没有添加任何的矩阵,我们首先使用正交矩阵解决了横竖屏切换时的比例问题,然后我们使用透视投影矩阵以获得一种3D投影的效果,接着我们使用模型矩阵移动场景中的物体;其实view矩阵仅仅只是模型矩阵的一种延伸,使用它的目的也是一样的,但是它会移动场景中的所有物体而不是只针对单一物体。
8.6.1 矩阵层级(A Simple Matrix Hierarchy)
    现在复习下将会使用到的三种矩阵,我们将会使用他们把物体绘制到屏幕上:

  • 模型矩阵(Model matrix)

模型矩阵用于把物体放置到世界坐标系(world-space coordinates)中,举个例子:我们的球棍或者冰球的中心一开始位于顶点(0, 0, 0)处,假如没有模型矩阵,那么我们的球棍模型或者冰球模型将会停留在原处(即顶点(0, 0, 0)),假如我们想要移动他们,那么我们就需要自己更新每一个坐标顶点;但是假如有模型矩阵的话则情况就不一样了,只需要把模型矩阵与需要移动的物体的顶点坐标相乘即可。比如假如我们需要把冰球的中心移动到点(5, 5)处,那我们需要做的只是准备相应的模型矩阵然后把它与相关顶点相乘即可。

  • 视图矩阵(View matrix)

使用视图矩阵的目的与使用模型矩阵的目的一样,但是会把这种效果施加于场景中的所有物体。由于它会影响场景中的所有物体,因此它的功能就类似于相机(camera):移动相机你就可以从不同的角度观察物体。
    使用另外一个单独矩阵的一个好处是我们可以提前准备好需要变换的矩阵,举个例子:想像下我们需要旋转场景并移动一个特定的距离,一种方法是首先使用旋转矩阵首先旋转场景,然后再针对场景中的每一个物体使用平移矩阵;虽然刚刚的方法可行,但是更简单的方法是把这些变换保存到一个单独的矩阵中然后再把它应用于场景中的每一个物体。

  • 投影矩阵(Projection matrix)

最后是投影矩阵,这个矩阵的目的是构造3D视角,而且仅仅当屏幕方向改变的时候该矩阵才会改变。
    现在再来复习下一个顶点是如何从它的原始坐标变换到屏幕上的屏幕坐标的:

  • 模型坐标(

这是一个位于模型坐标系中的一个坐标顶点;比如我们的曲棍球桌面所包含的顶点坐标就属于模型坐标。

  • 世界坐标(

这是使用模型矩阵变换后的坐标(使用模型矩阵变换后的坐标就是世界坐标,位于世界坐标系中)

  • 视图坐标(

这是相对于我们的眼睛或者相机的坐标;我们使用视图矩阵(view matrix)把物体移动到一个与我们的视点相关的视野中。

  • 裁剪坐标(

这是使用投影矩阵处理过后的坐标,下一步是进行透视分割。

  • 规范化设备坐标(

这是位于规范化设备坐标系中的坐标;一旦一个坐标位于这种坐标系中,OpenGL将会把它映射到视口上去,然后就你可以在屏幕上看到相应的顶点了。
    下面是刚刚的描述的矩阵链:


    相关矩阵都按照这种顺序相乘就可以得到正确的结果。
8.6.2 修曲棍球桌面(Adding the New Objects to Our Air Hockey Table)
    现在可以在AirHockeyRender加入新的视图矩阵了,同时我们将会使用新的mallet与puck对象,首先增加如下矩阵的定义:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
private final float[] viewMatrix = new float[16];
private final float[] viewProjectionMatrix = new float[16];
private final float[] modelViewProjectionMatrix = new float[16];

我们将会把视图矩阵存储于viewMatrix中,同时另外两个矩阵变量将会存储相应矩阵相乘结果。现在在mallet定义的后面加入如下定义:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
private Puck puck;

下一步是初始化mallet与puck对象,我们将在onSurfaceCreated()方法中使用指定的大小创建他们,代码如下:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
mallet = new Mallet(0.08f, 0.15f, 32);
puck = new Puck(0.06f, 0.02f, 32);

冰球及球棍的半径可以设置成任何值,每一个物体都是使用32个顶点进行构建的。
8.6.3 初始化视图矩阵(Initializing the New Matrices)
    下一步是更新onSurfaceChanged() 并且初始化视图矩阵,现在按照代码更新onSurfaceChanged() 方法:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {// Set the OpenGL viewport to fill the entire surface.glViewport(0, 0, width, height);MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f);setLookAtM(viewMatrix, 0, 0f, 1.2f, 2.2f, 0f, 0f, 0f, 0f, 1f, 0f);
}

第一部分代码比较常规,首先设置视口然后设置投影矩阵;后面的则是新内容,我们调用方法setLookAtM() 创建了一个特殊的视图矩阵。
setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY,
float centerZ, float upX, float upY, float upZ)

float[] rm 这是目的数组,该数组的长度应该至少为16以便它可以存储下视图矩阵。
int rmOffset setLookAtM()方法将会从offset处开始向数组rm中写入数据
float eyeX, eyeY, eyeZ 这是视点(眼睛)处的位置,场景中的所有物件看上去就像是从此位置观察一样。
float centerX, centerY, centerZ 这是眼睛观察的朝向,该位置将会是所绘制场景的中心。
float upX, upY, upZ 说到眼睛,那么这就是你的头顶所指向的位置,upY为1意味着你的头顶方向是垂直向上的。

我们使用参数 (0, 1.2, 2.2)调用方法setLookAtM(),这意味着视点在x-z平面上方1.2个单位并靠后2.2个单位处;换句话说场景中的物体位于你的下方1.2单位及你前方的2.2单位处;中心点(0, 0, 0) 则意味着你向下看向位于你前方的原点处, (0, 1, 0) 则意味着垂直向上并且场景没有旋转。
8.6.4 更新绘制(Updating onDrawFrame)
    在glClear()的后面添加如下代码:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);

这句代码将会把投影矩阵与视图矩阵相乘的结果存储于变量viewProjectionMatrix中,把onDrawFrame()剩余的代码更新为如下:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
positionTableInScene();
textureProgram.useProgram();
textureProgram.setUniforms(modelViewProjectionMatrix, texture);
table.bindData(textureProgram);
table.draw();// Draw the mallets.
positionObjectInScene(0f, mallet.height / 2f, -0.4f);
colorProgram.useProgram();
colorProgram.setUniforms(modelViewProjectionMatrix, 1f, 0f, 0f);
mallet.bindData(colorProgram);
mallet.draw();positionObjectInScene(0f, mallet.height / 2f, 0.4f);
colorProgram.setUniforms(modelViewProjectionMatrix, 0f, 0f, 1f);
// Note that we don't have to define the object data twice -- we just
// draw the same mallet again but in a different position and with a
// different color.
mallet.draw();// Draw the puck.
positionObjectInScene(0f, puck.height / 2f, 0f);
colorProgram.setUniforms(modelViewProjectionMatrix, 0.8f, 0.8f, 1f);
puck.bindData(colorProgram);
puck.draw();

这部分代码与上章节中的代码非常相似,但是也有些不同;首先是在绘制形体之前我们调用了方法positionTableInScene() 及方法positionObjectInScene() ,同时还更新了setUniforms()方法的调用,最后是绘制相应的形体。
    你注意到了这里使用了相同的球棍数据绘制了两个球棍吗?只要你想要我们可以使用相同的顶点数据绘制几百个物体,我们需要做的只是在绘制物体之前更新模型矩阵以移动物体到不同的位置上。
    下一步是定义positionTableInScene(),代码如下:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
private void positionTableInScene() {// The table is defined in terms of X & Y coordinates, so we rotate it// 90 degrees to lie flat on the XZ plane.setIdentityM(modelMatrix, 0);rotateM(modelMatrix, 0, -90f, 1f, 0f, 0f);multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
}

由于桌面最开始由x-y坐标定义的,因此这里围绕x轴旋转了90度,注意这里并不像前面一章把桌面平移了一定距离,因为这里我们想保持桌面位于世界坐标系中的 (0, 0, 0) 处,最后由视图矩阵保证桌面位于我们的视野中(可见)。
    最后一步是把所有矩阵变换集成到一个矩阵中去,这是通过把矩阵viewProjectionMatrix 与矩阵modelMatrix 相乘并把结果存储于矩阵modelViewProjectionMatrix中来实现的,后面我们会把结果矩阵传递给着色器程序。
    下面是方法positionObjectInScene()的定义:

//AirHockeyWithImprovedMallets/src/com/airhockey/android/AirHockeyRenderer.java
private void positionObjectInScene(float x, float y, float z) {setIdentityM(modelMatrix, 0);translateM(modelMatrix, 0, x, y, z);multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
}

由于球棍及冰球都已经定义于x-z平面上,因此这里没有必要进行旋转。这里仅仅只是根据传递进来的参数进行平移以保证物体位于桌面上的正确位置。
    现在可以运行程序了,最后没有什么问题的话效果将会如下图不所示,现在球棍及冰球都已经绘制到屏幕上了,但是现在球棍看起来并不那么真实,这个问题我们将会在第13章进行改善。

这一章终于把一个像样的球棍及冰球绘制出来了,下一章节我们将会添加触摸反馈,并使得我们的曲棍球游戏真正可以交互(点击进入下一章)。
    最后附上本章源代码(点击下载)

Part I 空气曲棍球 Chapter8(8.6 Integrating Our Changes)相关推荐

  1. Part I 空气曲棍球 Chapter8(Building Simple Objects)

    我们的空气曲棍球项目已经捣鼓好久了,现在绘制出来的桌面也是呈现出了一个好视角并且配合纹理映射后看起来更好了:然而由于球棍只是一个点所以看起来并不像真正的球棍,你能想象下使用像一个点一样的球棍打球会是什 ...

  2. Part I 空气曲棍球 Chapter8(8.2 Adding a Geometry Class)

    8.2 定义形体(Adding a Geometry Class) 我们现在已经明确的知道需要些什么去构建冰球及球棍:为了构建冰球,我们使用triangle fan命令构建冰球的顶部,再使用trian ...

  3. Part I 空气曲棍球 Chapter8(8.5 Updating Shaders)

    8.5 更新着色器(Updating Shaders) 我还需要更新相应的着色器,我们之前使用atrribute类型属性定义了球棍或者冰球的顶点坐标,这里我们将会使用uniform类型定义相应的颜色, ...

  4. Part I 空气曲棍球 Chapter8(8.4 Updating Our Objects)

    8.4 更新相应类(Updating Our Objects) 现在我们已经有了一个ObjectBuilder对象,时候更新Mallet类了因为我们不再把它画成一个点了.我们还需要增加新类Puck,在 ...

  5. Part I 空气曲棍球 Chapter8(8.3 Adding an Object Builder)

    8.3 构造形体(Adding an Object Builder) 现在开始创建我们的构建者类,在包com.airhockey.android.objects中创建类ObjectBuilder,并添 ...

  6. Part I 空气曲棍球 Chapter8(8.1 Combining Triangle Strips and Triangle Fans)

    8.1 三角形绘制(Combining Triangle Strips and Triangle Fans) 在开始构建球棍或者冰球之前,我们可以站在一个更高的角度上来想象下,一个冰球可以使用一个扁平 ...

  7. 空气曲棍球 由哪几部分组成_Excel中的曲棍球运动员数据分析

    空气曲棍球 由哪几部分组成 Congratulations to the USA Women's Hockey team, who won the Olympic gold medal. They b ...

  8. 《OpenGL ES应用开发实践指南:Android卷》—— 2.1 为什么选择空气曲棍球

    本节书摘来自华章出版社<OpenGL ES应用开发实践指南:Android卷>一 书中的第2章,第2.1节,作者:(美)Kevin Brothaler ,更多章节内容可以访问云栖社区&qu ...

  9. 《OpenGL ES应用开发实践指南:Android卷》—— 2.3 定义空气曲棍球桌子的结构...

    本节书摘来自华章出版社<OpenGL ES应用开发实践指南:Android卷>一 书中的第2章,第2.3节,作者:(美)Kevin Brothaler ,更多章节内容可以访问云栖社区&qu ...

最新文章

  1. CentOS安装配置之基本
  2. ViewPager+RadioGroup实现标题栏切换,Fragment切换
  3. 计算机组成原理形考任务五答案,计算机组成原理形考任务5
  4. 06 | 案例篇:系统的 CPU 使用率很高,但为啥却找不到高 CPU 的应用?
  5. ABAP SAPGUI 里使用 F4 value help 选择时间
  6. LINUX --基本概念和操作
  7. C语言指针与数组之间的恩恩怨怨
  8. SOLIDWORDS API修改零部件属性全部保存
  9. c语言经典题(期中/期末复习)(xdoj)
  10. 2021-2024年中国两轮电动车企业经营情况对比
  11. 笔记本CPU正常温度是多少?
  12. 用JS写了一个30分钟倒计时器
  13. 一、微信小程序拼团项目简介
  14. 用C语言和JS分别实现“个税年度汇算清缴”计算
  15. css3彩色3D文字上下漂浮动画js特效
  16. Oracle分区之五:创建分区索引总结
  17. 不成熟的男人的爱情观——知乎上另一个关于不成熟男人的见解
  18. 智能云考勤机的舵机模块
  19. python数据本地保存_python保存数据到本地文件的方法
  20. 【超详细】7z的详解和7z的控制台参数说明

热门文章

  1. “三步走”构建全链路数据能力,助力企业全面唤醒数据价值
  2. C语言实现MQTT协议(一)协议讲解
  3. linux大文件分区工具,磁盘分区工具 GParted
  4. windows配置多个NTP服务器地址
  5. 【srs】play.srs.com
  6. Serverless探秘
  7. MSDN Windows XP with SP3 英文版下载
  8. java 一元线性回归_java一元线性回归方程代码怎么理解的
  9. sublime使less产生高亮效果
  10. SpringBoot 动态配置数据源