最初这篇教程我并不打算作为第9章发布,原计划是第10章。在深入了解Opengl ES 2.0 和着色器之前,我想讨论下更基础的:动画。
注意:你可以在这里找到这篇教程的配套 代码,新版本的代码已经在西部时间10:14更新了,更新的代码里面修正了一个不能动画的错误。
目前为止,想必你已经看过了opengles最基本的动画形式。通过随时间改变rotate, translate, scale(旋转、移动和缩放)等,我们就可以使物体“动起来”。我们的第一个项目 the spinning icosahedron就是这种动画的一个例子。我们把这种动画叫做简单动画。然而,不要被“简单动画”这个名称迷糊,你可以实现复杂的动画,只需要随时间改变一下矩阵变换。
但是,如何掌握更加复杂的动画呢?比如说你想让一个人物行走或者表现一个被挤压正要反弹的球。
实际上这并不困难。在OpenGL了里面有两种主要实现方法:关键帧动画和骨骼动画。在这章里面我们谈论关于帧动画的话题,下一章(#9b)里面,我们将要谈论的是骨骼动画。
Interpolation & Keys
动画只不过是随着时间改变每个顶点的位置。这是是动画的本质。当你移动、旋转或缩放一个物体的时候,你实际上是移动了一个物体的所有顶点。如果你想让一个物体有一个更复杂、精细的动画,你需要一个方法按设置时间移动每个顶点。两种动画的基本原理是存储物体关键位置的每一个顶点。在关键帧动画中,我们存储独立关键位置的每一个顶点。而骨骼动画,我们存储虚拟骨骼的位置信息,并且用一些方法指定哪个骨骼会影响动作中的哪些顶点。
那么什么是关键帧?如果要最简单的方法说明他们,我们还得回到他们的起源,传统逐格动画,如经典的迪斯尼和华纳兄弟的卡通。早期的动画,一个小的团队就能完成所有的绘画工作。但是随着产品的慢慢变大,那变得不可能,他们不得不进行分工。比较有经验的漫画师成为 lead animator (有时叫关 key animator )。这些有经验的画师并不画出动画的每一格,而是绘制更重要的帧。比如说一个极端的运动或姿势,体现一个场景的本质。如果要表现一个人物投掷一个球的动画,关键帧是手臂最后端时候的帧,手臂在弧线最顶端的帧,和人物释放球体的帧。
然后, key animator 会转移到新场景 而 另一个 in-betweener (有时叫 rough in-betweener )会算出关键帧之间的时间间隔,并完成这些关键帧之间帧的绘画。比如一个一秒钟的投掷动画,每秒12帧,他们需要指出怎样在首席动画师绘制的关键帧中间完成剩下的9帧。
三维关键帧动画的概念也是一样。你有动作中 关键 位置的顶点数据,然后插值算法担当rough in-betweener的角色。 插值 将是你在三维动画里面用到的最简单的数学算法。
或许我们看一个实际的例子会更明白一点。让我们只关注一个顶点。在第一个关键帧,假设是在原点(0 ,0, 0)。第二个关键帧,假设那是在(5、5、5),并且在这两个关键帧之间的时间间隔是五秒(为了计算方便)。
动画的一秒钟,我们只需要表现出这一秒前后两个顶点在每个坐标轴上的变化。所以,在我们的例子中,两个关键帧在x,y,z轴总共移动了5个单位(5减去0等于5)。一秒钟的动画走了1/5的路程,所以我们添加5的1/5到在第一关键帧的x,y,z轴上面,变成(1, 1, 1)。目前数值算出来的过程并不优雅,但是数学算法是一样的。算出总距离,算出与第一关键帧之间流逝的时间比例,两种相乘再加上第一关键帧的坐标值。
这是最简单的插值,叫 线性插值 ,适用于大部分情况。更加复杂的算法,要权衡动画的长度。例如在Core Animation中,提供了几种"ease in", "ease out", or "ease in/out"等几种选项。也许我们会在以后的文章中讨论非线性插值。不过现在,为了保持简单易懂,我们继续讨论线性插值。你可以通过改变关键帧的数量和它们的时间间隔,完成绝大多数动画。
Keyframe Animation in OpenGLES
让我们看一个OpenGL中简单动画的例子。当一个传统的手工绘画师被训练以后,他们做的第一件事情就是做一个能够被挤压的而且正在反弹的小球。这同样适合我们,程序会像下面这样:让我们用 Blender (或者任何你想用的3d程序,如果你有方法输出vertex , normal data的数据用人工的方法。在这个例子里面我会用 Blender export script ,它能生成一个有顶点数据的头文件)创建一个球。
我开始在原点创建一多面体,并且重新命名为Ball1,然后我保存这个文件。使用我的脚本渲染并且输出ball1。你可以在这里找到这个帧的 渲染文件 。
现在,我们按另存为(F2)保存一个Ball2.blend的副本。我重命名为Ball2以便于输出脚本使用不同的名字命名数据类型。接着点击 tab键进入编辑模式,点击A移动和缩放球体上的点,直到球体被压扁。保存压扁的球然后输出到Ball2.h。 你可以在这里找到压扁的球的 资料 。
到这里,我们有两个头文件,每个文件里面都包包含着我的动画里面要用到的每个帧的顶点数据。从 my OpenGL ES template 开始工作,我先在 GLViewControler.h定义了一些新的值,它能帮助我追踪小球的运动。