OSG动画库Animation解析(三)

来源:互联网 发布:圆形蓄水池算法 编辑:程序博客网 时间:2024/06/16 11:47

通过之前的两篇文章,基本上对osgAnimation整个动画的实现体系有了一个比较完整的介绍,本文主要介绍动画中其他的一些内容,主要包括权、优先级、以及动画变换中的顺序等内容。

1. 动画的权重(weight)

假设对一辆匀速运行中的汽车施加两股力量,单独作用时,其中一股力量令汽车以60公里每小时向东北方向移动,另一股力量以100公里每小时向西北方向运动,如果希望汽车的方向修正为正北方向运行,需要向西北方向施加一个权重小于1.0的权重,让它与东北方向的力量持平,这就是运动叠加和加权的意义。这个过程有点类似于向量的加法运算,遵循平行四边形规则,权重类似于对向量乘上一个标量。

1.1 权重的设置

在osgAnimation中,和权重相关的类应该是:
1. AnimationManagerBase及其派生类
2. Animation
3. Channel
4. Target
这其中Channel和Target没有设置权重的接口,但是在它们的内部需要使用权重这个参数参与运算,换言之它们权重是Animation设置给它们的。Animation可以通过设置函数void Animation::setWeight (float weight)设置动画的权重,动画管理类BasicAnimationManager使用void playAnimation (Animation* pAnimation, int priority = 0, float weight = 1.0);指定动画的权重。假设使用BasicAnimationManager来编写动画代码,那么权重设置的传递关系是:

WeightDirection

也就是说playAnimation中指定的权重会设置给animation,animation遍历它管理的所有Channels,并把这个权重设置给这些Channels【也就是说同一个Animation中所有频道的权重都是一样的】,这些Channels把权重设置给它管理的各自的Target对象,Target对象使用这个设置的权重进行运算,得到最终的结果。

1.2 单动画单频道

在之前编写打RotateCallback的示例中,将一个频道添加到了一个动画之中,如果设置权重看是否会对结果产生影响。代码如下:

int main(int argc, char **argv){    osgViewer::Viewer viewer;    osg::Group* grp = new osg::Group;    osg::MatrixTransform *mt = new osg::MatrixTransform;    osgAnimation::UpdateMatrixTransform *umt1 = new osgAnimation::UpdateMatrixTransform;    umt1->setName("move1UpdatedateCallback");    umt1->getStackedTransforms().push_back(new osgAnimation::StackedTranslateElement("move1"));    osgAnimation::Vec3LinearChannel *channel1 = new osgAnimation::Vec3LinearChannel();    channel1->setTargetName("move1UpdatedateCallback");    channel1->setName("move1");    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(5, 0, 0)));    osgAnimation::Animation *anim1 = new osgAnimation::Animation();    anim1->addChannel(channel1);    osgAnimation::BasicAnimationManager *bam = new osgAnimation::BasicAnimationManager();    bam->registerAnimation(anim1);    bam->playAnimation(anim1, 10, 0.5);    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");    grp->addChild(mt);    mt->addChild(cowNode);    grp->addUpdateCallback(bam);    mt->addUpdateCallback(umt1);    viewer.setSceneData(grp);    viewer.setUpViewInWindow(300, 300, 800, 600);    return (viewer.run());}

使用playAnimation设置了当前的动画权重是0.5, 优先级是10(默认参数weight=0.5, priority=0),通过1.1中对权重的设置,可以最终得到
Channel的权重代码部分:

        virtual void update(double time, float weight, int priority)        {            // skip if weight == 0            if (weight < 1e-4)                return;            typename SamplerType::UsingType value;            _sampler->getValueAt(time, value);            _target->update(weight, value, priority);        }

如果设置了权重小于(0.00001,也就是近似等于0的情况),那么动画是不会起作用的。设置其他值,代码会进入到Target使用权重计算的代码中:

        void update(float weight, const T& val, int priority)        {            if (_weight || _priorityWeight)            {                if (_lastPriority != priority)                {                    _weight += _priorityWeight * (1.0 - _weight);                    _priorityWeight = 0;                    _lastPriority = priority;                }                _priorityWeight += weight;                float t = (1.0 - _weight) * weight / _priorityWeight;                lerp(t, _target, val);            }            else            {                _priorityWeight = weight;                _lastPriority = priority;                _target = val;            }        }

先看看Target类保存的和优先级以及权重相关的变量

    class  Target : public osg::Referenced    {    public:        Target(): _weight(0), _priorityWeight(0), _lastPriority(0) {}        virtual ~Target() {}        void reset() { _weight = 0; _priorityWeight = 0; }        int getCount() const { return referenceCount(); }        float getWeight() const { return _weight; }    protected:        float _weight;        float _priorityWeight;        int _lastPriority;    };

这三个变量_weight, _priorityWeight, _lastPriority的含义是:
_weight: 当前的已经计算的权重,如果把整个动画管理器管理的动画按照优先级进行排序,那么当优先级进行切换时,_weight会记录之前所有优先级动画所消耗的权重(如果把整个权重看作是1)
_priorityWeight:指的是遍历某一个优先级时,它下面所有频道的权重之和。当优先级切换时,这个值为重设为0,并重新开始计算。(这也是为什么名字是priorityWeight,因为它只记录某一priority下面的权重之和)
_lastPriority是上一次的优先级(通过记录上一次优先级,可以知道什么时候优先级发生了切换)
这三个变量默认情况下都是0,通过reset函数的调用可以将_weight和_priorityWeight设置为0.
当单动画单频道的时候,BasicAnimationManager的更新代码如下:

void BasicAnimationManager::update (double time){    ...    for (TargetSet::iterator it = _targets.begin(); it != _targets.end(); ++it)        (*it).get()->reset();    for( AnimationLayers::reverse_iterator iterAnim = _animationsPlaying.rbegin(); iterAnim != _animationsPlaying.rend(); ++iterAnim )    {        // update all animation        int priority = iterAnim->first;        AnimationList& list = iterAnim->second;        for (unsigned int i = 0; i < list.size(); i++)        {            list[i]->update(time, priority)        }    }    ...}

整个遍历的循环其实只遍历了一次,因为只存在一个优先级为0的动画,并且动画中仅有一个Channel,这段循环只执行一次,注意在执行update之前,执行了target的reset操作,将target中的_weight和_priorityWeight设置为0,导致Target在执行update时,仅仅返回Channel中插值好的值,

            else            {                _priorityWeight = weight;                _lastPriority = priority;                _target = val;            }

并没有权重的参与计算。
也就是说单动画单频道的情况下:
1. 设置的权重并没有什么作用,但是不要设置为0(确切的说是不要小于1e-4)
2. 设置的优先级并没有什么作用
3. Target仅仅帮助传递数据,传递Channel中Sampler计算的数据给UpdateMatrixTransform

1.2 单动画多频道

既然动画Animation是用来管理众多Channel,尝试在Animation中添加多个Channel:

int main(int argc, char **argv){    osgViewer::Viewer viewer;    osg::Group* grp = new osg::Group;    osg::MatrixTransform *mt = new osg::MatrixTransform;    osgAnimation::UpdateMatrixTransform *umt1 = new osgAnimation::UpdateMatrixTransform;    umt1->setName("move1UpdatedateCallback");    umt1->getStackedTransforms().push_back(new osgAnimation::StackedTranslateElement("move1"));    //Channel 1    osgAnimation::Vec3LinearChannel *channel1 = new osgAnimation::Vec3LinearChannel();    channel1->setTargetName("move1UpdatedateCallback");    channel1->setName("move1");    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(5, 0, 0)));    //Channel 2    osgAnimation::Vec3LinearChannel *channel2 = new osgAnimation::Vec3LinearChannel();    channel2->setTargetName("move1UpdatedateCallback");    channel2->setName("move1");    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(0, 0, 5)));    osgAnimation::Animation *anim1 = new osgAnimation::Animation();    anim1->addChannel(channel1);    anim1->addChannel(channel2);    osgAnimation::BasicAnimationManager *bam = new osgAnimation::BasicAnimationManager();    bam->registerAnimation(anim1);    bam->playAnimation(anim1);    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");    grp->addChild(mt);    mt->addChild(cowNode);    grp->addUpdateCallback(bam);    mt->addUpdateCallback(umt1);    viewer.setSceneData(grp);    viewer.setUpViewInWindow(300, 300, 800, 800);    return (viewer.run());}

运行修改程序,发现模型在两个频道的共同作用下(Channel1向右移动,Channel2向上移动)向屏幕右上方向运动。
由于同一个动画的权重是一样的,也就是说该动画下所有的频道的权重也都是一样的。UpdateMatrixTransform只包含一个StackedTranslateElement,这个StackedTranslateElement被两个频道都连接,因此产生的Target也是二者共用的。从Target使用权重计算的代码,可以知道:
1. Target的_weight和_priorityWeight在每一次遍历的过程中会首先被设置为0, 如果所有的频道的优先级都没有变化,那么Target中的_weight永远将是0,下面这段代码将永远不会被执行(1.2中单动画由于所有的动画频道下的优先级是一样的,因此下面这段代码不会被执行)

                if (_lastPriority != priority)                {                    // change in priority                    // add to weight with the same previous priority cumulated weight                    _weight += _priorityWeight * (1.0 - _weight);                    _priorityWeight = 0;                    _lastPriority = priority;                }
  1. 遍历的第一个频道肯定会执行else中的语句,也就是(第一次循环必定会进入else分支)
            else            {                _priorityWeight = weight;                _lastPriority = priority;                _target = val;
  1. 对于本例中的情况,Target的update代码会被执行两次,由于_weight = 0,实际代码如下:
                _priorityWeight += weight;                float t =  weight / _priorityWeight;                lerp(t, _target, val);

lerp是一个线性的差值,lerp的这一行是
_target = _target * (1-t) + t * val;
由于一个Animation中所有的weight都是相等的,也就是这个t=0.5,假设一个Animation中有多个Channel,那么t依次等于 1/2, 1/3, 1/4, … 知道 1/Channel个数。

如果权重相同,那么一段动画的最终取值应该是:
最终值 = 1/N * Channel1取值 + 1/N Channel2取值 + … + 1/N ChannelN取值。
使用这段代码可以得到这个结论。

这段代码的计算推导过程简单说一下:

假设一个动画Animation中有n个Channel,由于这些Channel的权重和优先级都是一样的,假设它们的权值都是w,并且每个Channel由它的Sampler采样器计算出来的结果是v, 最终的计算的结果(由target存储)是value,那么代码的计算过程如下:

第一次迭代: value = v1 (_priorityWeight = w)

第二次迭代: 由于_priorityWeight = _priorityWeight + w, 也就是_priorityWeight = 2w,那么有: value = ( 1 - w / 2 * w )v1 + v2 w = 1/2*(v1+v2)

第三次迭代: 同样 value = (1 - w / 3*w)(0.5(v1+v2))+ 1/3 cv3 = 1/3*(v1+v2+v3)
……

第n次迭代: value = [1(1/n)](1/n1)(v1+v2+...+vn1)+(1/n)vn = 1/n * (v1+v2+…+v_n)

也就是说当一个动画中有多个频道时,有以下结论:
1. 所有频道的权重和优先级都只能由Animation设置,并且权重都一样,优先级也一样。
2. 权重不能设置为0(具体来说是不能小于1e-4),否则update函数直接退出。除此之外,权重设置成任何其他数都是一样的效果
3. 最后得到的结果是所有频道的平均值

2. 动画的优先级(Priority)

osgAnimation的动画管理器BasicAnimationManager中存储着动画的数组,它是用一个map来进行存储的,这个map的键值是动画的优先级,默认情况下添加进去的Animation的优先级是0,在使用void playAnimation (Animation* pAnimation, int priority = 0, float weight = 1.0);进行播放时设置优先级。按照之前的讨论,优先级只有在存在多个动画的时候才有意义。

2.1 多动画单频道

多动画单频道指的是管理器BasicAnimationManager中添加很多个Animation动画,每一个动画中只包含一个Channel,修改代码:

int main(int argc, char **argv){    osgViewer::Viewer viewer;    osg::Group* grp = new osg::Group;    osg::MatrixTransform *mt = new osg::MatrixTransform;    osgAnimation::UpdateMatrixTransform *umt1 = new osgAnimation::UpdateMatrixTransform;    umt1->setName("move1UpdatedateCallback");    umt1->getStackedTransforms().push_back(new osgAnimation::StackedTranslateElement("move1"));    //第一个动画    osgAnimation::Vec3LinearChannel *channel1 = new osgAnimation::Vec3LinearChannel();    channel1->setTargetName("move1UpdatedateCallback");    channel1->setName("move1");    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(5, 0, 0)));    osgAnimation::Animation *anim1 = new osgAnimation::Animation();    anim1->addChannel(channel1);    //第二个动画    osgAnimation::Vec3LinearChannel *channel2 = new osgAnimation::Vec3LinearChannel();    channel2->setTargetName("move1UpdatedateCallback");    channel2->setName("move1");    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(0, 0, 5)));    osgAnimation::Animation *anim2 = new osgAnimation::Animation();    anim2->addChannel(channel2);    //第三个动画    //第二个动画    osgAnimation::Vec3LinearChannel *channel3 = new osgAnimation::Vec3LinearChannel();    channel3->setTargetName("move1UpdatedateCallback");    channel3->setName("move1");    channel3->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));    channel3->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(0, 5, 0)));    osgAnimation::Animation *anim3 = new osgAnimation::Animation();    anim2->addChannel(channel3);    osgAnimation::BasicAnimationManager *bam = new osgAnimation::BasicAnimationManager();    bam->registerAnimation(anim1);    bam->registerAnimation(anim2);    bam->registerAnimation(anim3);    bam->playAnimation(anim1, 1, 1);    bam->playAnimation(anim2, 1, 1);    bam->playAnimation(anim3, 1, 1);    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");    grp->addChild(mt);    mt->addChild(cowNode);    grp->addUpdateCallback(bam);    mt->addUpdateCallback(umt1);    viewer.setSceneData(grp);    viewer.setUpViewInWindow(300, 300, 800, 600);    return (viewer.run());}

有以下几种情况:
1. 当所有Animation的优先级一样,权重也一样的时候,和单动画多频道的结果是一样的(所有的动画平均起作用)
2. 当Animation的优先级一样,但是权重不一样的时候,按照权重起作用,也就是最终的结果是 : value = w1 * v1 + w2 * v2 (这里的权重w1、w2,都是它们设置的权除以它们所有的权的和),也就是说这时候的权可以随便设置,不要求所有的权重之和是1
3. 当Animation的优先级不一样,但是权重一样的时候
4. 当Animation的优先级不一样,权重也不一样的时候
第3和第4两种情况其实是一样的处理过程,算法基本上是这样的:


优先级高的动画值先计算,在先计算的时候有优先权,它的权重起的作用更大,假设有n的动画,一次按优先级从高到底排列是 A1, A2,A3,A4… An,假设A1的动画的权重是W1,A2是W2… An是Wn,那么A1优先级最高,它的权重分的是整个权重是已1为参考的,如果A1的权重是1,那么其他A2到An的动画都不起作用。如果A1的权重不是1,而是一个小于1的值W1,那么剩下的A2到An只能去分享(1-W1),它们的权重都是以(1-W1)来计算的,也就是分到的权重就会更小。

这个过程可以用下面一个场景来类比:假设很多人花钱买了一个西瓜分,现在大家安排怎么分这个西瓜,西瓜怎么分以家庭为单位,家庭里面每个人分到的西瓜大小都一样。每个家庭可以分一个百分数的大小,但是这个百分数是以剩余部分来计算的。也就是说第一个分西瓜的家庭,他分的百分数是以整个西瓜大小来计算的(假设他分50%),那么直接就切走一半的西瓜。剩下的人分的部分的百分数只能以剩下的半个西瓜来计算,假设第二家分的的也是50%,那么事实上它只能分走1/4个西瓜,那么以此类推,越是后分的家庭,分到的西瓜越小(虽然后面分的家庭可能的百分数也可能很大,但是给他分的基数小了(剩余的西瓜大小)),也就是说优先级越高的家庭得到的实际西瓜多,也就是贡献越大。


上面这个类比已经很贴切了。这里面每一个家庭实际上对应一个Animation,每个家庭成员对应一个Channel。我们现在讨论的是多动画单频道,也就是相当于每一个家庭只有一个成员,多动画多频道就相当于每个家庭有多个成员而已,但是实际上不影响最后计算的结果。从上面的分析可以看出:

  1. 优先级越高的动画对最后的Target得到的最终插值结果的贡献越大。
  2. 动画的计算如果存在不同优先级和权重的时候,设置的权重并不是简单的线性关系
  3. 如果我们把优先级最高的权设置为大于1,那么会有一个很奇怪的效果,其他动画的权重会在计算中出现负数,导致动画的计算结果是反的。也就是说插值的结果是我们预期插值结果的相反数,最好避免这样做,在设置权重的时候都设置在[0,1]之间。

2.2 多动画多频道

这种情况和多动画单频道类似,只是每个动画多出来很多的Channel,但是这些Channel叠加起来的权重是和整个动画的权值一样。假设动画的权值是 W,那么所有它包含的Channel(N个)叠加的结果权值也是W。

w=1/Nw+1/Nw+...+1/Nw(N

整个过程在2.1中已经论述清楚了。

至此,整个osgAnimation中的动画过程都解析完整了。接下来的文章会继续讨论osgAnimation中渐进动画和骨骼动画等内容。

原创粉丝点击