osgAnimation(三):动画种类介绍及实现流程

osg动画种类

osg中的动画主要的有五种分别是轨迹动画,序列动画,骨骼动画,变形动画和粒子动画。甚至自己也可以通过重写frame()函数或者说利用各种回调函数来实现动画效果。

轨迹动画

轨迹动画主要是指的osg::AnimationPathUpdateCallBack 这个回调类,其本质是一个随时间变化的4*4矩阵序列,用以控制刚体在轨迹上以给定姿态运动。

回调类的构造函数参数是轨迹osg::AnimationPath,轨迹由若干个控制点进行控制,一个控制点就是一个变换矩阵(控制点有很多种构造函数),动画自然离不开时间,因此轨迹实际上是时间到控制点的map,TimeControlPointMap,控制点与控制点之间的位置姿态是通过线性插值得到的。轨迹可以存取,可以通过交互的方式得到,也可以人为设计,这里为了演示用人为设计的方式,流程代码如下:

#include <osg/AnimationPath>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>

int main() {
	osg::AnimationPath* path = new osg::AnimationPath;
	osg::AnimationPath::TimeControlPointMap tcp;//这是一个时间到控制点的map
	tcp[0.0] = osg::AnimationPath::ControlPoint(osg::Vec3d(-4, 0, 0), osg::Quat(1, 0, 0, 0));//控制点位姿用位移加四元数旋转表示
	tcp[4.0] = osg::AnimationPath::ControlPoint(osg::Vec3d(4, 0, 0), osg::Quat(1, 0, 0, 0));//4.0表示时间,单位是秒
	path->setTimeControlPointMap(tcp);//与轨迹绑定

	osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
	mt->setUpdateCallback(new osg::AnimationPathCallback(path));//矩阵序列当然要赋给矩阵节点
	mt->addChild(osgDB::readNodeFile("glider.osg"));//加入要运动的刚体

	osgViewer::Viewer view;
	view.setSceneData(mt);
	view.setUpViewInWindow(200, 100, 800, 600);
	return view.run();
}
轨迹动画
轨迹动画
序列动画

                                                                                      

序列动画

序列动画亦称关键帧动画,通过osg::Sequence这个类实现,故名思意,它会对每一个关键帧的节点进行绘制,实际上就是一个自动的Switch节点,这个Switch节点会根据时间自动进行子节点的切换。当笔者做完这里的实验时,很遗憾的发现之前有许多工作都可以用这个实现,实现起来将更为简单(动画切换动态显示文字)。

Sequence的设置主要使用这三个函数:setMode(),可以用它实现暂停,重新播放;setDuration(),可以用它来设置每个关键帧的播放时间间隔,以及播放次数;setInterval(),可以用它控制加载的起点,选择哪些子节点进行播放,播放的方向。下面是一个倒计时的例子:注意setMode()最好在所有子节点加入完之后调用,这一点很重要。

#include <osg/Sequence>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgText/Text>
int main() {
        //设置Sequence参数
	osg::ref_ptr<osg::Sequence> seq = new osg::Sequence;
	seq->setDuration(1.0f, 1);//每帧间隔,循环次数
	seq->setInterval(osg::Sequence::LOOP, 9, 0);//从10显示到1
        
        //设置Sequence每一关键帧要显示的节点
	for (int i = 0; i <10; i++) {
		char show[3]; itoa(i+1, show, 10);
		//设置Text参数
                osg::ref_ptr<osgText::Text> num = new osgText::Text;
		num->setText(std::string(show));
		num->setPosition(osg::Vec3(0, 0, 0));
		num->setCharacterSize(80);
		num->setFont(osgText::readFontFile("fonts/arial.ttf"));
		//num->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
                num->setAlignment(osgText::Text::CENTER_CENTER);
		num->setAxisAlignment(osgText::Text::SCREEN);
		num->setColor(osg::Vec4d(1, 0, 0, 1));

                osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	        geode->addDrawable(num.get());
		seq->addChild(geode);//加入子节点
	}
        seq->setMode(osg::Sequence::START);

	osgViewer::Viewer view;
	view.setSceneData(seq);
	view.setUpViewInWindow(200, 100, 800, 600);
	return view.run();
}

骨骼动画

不像序列动画,对每一个关键帧都要指定一个模型,根据时间加载不同的模型,骨骼动画是通过更改模型骨骼的位置和姿态来形成动画的效果,这一点和轨迹动画很像,区别在于骨骼动画可以帮助管理多个骨骼,另外还可以引进蒙皮一个顶点受多个骨骼影响,让骨骼连接处的动画显得更加自然。由于篇幅限制,这里不讨论蒙皮的实现,只介绍骨骼的构造和动画的实现。

首先我们要有一个骨架Skeleton(继承于MatrixTransform),然后这个Skeleton设置BasicAnimationManager这个回调(继承于NodeCallback),这两个都是负责整体的控制,只不过一个负责骨骼,一个负责动画。

然后是骨骼Bone,Skeleton可以看做是一个树的根结点,而骨骼就是这棵树的子节点,骨骼依然是继承于MatrixTransform,因此很自然的骨骼与骨骼之间的作用关系是通过树的路径进行传递。让骨骼动起来的东西也是一个回调UpdateBone,他和骨骼应该具有同样的名字,名字在骨骼动画里起着很重要的作用。UpdateBone有个矩阵堆栈,后续动画部分可以根据名字按时序进行更新,也可以不更新,仅用来调整骨骼位姿(为了说明矩阵堆栈的使用,笔者没有采用Bone的SetInvBindMatrixInSkeletonSpace函数调整骨骼位姿)

最后是动画Animation,一个Animation是一种动作,所有骨骼都要参与进来,通过Channel来控制骨骼运动,Channel的名字控制骨骼怎么运动(更新矩阵堆栈种的哪个矩阵),Channel的TargertName控制哪块骨骼运动。之前的UpdateBone只是占了一个坑,还要给予具体的动画数据。指定每一帧KeyFrame如何运动,将他放在容器KeyFrameContainer里,再指定关键帧之间运动插值的方式Sampler,把这个Sampler给Channel,就完成了一个骨骼的运动。这里的KeyFrame的矩阵会覆盖对应的UpdateBone的矩阵并随时间变化

可以有多个Animation,将之注册到BasicAnimationManager里,再绑定对象,就可以通过PlayAnimation,StopAnimtion选择播放哪个动画。

示例程序由一个父骨骼(不是骨架)和六个子骨骼构成,父骨骼像一个车轮一样运动,子骨骼沿着车轮直径运动,希望大家能通过这个示例明白基础的骨骼动画实现流程,以及骨骼的变换矩阵是如何在矩阵堆栈,如何在场景树中一级级传递的。

骨骼动画
#include <osgAnimation/UpdateBone>
#include <osgAnimation/StackedMatrixElement>
#include <osgAnimation/StackedRotateAxisElement>
#include <osgAnimation/BasicAnimationManager>
#include <osgAnimation/Bone>
#include <osgAnimation/Skeleton>

#include <osgDB/ReadFile>
#include <osgViewer/Viewer>

int main() {
	///加载模型
	osg::ref_ptr<osg::Node> no = osgDB::readNodeFile("glider.osg");
	///定义骨架、骨骼
	osg::ref_ptr<osgAnimation::Skeleton> sk = new osgAnimation::Skeleton;
	osg::ref_ptr<osgAnimation::Bone> o = new osgAnimation::Bone;
	//六个子骨骼,一个父骨骼
	for (int i = 0; i < 7; i++) {
		std::string name = "o"; name += char(i + '0');
		osg::ref_ptr<osgAnimation::Bone> bo = new osgAnimation::Bone;
		bo->setName(name);

		osgAnimation::UpdateBone* pBoneUpdate = new osgAnimation::UpdateBone(name);//与骨骼名字一样
		pBoneUpdate->getStackedTransforms().push_back(new osgAnimation::StackedRotateAxisElement("matrixR",osg::Vec3(0,0,1), osg::PI * i / 3));
		pBoneUpdate->getStackedTransforms().push_back(new osgAnimation::StackedMatrixElement("matrixT", osg::Matrix::translate(osg::Vec3d(5 ,0, 0))));
		
		if (i == 6) {//父骨骼
			//考虑先旋转再平移(父骨骼运动)和先平移再旋转(子骨骼运动)的区别,视觉效果完全不同
			pBoneUpdate->getStackedTransforms().pop_back();
			pBoneUpdate->getStackedTransforms().pop_back();
			pBoneUpdate->getStackedTransforms().push_back(new osgAnimation::StackedMatrixElement("matrixT", osg::Matrix::translate(osg::Vec3d(5, 0, 0))));
			pBoneUpdate->getStackedTransforms().push_back(new osgAnimation::StackedRotateAxisElement("matrixR", osg::Vec3(0, 0, 1), 0));
			o->setName(name);
			o->addChild(no);
			o->setUpdateCallback(pBoneUpdate);
			continue;
		}//子骨骼
		bo->setUpdateCallback(pBoneUpdate);
		bo->addChild(no);//加入模型
		o->addChild(bo);
	}
	sk->addChild(o);

	///定义动画
	osg::ref_ptr<osgAnimation::Animation> anim = new osgAnimation::Animation;
	//子骨骼动画0-5 父骨骼动画6
	for (int i = 0; i < 7; i++)
	{
		//if (i == 6) continue;//这一句让父骨骼静止,注释情况父骨骼运动
		//每个关键帧的变换矩阵
		osgAnimation::MatrixKeyframeContainer* keyT = new osgAnimation::MatrixKeyframeContainer;
		keyT->push_back(osgAnimation::MatrixKeyframe(0, osg::Matrix::translate(osg::Vec3d(2.5, 0, 0))));
		keyT->push_back(osgAnimation::MatrixKeyframe(5, osg::Matrix::translate(osg::Vec3d(-2.5, 0, 0))));

		//帧间运动的插值方式
		osgAnimation::MatrixLinearSampler* samplerT = new osgAnimation::MatrixLinearSampler;
		samplerT->setKeyframeContainer(keyT);

		//指定运动的通道,同一物体同一时间可以受多种运动方式影响,这里只考虑单通道
		osgAnimation::MatrixLinearChannel* channelT = new osgAnimation::MatrixLinearChannel(samplerT);
		channelT->setName("matrixT");//与上方UpdateBone的StackedMatrixElement 名字对应


		std::string name = "o"; name += char(i + '0');
		channelT->setTargetName(name);骨骼的名字
		anim->addChannel(channelT);

		if (i == 6) {//父骨骼双通道,既有旋转又有平移
			osgAnimation::FloatKeyframeContainer* keyR = new osgAnimation::FloatKeyframeContainer;
			for (int j = 0; j < 6; j++)
				keyR->push_back(osgAnimation::FloatKeyframe(j,osg::PI / 6 * j));
			osgAnimation::FloatLinearSampler* samplerR = new osgAnimation::FloatLinearSampler;
			samplerR->setKeyframeContainer(keyR);
			osgAnimation::FloatLinearChannel* channelR = new osgAnimation::FloatLinearChannel(samplerR);
			channelR->setName("matrixR");
			channelR->setTargetName(name);
			anim->addChannel(channelR);
		}
	}
	osg::ref_ptr<osgAnimation::BasicAnimationManager> manager = new osgAnimation::BasicAnimationManager;
	manager->registerAnimation(anim);
	manager->buildTargetReference();
	manager->playAnimation(anim);
	sk->setUpdateCallback(manager);

	///渲染
	osgViewer::Viewer view;
	view.setSceneData(sk);
	view.setUpViewInWindow(200, 100, 800, 600);
	return view.run();
}

 

变形动画

to be done

粒子动画

to be done

本人主要从事图形图象工作,空闲之余接触了一些游戏编程,特写一些编程心得,本文 适合没有接触过人物动画编程的初学者,希望游戏制作的大虾们指点,交流。 在以前还有没接触人物动画编程的时候,觉得通过编程让人物动起来一定是一件很麻烦 的事情,尤其是初学者,大都会摸不着头脑,碰到诸如骨骼动画之类,似乎无从下手。但是 当你了解了它们的格式,就会发现其实真正的人物动画的制作并不是在编程阶段,而是在模 型构建阶段,程序员主要做工作的是掌握模型文件的格式,将存储在人物模型中的各种信息, 如顶点,面片,材质,骨骼或顶点运动的关键帧序列等信息读入内存然后用你熟悉的 SDK 绘制出来,再根据时间采用线性或者球形插值对动作序列的关键帧进行插值,不断变换顶点 坐标,从而得到了一系列连续的人物动画,听起来确实不难吧!当然你也可以在程序里自己 控制人物每一帧每一个关节的运动,我想做游戏的很少有人这么做吧。下面我向大家介绍一 下自己是如何编写人物动画程序的。本人从事的图形图象开发主要是基于 OpenGL 和 OSG 因此范例程序将采用 OpenGL 或 OSG。先声明一下,本人的语言表达能力很差,请大家多 多谅解指正。 考虑到没有接触过人物模型的朋友,我首先从人物模型的结构讲起,游戏人物编程主要 采用的人物模型格式有 Quake 里的md2,md3,Half Life 里的 mdl,Doom里的 md5,还有 典型的骨骼动画模型 ms3d…,至于3dmax 的模型,本人觉得太麻烦!在此我说两个有代表 性的 Md3,和 ms3d,其它的模型都大同小异,只要你了解了它们的格式,程序实现都不难。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值