OGRE 动画 收藏
动画是由若干静态画面,快速交替显示而成。因人的眼睛会产生视角暂留,对上一个画面的感知还末消失,下一张画面又出现,就会有动的感觉。计算机图形学中的动画也同样遵循着这一本质的原理。只不过不同于传统动画的手绘和拍摄,Ogre图形引擎可以通过自动或半自动的方式实时产生并播放动画图片。
不过Ogre本身并不能像人一样了解场景中动画角色动作的具体含义,例如它不能明确的知道什么是抬手或者走路,只能简单的从数学角度处理顶点的位移和旋转。我们可以通过
离线工具来生成具体的动画文件,也可以通过实时的运算产生动画,比如可以实时产生摄像机的运行轨迹。
Ogre会根据你提供的参数来更新动画,一般来说这个参数指的是时间(你也可以用其他的变量)。Ogre系统根据不同的时间运算并渲染出不同的画面,不论是否场景中真的有动画角色,画面会不间断的在每一帧渲染到屏幕(亦或者其他渲染目标)。同时上一帧画面会被清理,包括释放所使用的GPU资源。这种动画的运作方式本质上来说和手工的动画工作是没有什么区别,只不过是实时的进行而已。在这一章节中,让我们开始精彩的动画之旅。(译注:英文原文中这一段文字中还包括介绍电影的诞生以及华纳公司如何绘制兔八哥的细节,但翻译过来略显突兀,所以省去。如果你对这些信息抱有兴趣,可以到网络上搜寻,但对Ogre的使用应该没有太大帮助。)
Ogre所支持的动画类型
通常的情况下,Ogre有两种不同的操作动画对象的方法,一种是通过关键帧,另外一种是通过控制器。
动画
在一个较高的抽象层次上来讲,Ogre的动画通常是一系列运动轨迹的集合。其中的每一个动画轨迹通过时间函数的方式储存了一系列数据(输入时间参数,返回相应数据)。通过一个指定的“关键”时间所得到的所有轨迹数据的值组成为一个关键帧(用通俗的说法,关键帧是“在某一时间对物体所有位置、方向、缩放的采样”)。依赖于不同的动画类型,Ogre系统中也支持多种的关键帧类型。而且在后面你还会进一步了解,Ogre中也有多种不同的运动轨迹类型用来储存相应类型的关键帧数据。
提示:关键帧的概念源自手绘动画动画行业(比如华纳兄弟的兔八哥)。高级动画工程师会提供一些“关键”的画面给手下的助手绘图师。这些关键帧提供了目标的关键姿势和具体位置。而工程师手下的助手们把那些关键画面中间的“中间帧”绘制出来,从而实现连贯动画效果。在使用Ogre过程中,你就好比是那个高级动画工程师,把关键的画面通过“关键帧”的方式提供给系统,Ogre作为你的助手计算出关键帧之间的画面。而现实中这个过程是这样的:一般来说你(或者你的美工)会通过离线的3D建模工具(比如Maya,或3D Studio Max等)声称关键帧动画文件,Ogre通过这些文件的数据对骨骼位置和网格顶点进行插值计算,从而使你的动画可在Ogre场景中与在离线3D建模工具中一样运动。
Ogre支持下面几种动画轨迹类型(在同一轨迹中的所有关键帧必须使用相同类型)。
•数字动画轨迹(NumericAnimationTrack):与数字关键帧(NumericKeyFrame)对应,每个关键帧中都保存了相应的数字数据。这里使用了AnyNumeric数据类型来保存这些数值。在Ogre自身有一种特殊的数据结构被称为Any,它很类似动态语言中的可变类型,可以用来储存各种C++类型的数据。AnyNumeric是Any的子类,负责储存各种数值类型,比如实数和整形。
•节点动画轨迹(NodeAnimationTrack):与变换关键帧(TransformKeyFrame)对应,每个关键帧都包含了两个三元向量和一个四元数,分别用来表现节点在当前帧的位置、缩放以及方向。
•顶点动画轨迹(VertexAnimationTrack):同时对应于顶点变形关键帧(VertexMorphKeyFrame)和顶点姿态关键帧(VertexPoseKeyFrame),每个关键帧都保存了特定时间的顶点位置数据,在姿态动画(Pose)中还保存了顶点混合权重。
动画状态
你的应用程序控制Ogre中动画的的主要途径就是“动画状态”。你可以通过离线建模工具把骨骼或者模型动画根据不同的时段划分成许多小的动画部分。在从工具中导出的时候,每个动画部分都被定义了自己的名字,以便与程序调用。在代码中你可以通过这些名字来调用实体中的某个动画部分,而物体则返回相应的动画状态以供你使用。其中包括以下动画属性:
•长度:以秒为单位,获得动画片段的长度。
•当前位置:得到或者设置当前播放的位置,换句话说是从动画片段(而不是整个动画)开始到当前位置所流逝的时间(秒)。
•动画名称:尽管你可以通过物体本身得到所有的动画列表,这里仍然提供只读属性的动画名称用来在不知道动画名字情况下调用。
•循环:设置或者得到在动画片段结束后是否循环播放。
•启用:设置或者得到这个动画是否启用。
•权重:动画可以被混合(但有相应的一些限制,在后面介绍)。这个属性可以设置或者得到当动画与其他动画混合的时候的影响的权重。
动画混合权重依赖于不同动画混合类型而有不同的执行效果。
•平均混合权重:当你指定Ogre使用平均权重混合(ANIMBLEND_AVERAGE)的时候,权重的加和必须为1.0。如果你的权重加和不是1.0,Ogre会把当前所有权重加和规格化为1.0。这可能会导致意外的状况发生,例如这样一个已经分配的权重为0.7,那么它只能与另外一个权重为0.3的混合。同样的两个动画混合,每个权重为1.0:他们混合的时候每个都只能是原来的一半。平均混合权重只对骨骼动画有用,并且是默认的混合方式。
•累积混合权重:当使用ANIMBLEND_CUMULATIVE的时候,Ogre会简单的计算所有混合权重的累积,不去平均。累积混合是顶点动画(包括变形和姿态)唯一能够使用的混合方式,但是骨骼动画也可以使用它。
动画状态也可以实现动画的快进(或者后退,通过使用负数的时间数值),这是通过调整“时间间隔”(Time Delta) 来实现的。所谓“时间间隔”,其意思就是从上一帧结束到这一帧画面开始的时间。因此,通过“时间间隔”来控制动画可以和直接改变动画播放位置是想相同的效果。总之这是Ogre的做法,通过在当前时间加上“时间间隔”来得到新的动画位置。
骨骼动画
骨骼动画可能是最常用的动画类型了。在使用骨骼动画的时候,模型中的顶点数据被绑定到(绑定工作通常是在3D模型工具中完成)有多块骨头组成的骨骼上(就如同你身体上的肌肉和骨骼的关系)。和你身体里的骨骼不同的是,在动画中并没有真实存在的骨头;它们被定义为移动、旋转和缩放这种“无实体”的空间变换。如果你有机会查看导出的骨骼数据文件,你会发现里面并没有提及任何几何体,也同样没有提及任何实际的骨骼信息。取而代之的是空间变换数据(正确的说,应该被称为“矩阵调色(Matrix Palette)”),虽然接下去我们仍然把这些数据称为“骨头”,但是你现在至少知道了它们到底是什么东西。事实上骨骼动画正确定名字应该叫做矩阵调色蒙皮(Matrix Palette Skinning)技术,我们把顶点数据作为表皮贴在骨头表面上。
骨骼动画的作用是通过表示骨头的一个矩阵来影响所绑定顶点的位置而实现的(当一个点受多个骨头影响时,也可以是多个矩阵)。而所谓骨骼动画关键帧,就是对时间轴上的某些关键点对骨头位置、方向以及缩放的取样而得来。
骨骼
在Ogre中,骨骼被定义为一个类似多叉树一样的父子结构。就如同我们所知道的常识,“足骨联结到小腿骨头上,而小腿骨联结到膝盖骨头上”诸如此类。通常来说,一个骨骼中所有的骨头都会有连接到上一级骨头上,如同树枝一样,最终连接到根部,我们姑且称之为“根骨头”。根骨头没有父节点,在使用中我们可以指定任何一块骨头作为根骨头,但是只能有一个;骨头的运动会传递到所有下一级的骨头,然后依次传递。举例来说,如果你移动你的肩膀上的骨头,就会影响你的手臂上的骨头,进而也会影响你的手腕上面的。这种结构来源于现实世界中骨骼的概念,进而也能很好的模拟真实骨骼的作用。
注意:可能在谈论这些问题的时候,你会想到目前比较流行的逆向运动学(IK)。不过在这里先不要考虑它;目前Ogre内部并不支持IK的实现,如果你非要使用的话,就需要通过自己的方法来把IK转换为普通的关键帧动画。如果你希望进一步了解IK,可以参考Wikipedia:http://en.wikipedia.org/wiki/Inverse_kienematics。
在空间变换的领域中,骨骼中的骨头和场景中的节点有着相同的情况:改变任何一个骨头都会影响所有子骨头的状态。前面说过,Ogre本身不支持IK,骨头的变换不会反过来影响父骨头。
顶点绑定
为了让模型的每一个顶点能随着骨骼中的一个或者多个骨头的运动而产生动画,我们必须把顶点“绑定”到骨骼上面。这个工作一般来说都是交给3D模型工具来完成(比如Maya或者3D Max)。其中包括分配顶点到特定的骨头上以及设置相应的权重,这里的权重指的是骨头对指定顶点的影响效果。举例来说,如果指定一个骨头对当前顶点的权重为1.0的话,就等于说这块骨头移动时完全的控制了这个顶点的变形效果(在这里,变形是一种表示顶点移动的术语)。但如果把两块骨头分别对当前顶点分配0.5的权重,这样的结果是这两块骨头的移动都会影响到这个顶点,并且影响力度相同,不过比起分配1.0权重的时候,现在能有一半的影响力。
把一个顶点绑定到多个骨头上是一种比较好的模拟身体肌肉和骨头关系的方式。在这里我们可以通过肩膀和胳膊来举例,当你抬起胳膊的时候肩膀上面的肌肉也会受到一定的影响。换句话说,肩膀和胳膊上的骨头都会影响肩膀上的肌肉。(顺便提及一下,肩膀部分是3D人体模拟中最复杂的部分。)在应用中,就等同于把肩膀上的顶点分别通过不同的权重绑定到肩膀和胳膊这两块骨骼上。
Ogre的一个顶点只能被绑定到四块骨头,但一块骨头却可以影响任意数量的顶点。
顶点动画
相对于骨骼动画中通过骨头的移动间接控制顶点,顶点动画采用直接对顶点位置进行计算的方法来实现。你一定能想象的到,顶点动画是一种资源密集型(Resource-Intensive)方案,在执行的过程中会把所有(在模型资源中)动画需要移动的顶点数据进行拷贝,这项工作会耗费大量资源。不过,某一些动画效果只能通过这种方法实现(比如脸部动画效果)。
顶点动画在动画轨迹层被管理。这意味着你能够让网格的不同部分使用不同的动画类型运动。例如,你能够使用相对复杂(但是灵活)的姿态动画(在后面的部分会介绍)移动模型的头和脸,同时使用相对简单但是不灵活的变形动画(下一部分介绍)移动模型的其他部分。但是,你不能够应用不同类型在同一个顶点上:比如,你无法同时应用一个姿态动画和另外一个变形动画轨迹在相同的嘴部顶点上面。
对于被顶点动画所能影响的顶点集合遵守着全或无定律(All or None),这意味着在模型(Mesh)级别上,所有模型的“共有几何体(shared eometry)”都包含着顶点动画的数据,在子模型(SubMesh)级别里,所有子模型的顶点都包装着顶点动画的顶点数据。这就意味着,你无法只在某个模型的一部分混合顶点动画的(例如,头部模型的一部分使用变形动画,而其他部分使用姿态动画)。
变形动画(Morph Animation)
变形动画是两种顶点动画中是比较简单和直接的。和骨骼动画通过骨头绑定计算来得到顶点位置的方法不同,顶点动画通过对物体运动的快照(在变形动画中被称为变形目标“Morph Targets”)来得到关键帧顶点的实际位置。这种方法与现实与我们在电视上看到的传统动画的实现方法是类似的:调整场景中模型的位置,然后把所有顶点位置储存为关键帧。可以想象得到,这是一种“资源密集型”的操作,因为每一帧都包含了对所有顶点的全部拷贝。然而,这样的执行方法却是高效率的,因为它只需要简单的把每一帧进行线性插值就可以得到全部动画效果,而不用进行复杂的运算。
变形动画的主要缺点是无法把多个动画混合成为一个,这是因为变形动画储存的是顶点在场景中的绝对位置,这样就无法对同一个顶点通过简单的相加得到混合的结果(例如,你无法对同一个手部模型同时使用两套变形动画)。不过两套变形动画分别影响不通的顶点的话(比如两套动画分别控制胳膊和手),你才能够混合它们。
姿态动画(Pose Animation)
姿态动画中所提及的姿态类似变形动画里面“变形目标” 的概念。不过可以用来被混合成为更复杂(并且真实)的动画。姿态动画中和变形动画的不同之处在于储存的是顶点的相对位置而不是绝对位置。此外,只有被改变的顶点才会被动画记录(与变形动画中对所有顶点取样不同),这样做的结果不仅会让姿态动画更加灵活多变,并且也能节省一些资源。
我们可以在Ogre提供的FacialAnimation演示程序中,可以看到多个发音口形以及表情动画的混合效果。在图9-1中我们展示了其中几个画面的截图,展示了通过混合多个动画混合成特殊的表情。
图9-1:头像(a)展示了普通的表情(默认)的姿势,(b)是高兴的姿势,(c)是悲伤的姿势,(d)是悲伤(b)和高兴(c)的混合姿势。
我们在图9-1中展示了混合多种姿态来创建更有趣和复杂的面部表情。这上面,我们通过把悲伤和高兴两种截然不同的感情姿态混合成为一个复杂的姿态。在这里请注意一下,最后的混合结果中眉头的位置和悲伤姿态相同,这是因为高兴的姿态中没有改变眉头的位置,而知改变了嘴形,所以最终结果只收到了悲伤姿态的影响。最后的嘴部同时被两种表情影响,产生了一种中间的结果(哭笑不得)。
在姿态动画中,关键帧指定了一个或者多个姿态轨迹,并且为每个姿态分配一个影响权重。这个权重被用来决定在最终混合的时候顶点偏移到这个姿态的幅度。这个权重的影响力度是根据全部的权重来分配的。在使用的过程中,你只需要在一帧中引用这个姿态,它就会一直持续这个影响;如果某个姿态在关键帧n中产生作用,在n+1中失效,这时候就需要再n+1中重新设置这个姿态的权重为0。
对使用姿态动画时候有一些经验上的限制:对混合姿态数量的增加会消耗更多地执行时间。类似的状况,如果使用交叉混合的话(比如从A和B混合的姿态帧过渡到C和D混合的下一关键帧)会同时执行4个活动轨迹,而不是2个。这可能和你期望的结果有些不同。(在硬件加速的方法中,这是一个很重要的问题,我们在后面会作详细讨论。)
动画混合(Blending Animations)
在Ogre中,你可以混合多种类型的动画来创建更复杂的动画。唯一例外的是你无法把变形动画混合到姿态动画上面,或者混合多个变形动画。这是因为变形动画的实现方式而产生的结果,在这里可以回忆一下前面的章节,这里就不再重复了。
当把顶点动画混合到骨骼动画的时候(任何类型),Ogre会首先计算顶点动画的结果,然后在应用骨骼进行空间变换到最终位置。
对于姿态动画和骨骼动画的混合,比较常用且标准的做法是:用简单且高效的骨骼动画控制整个动画角色的整个身体动作(包括头部模型),而复杂的面部表情则通过顶点姿态动画来控制。
通常来说,混合多个骨骼动画(一般只有两个)对创建动画之间转换过渡也是非常有用处的。例如,你所控制的动画角色希望从“跑”的动画过渡到“停止”动画。如果直接关掉“跑”的话,会觉得整个过程相当笨拙(中间没有过渡动作),这个时候就需要使用骨骼动画之间的混合工作。进而实现从“跑”逐渐过渡到“停止”的一系列动作。
通过把变形动画和骨骼动画混合,可以得到一种给骨骼动画角色覆盖变形“罩子”的效果。例如,有时候你希望动画角色的衣服可以随风摆动,这时候就可以考虑通过骨骼动画控制角色本身,而摆动的衣服则交给变形动画处理。在很多情况下,这种组合可以很好的替代耗时的即时模拟计算。
硬件动画vs.软件动画
到目前为止,我们所提及的所有动画类型都可以通过软件的方式执行(换句话说,在CPU中执行),并且这也是默认情况下的执行方式。并且,所有的动画类型也可以通过GPU顶点程序来加速执行。这一个平衡GPU和CPU之间计算负载的好办法,当移交给GPU的时候,每种动画都有一些相应的注意事项。
警告:通过硬件执行动画加速时有一点很值得注意,硬件动画中的模型数据无法被CPU使用。这是一个极其重要的问题,特别是你需要对模型进行某些计算的时候,诸如软件实现的Shadow-Volume(阴影锥)或者以CPU或PPU实现的物理模拟技术。一旦顶点变换的计算工作被交给GPU来执行的时候,你后面的代码就无法得到这些顶点的数据了。如果你非常需要再CPU中重新得到这些顶点数据,你只能在CPU中执行和GPU相同的顶点变换计算;这时候你可以通过Entity::addSoftwareAnimationRequest()方法来通知Ogre进行这种操作。
通过硬件加速的骨骼动画的顶点动画的混合过程也需要在GPU的顶点程序中执行。如果你不在意之前描述的Ogre混合顺序(先顶点后骨骼),这样做就能带来的额外好处是可以自己手动控制实际的混合顺序。然而,这意味着更长也更复杂的GPU程序。并且混合的动画必须都是使用硬件加速的(换句话说,你无法混合软件的顶点动画和硬件加速的骨骼动画)。
骨骼动画
骨骼动画是一种相当适合通过顶点程序进行GPU加速的动画类型。这种执行方式比软件蒙皮显得更加真实(特别是场景中对需要大量蒙皮的动画对象)。不过对某一些细节需要特别注意。
骨骼动画的本质就是一种根据骨骼中所有骨头数据计算每一个顶点位置的算法。首先面临的就是各种等级的图形硬件所带来的限制,具体点说就是早期的图形硬件可能每个渲染通路(Pass)中只支持24个骨头甚至更少(支持DirectX8等级的图形硬件,例如NVIDIA GeForce3/4 Ti-serie)。如果你需要让你的程序支持更广泛的图形硬件的话,你就需要在程序中作适当的限制。当然你也可以把程序所需求的最低配置要求提高一点(比如高级一点的图形硬件,例如GeForce FX,支持每个渲染通路中85个骨头)。如果希望在低等级硬件中突破每个渲染通路24块骨头的限制,你就需要把模型网格拆分成多个独立的片断通过多个渲染通路传递到图形硬件中。Ogre在传递骨骼数据的时候针对的是子模型(SubMesh)数据而不是整个骨骼中的所有骨头,进而拆分骨头到多个渲染通路是一种可行的方法。
如果你希望你的程序可以运行在不支持分支运算的GPU环境下,你就需要同是维护多个顶点着色程序,其中每个程序对应在你程序中被不同数量骨骼影响的顶点运算。这是因为你程序中的顶点绑定的骨骼数量是不定的,导致计算顶点变形所需要参照的骨骼数量也是不一定的。如果你在你程序的说明中写上“至少需要支持分支运算的显示硬件”这句话,这个问题就不重要了。否则,你就需要维护多个程序,当然,你也可以简单的命令你的美工把每个顶点绑定的骨骼集合数量限制固定,不过这同时可能意味着每个顶点都会尽可能绑定个多的骨骼,最终导致GPU运算周期的浪费。
变形动画
在GPU中实现硬件加速的顶点变形动画的好处很明显,因为GPU固有其平行运算的能力,可以同时支持多个顶点位置被改变(在CPU运算中,就算你能有一颗高性能双核且超线程的处理器,在同一周期内你也只能执行四个变形运算)。前面我们提到过,硬件的变形动画并不复杂,只需要对同一个顶点同时提供两个顶点位置数据(通常来说,变形目标会被放置在第一个自由纹理单元中,作为TEXCOORD参数传入顶点着色程序)和一个内插因子(Interpolation Factor)就可以了,然后程序会把改变后的顶点位置输出。
之前我们说过,如果混合硬件加速的顶点动画,骨骼动画业必须是硬件加速实现的。同时在这里我们也知道你不能把一个顶点程序运算的输出传给另外一个顶点(至少是不能直接传递),你必须在同一个顶点着色程序中实现变形动画运算和矩阵调色蒙皮运算,进而输出最终的混合结果。
姿态动画
再GPU中执行的姿态动画与GPU中的变形动画基本上是相同的情况,并且与骨骼动画的混合也有着相同的限制。主要的区别在于提供给顶点着色程序姿态的数量的不同,以及它们如何被传递(携带着姿态对应的权重)。和硬件变形动画类似,在第一个自由纹理单元中传递了第一个姿态,之后紧跟着其他的姿态参数。而相应的权重则通过一个或更多的常量参数传递给顶点着色程序。这里我们给出了一个在Ogre演示中使用的例子,代码9-1展示了在FacialAnimation演示程序中使用的Cg程序的源代码(可以在Samples/Media/materials/programs/Example_Basic.cg找到相应的程序)。
代码 9-1:Demo_FacialAnimation使用硬件姿态动画的Cg程序
void hardwarePoseAnimation(float3 pos : POSITION,
float4 normal : NORMAL,
float2 uv : TEXCOORD0,
float3 pose1 : TEXCOORD1,
float3 pose2 : TEXCOORD2,
out float4 oPosition : POSITION,
out float2 oUv : TEXCOORD0,
out float4 colour : COLOR,
uniform float4x4 worldViewProj,
uniform float4 anim_t)
{
// interpolate
float4 interp = float4(pos + anim_t.x*pose1 + anim_t.y*pose2, 1.0f);
oPosition = mul(worldViewProj, interp);
oUv = uv;
colour = float4(1,0,0,1);
}
在代码9-1中,我们通过第二个和第三个自由纹理单元传递进来另外两个需要混合的姿态,并通过anim_t参数传递进来相应的混合权重。
挂载点(Tag Points)
从本质上来说,挂载点并不在动画的概念中。它主要被用来挂载任意的对象(比如武器)到一个已经存在的动画模型以及骨骼上(比如第一人称射击游戏中的角色)。在这里讨论的原因是它和骨骼动画中的有着密切的联系(毕竟它被挂载到了骨头上)。
挂载点的原理其实很简单:它只不过是作为骨骼中的一个特殊的骨头而存在,不同的是可以在运行的时候控制其绑定到上面的对象的位置和方向。挂载点可以在运行时后绑定,因此没有必要预先创建,这个工作是通过Entity类型中的attachObjectToBone()方法来实现的,执行的结果可以把任何活动对象绑定到目标骨骼中的任何一个骨头上。一旦活动物体被绑定到挂载点上,它就会随着挂载点一同移动。需要注意的是活动物体并没有真正的绑定到挂载点上面,而是通过骨骼连接到了实体上。另外,如果你需要的话,可以修改调整绑定活动对象的位置和方向。
控制器
如同关键帧动画对模型的控制,控制器能够控制角色的移动。换句话说,控制器通过一个任意的函数来针对场景中节点的运动进行控制,
控制器通过计算的结果来对一个或者更多的活动物体进行位置、方向以及缩放等操作。进而代替关键帧数据的轨迹操作。
动画 vs. 控制器
在操作一个活动物体的时候如何取舍动画和控制器呢?它们有很强的相似性,都能够操作活动物体的状态,都接受一维的输入数据,在动画的时候这个输入数据总是时间,而在控制器的情况下可以使任意你所希望的数据。通常来说,动画和控制器的运行都是通过一个输入数据,然后转换为输出结果。
下面列出了动画所具有的特征:
·对于动画而言,输出是根据给定的时间值通过两个插值算法中的一个来计算出来的动画轨迹(以动画轨迹中的关键帧为基础)。输出的结果被用来驱动动画,其中既包括对顶点和骨骼动画中顶点和骨头的移动、旋转以及缩放,也包括对场景中的节点的移动、旋转以及缩放。
·动画可以被看作为一个封闭的系统;你提供给Ogre系统输入值,Ogre根据这个值更新动画关联的物体相应的空间属性(动画必须关联到一个物体上,可以是“可动画”物体或者顶点数据)。
·动画的插值方式包括:线性插值和三次样条插值。
·Ogre提供了动画相关的一系列支持工具(其中包括3D导出插件以及网格/骨骼转换工具等,见附录A)。
下面列出了控制其所具有的特征:
·控制器可以对给定的输入提供更加灵活的输出计算方法:你必须提供具体的计算函数,因此你也可以得到更高的自主控制性。
·控制器之间可以被连接起来使用(一个控制器的输出可以被作为另外一个控制器的输入),而动画则不可以这么做(动画只能提供改变了的模型数据,而不能够被其他动画作为输入使用)。
·控制其本身并不能操作任何物体:你必须借助输出关联到控制对象。例如,如果你需要的话,可以把控制器的输出结果用来作为一个动画的驱动输入。另外你也可以把它用作纹理动画的使用;Ogre中所的纹理动画都支持通过控制器来驱动执行。
•控制器可以在每一帧自动更新(而动画却需要你在代码中每一帧前或者每一帧后“压入”它)。
上面所有的介绍是为了说明控制器和动画之间的异同,而不是告诉你哪个更好;控制器和动画在Ogre中是两种完全独立的功能,如何使用它们完全取决于你的程序应用。
有一些情况考虑使用控制器,比如在一个模拟天体运动的机械模型中。可以通过控制器来控制地球围绕太阳的运动过程。可以把时间作为输入数据,函数用来模拟地球公转的轨迹,控制器最终输出一个三元向量表示地球在三维宇宙空间的位置信息。
动画实例
可以想象得到,只靠简单的文字很难充分表现出动画的效果,我们接下来会重点介绍Ogre中提供的演示程序(你可以参照相应的演示程序来看看具体的运行效果)。在这里我们选择了Demo_SkeletalAnimation演示程序。通过对这个演示程序的具体分析,我们将会了解如何从实体(Entity)中得到动画相关的的信息以及相应的数据,并且了解如何在我们的程序中添加硬件动画支持。
Demo_SkeletalAnimation
作为我们了解Ogre中动画相关代码的开始,首先来看一下动画的执行效果(参看图9-2)。
图9-2:Demo_skeletalAnimation程序的屏幕截图
代码9-2中展示了一个简单直接的动画程序,其中创建了六个执行骨骼动画的模型角色(被称为Jaiqua,她是Softimage|XSI提供的动画明星)。每个Jaiqua都在重复的播放自己“Sneak”的动画(在这里有一个问题,她并不是一个“原地动画(animated-in-place)”,进而我们需要在代码中进行适当的处理,在后面的段落中会作具体的解释)。
注意:有必要告诉你的美工,尽量使用“原地动画”来表现模型的运动。否则虽然在模型工具中看上去没什么问题,但如果放到场景中执行,将会是一个巨大的噩梦。因为所有移动以及旋转的信息,你都需要进行大量的调整才能得到正常的执行效果。这一切问题的解决办法是:改用“原地动画”。
代码9-2:Demo_SkeletalAnimation 场景创建的代码,其中宏NUM_JAIQUAS被设置为6
void createScene(void)
{
mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE);
mSceneMgr->setShadowTextureSize(512);
mSceneMgr->setShadowColour(ColourValue(0.6, 0.6, 0.6));
// Setup animation default
Animation::setDefaultInterpolationMode(Animation::IM_LINEAR);
Animation::setDefaultRotationInterpolationMode(Animation::RIM_LINEAR);
// Set ambient light
mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
// 作者提示:
// 此处跳过动画修补代码;具体细节请看演示程序的原代码说明
Entity *ent;
Real rotInc = Math::TWO_PI / (float)NUM_JAIQUAS;
Real rot = 0.0f;
for (int i = 0; i < NUM_JAIQUAS; ++i)
{
Quaternion q;
q.FromAngleAxis(Radian(rot), Vector3::UNIT_Y);
mOrientations[i] = q;
mBasePositions[i] = q * Vector3(0,0,-20);
ent = mSceneMgr->createEntity("jaiqua" + StringConverter::toString(i), "jaiqua.mesh");
// Add entity to the scene node
mSceneNode[i] = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mSceneNode[i]->attachObject(ent);
mSceneNode[i]->rotate(q);
mSceneNode[i]->translate(mBasePositions[i]);
mAnimState[i] = ent->getAnimationState("Sneak");
mAnimState[i]->setEnabled(true);
mAnimState[i]->setLoop(false); // manual loop since translation involved
mAnimationSpeed[i] = Math::RangeRandom(0.5, 1.5);
rot += rotInc;
}
// 下面省略掉场景设置的基本代码
}
在上面的代码中,我们首先看到了对模型调节纹理阴影(Modulative Texture Shadows)的设置,稍后我们再解释这一步骤的重要性。然后我们进行六次循环,分别布置六个Jaiqua的实例在一个圆周上面,距离远心20个单位分别间隔60度(面向外)。
代码中比较值得留意的部分(动画相关的部分中)就是对getAnimationState("Sneak")方法的调用。这个方法返回了相应的动画状态,然后我们把和实例相应的动画状态以及随机产生的动画速度保存在和Jaiqua数量相等的数组里面,这样做的目的是保证我们之后可以单独管理每个动画实例。
前面提到过,动画状态被用来管理从实体中创建的动画:包括设置动画播放的时间点等等具体工作。在这个演示程序中,我们提供了六个Jaiqua实例,同时也就提供了六个动画状态。接下来的代码中启动了所有的动画状态(开启动画),然后关闭动画循环的开关(因为Jaiqua动画并非“本地动画”,所以在默认的情况下动画循环结束之后都会改变其位置,这时候就需要我们自己来调整了)。演示程序中通过把“动画速度”作为播放动画(以及改变位置和缩放)的因数来使用(换句话说,这不是Ogre本身的特性)。下面的代码中展示了“动画速度”因数的具体使用。
代码9-3:在帧监听中执行动画播放的代码
bool frameStarted(const FrameEvent& evt)
{
for (int i = 0; i < NUM_JAIQUAS; ++i)
{
Real inc = evt.timeSinceLastFrame * mAnimationSpeed[i];
//作者提示,在这里省略了对动画位置的修正代码
//可以参看Demo源代码了解
mAnimState[i]->addTime(inc);
}
// Call default
return ExampleFrameListener::frameStarted(evt);
}
代码9-3中展示了演示程序如何使用之前设置中得到的“Sneak”动画状态, 在这里通过时间间隔和前面随即得到的设置的“动画速度”因子进行动画播放,“动画速度”的作用就是加快或者减慢动画的播放速度。
上面所展示的动画可以在任何硬件环境上运行,不论硬件环境是否支持顶点程序;在低等级的图形硬件上,蒙皮的所有过程都可以通过软件来完成。对于硬件的执行,我们还有两段代码没有介绍,骨骼动画的和理阴影的硬件加速支持脚本代码。
骨骼动画材质脚本
下面代码9-4中提供的材质脚本可以提供给每个顶点绑定两块骨骼的模型执行硬件蒙皮使用。
代码9-4:Jaiqua 顶点程序声明以及材质脚本
vertex_program Ogre/HardwareSkinningTwoWeightsShadowCaster cg
{
source Example_Basic.cg
entry_point hardwareSkinningTwoWeightsCaster_vp
profiles vs_1_1 arbvp1
includes_skeletal_animation true
}
// Basic hardware skinning using two indexed weights per vertex
vertex_program Ogre/HardwareSkinningTwoWeightsCg cg
{
source Example_Basic.cg
entry_point hardwareSkinningTwoWeights_vp
profiles vs_1_1 arbvp1
includes_skeletal_animation true
default_params
{
param_named_auto worldMatrix3x4Array world_matrix_array_3x4
param_named_auto viewProjectionMatrix viewproj_matrix
param_named_auto lightPos[0] light_position 0
param_named_auto lightPos[1] light_position 1
param_named_auto lightDiffuseColour[0] light_diffuse_colour 0
param_named_auto lightDiffuseColour[1] light_diffuse_colour 1
param_named_auto ambient ambient_light_colour
}
}
material jaiqua
{
// Hardware skinning techniique
technique
{
pass
{
vertex_program_ref Ogre/HardwareSkinningTwoWeights
{
}
// alternate shadow caster program
shadow_caster_vertex_program_ref Ogre/HardwareSkinningTwoWeightsShadowCaster
{
param_named_auto worldMatrix3x4Array world_matrix_array_3x4
param_named_auto viewProjectionMatrix viewproj_matrix
param_named_auto ambient ambient_light_colour
}
texture_unit
{
texture blue_jaiqua.jpg
tex_address_mode clamp
}
}
}
// Software blending technique
technique
{
pass
{
texture_unit
{
texture blue_jaiqua.jpg
tex_address_mode clamp
}
}
}
}
在上面所展示的代码,实际上来源于两个不同的文件,其中材质定义代码在Example.material文件中,而顶点声明则在Examples.program文件里面。
在脚本中最重要的部分是对硬件动画的启动命令:
includes_skeletal_animation true
在每个顶点声明之后,还需要告知本程序适用的硬件等级,否则Ogre将不知道在什么时候使用硬件蒙皮代替软件。请看下面这行代码:
profiles vs_1_1 arbvp1
这行代码允许所有支持24块骨头的图形硬件使用本硬件动画程序,具体细节在前面的文章中提到过。
骨骼动画和阴影
当骨骼动画需要使用阴影支持的时候,我们需要依赖另外一个用于渲染阴影的单独顶点程序声明(因此要在createScene()代码中首先配制调节纹理阴影)。Ogre在渲染阴影的通路中会自动的使用这个顶点着色程序;这种阴影的缺点是只能使用单一的颜色,你必须通过设置ambient_light_color参数来确定这个颜色。如果你没有提供相应的阴影程序的话,Ogre将会回调一个固定功能材质来作为阴影通道,这时候对骨骼动画并没有什么影响。
注意:如果你选择了使用硬件的骨骼蒙皮动画时候,你可以使用任何类型阴影技术;阴影纹理会与任何的模型变形GPU程序配合,并且Ogre也能知道如何正确的处理模板阴影以及硬件蒙皮程序。但如果你使用了顶点程序的话,可能就没有这么幸运了,但很多情况下(比如脸部动画)顶点动画的变形对阴影的影响可以被忽略。
请注意,第一个顶点着色程序和后面的阴影顶点着色程序的参数并不相同;所有光照相关的参数都不会传递到阴影顶点程序中,因为对于阴影通路来说并不需要他们。world_matrix_array_3x4参数是这两个着色程序所共同需要的,因为它包含了蒙皮计算所需要的调色板矩阵信息(另外还有world-view-projection矩阵和ambient_light_colour参数)。
结语
通过对这一章节的学习,你应该已经掌握了在Ogre中对动画的使用方法。我们在这一章节中对控制器的介绍只是简略提及一下,我们将要在下一章节中重点介绍控制器两种应用的其中一个:管理Ogre中的粒子生存周期以及其相关行为。