天天看點

OSG動畫庫Animation解析(三)2. 動畫的優先級(Priority)

通過之前的兩篇文章,基本上對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來編寫動畫代碼,那麼權重設定的傳遞關系是:

OSG動畫庫Animation解析(三)2. 動畫的優先級(Priority)

也就是說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(, osg::Vec3(, , )));
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));

    osgAnimation::Animation *anim1 = new osgAnimation::Animation();
    anim1->addChannel(channel1);

    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(, , , );
    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 < )
                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 * ( - _weight);
                    _priorityWeight = ;
                    _lastPriority = priority;
                }

                _priorityWeight += weight;
                float t = ( - _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 = ; _priorityWeight = ; }
        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 = ; 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(, osg::Vec3(, , )));
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));

    //Channel 2
    osgAnimation::Vec3LinearChannel *channel2 = new osgAnimation::Vec3LinearChannel();
    channel2->setTargetName("move1UpdatedateCallback");
    channel2->setName("move1");
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));

    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(, , , );
    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 * ( - _weight);
                    _priorityWeight = ;
                    _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/n−1)∗(v1+v2+...+vn−1)+(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(, osg::Vec3(, , )));
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));
    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(, osg::Vec3(, , )));
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));
    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(, osg::Vec3(, , )));
    channel3->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(, osg::Vec3(, , )));
    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, , );
    bam->playAnimation(anim2, , );
    bam->playAnimation(anim3, , );

    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");
    grp->addChild(mt);
    mt->addChild(cowNode);
    grp->addUpdateCallback(bam);

    mt->addUpdateCallback(umt1);

    viewer.setSceneData(grp);
    viewer.setUpViewInWindow(, , , );
    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/N∗w+1/N∗w+...+1/N∗w(共有N項,每項的權重一樣)

整個過程在2.1中已經論述清楚了。

至此,整個osgAnimation中的動畫過程都解析完整了。接下來的文章會繼續讨論osgAnimation中漸進動畫和骨骼動畫等内容。