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