您的位置:首页 > 其它

osgAnimation之动画基础篇

2016-05-31 20:49 316 查看

简介

osgAnimation是osg库中提供场景动画效果的一个类库,它为我们提供了许多与场景动画相关的类,比如关键帧、插值、采样、频道、骨骼动画、材质变化等。本课就对osgAnimation库中的基础类进行一些解析。以下都是我个人学习过程中的一些记录和体会,方便以后自己复习之用。

开始

Keyframe

对应文件 osgAnimation/keyframe

学习osgAnimation库,首先需要理解关键帧的含义。关键帧顾名思义是对应某一时刻动画中的一种状态,就像制作动画片一样。我们知道早期的动画片是作者一页一页画出来的,在通过迅速地切换让我们感受到了动态的效果,在这里关键帧就相当于其中的一页画面。在osgAnimation中关键帧定义如下:

[cpp] view
plain copy

class Keyframe

{

public:

double getTime() const { return _time; }

void setTime(double time) { _time = time; }

protected:

double _time;

};

很简单是吧,你可能回想:这里面根本什么都没有啊!时间应该对应一个内容啊。由于对应的内容千变万化(可能是运动位置、颜色、角度等等),因此在派生类中采用模板的方式来处理,即:

[cpp] view
plain copy

template <class T>

class TemplateKeyframe : public Keyframe

{

protected:

T _value;

public:

TemplateKeyframe () {}

~TemplateKeyframe () {}

TemplateKeyframe (double time, const T& value)

{

_time = time;

_value = value;

}

void setValue(const T& value) { _value = value;}

const T& getValue() const { return _value;}

};

现在有了和时间对应的值,为了方便管理,还需要定义一个存储值的容器,即:KeyframeContainer,同样还是用相同的方式定义如下:

[cpp] view
plain copy

class KeyframeContainer : public osg::Referenced

{

public:

KeyframeContainer() {}

virtual unsigned int size() const = 0;

protected:

~KeyframeContainer() {}

std::string _name;

};

[cpp] view
plain copy

template <class T>

class TemplateKeyframeContainer : public std::vector<TemplateKeyframe<T> >, public KeyframeContainer

{

public:

TemplateKeyframeContainer() {}

typedef TemplateKeyframe<T> KeyType;

virtual unsigned int size() const { return (unsigned int)std::vector<TemplateKeyframe<T> >::size(); }

};

可以看到该容器继承自std::vector,这样我们就可以采用push_back这样的方法往容器里面插入关键帧。

Interpolator

对应文件osgAnimation/Interpolator

有了关键帧之后,我们需要对关键帧之间的时间对应的值进行计算,这就是所谓的插值,定义插值的基类如下:

[cpp] view
plain copy

template <class TYPE, class KEY>

class TemplateInterpolatorBase

{

public:

//KEY对应的是关键帧类型

typedef KEY KeyframeType;

//TYPE对应的是关键帧对应的值

typedef TYPE UsingType;

public:

mutable int _lastKeyAccess;

TemplateInterpolatorBase() : _lastKeyAccess(-1) {}

void reset() { _lastKeyAccess = -1; }

//通过时间time,计算出当前的索引值

//也就是该时间在两个关键帧之间

int getKeyIndexFromTime(const TemplateKeyframeContainer<KEY>& keys, double time) const

{

int key_size = keys.size();

if (!key_size) {

osg::notify(osg::WARN) << "TemplateInterpolatorBase::getKeyIndexFromTime the container is empty, impossible to get key index from time" << std::endl;;

return -1;

}

const TemplateKeyframe<KeyframeType>* keysVector = &keys.front();

for (int i = 0; i < key_size-1; i++)

{

double time0 = keysVector[i].getTime();

double time1 = keysVector[i+1].getTime();

if ( time >= time0 && time < time1 )

{

_lastKeyAccess = i;

return i;

}

}

return -1;

}

};

这个基类负责寻找到插值所需要的两帧,也就是与需要插值时刻相距最近的那两个关键帧。之后进行插值就相对简单,osg中定义了几种插值的方式:

[cpp] view
plain copy

template <class TYPE, class KEY=TYPE>

class TemplateStepInterpolator : public TemplateInterpolatorBase<TYPE,KEY>

{

public:

TemplateStepInterpolator() {}

void getValue(const TemplateKeyframeContainer<KEY>& keyframes, double time, TYPE& result) const

{

if (time >= keyframes.back().getTime())

{

result = keyframes.back().getValue();

return;

}

else if (time <= keyframes.front().getTime())

{

result = keyframes.front().getValue();

return;

}

int i = this->getKeyIndexFromTime(keyframes,time);

result = keyframes[i].getValue();

}

};

StepInterpolator直接找到与time时刻相距最近那一帧的值,另外还有Linear(线性的插值)、SphericalLinear(球面的插值)、CubicBezier(贝塞尔插值)

Sampler

对应文件osgAnimation/Samper

有了关键帧和处理关键帧的插值算法,在osgAnimation中使用了Sampler(采样器)的方式将二者组合起来,其中的成员函数实现一目了然,都是调用插值器中的函数:

[cpp] view
plain copy

//F实参化到时候需要传入的是一个Interpolator类

emplate <class F>

class TemplateSampler : public Sampler

{

public:

//KeyframeType关键帧的类型

typedef typename F::KeyframeType KeyframeType;

//关键帧容器类型

typedef TemplateKeyframeContainer<KeyframeType> KeyframeContainerType;

//通过关键帧计算出的值的类型

typedef typename F::UsingType UsingType;

typedef F FunctorType;

TemplateSampler() {}

~TemplateSampler() {}

void getValueAt(double time, UsingType& result) const { _functor.getValue(*_keyframes, time, result);}

void setKeyframeContainer(KeyframeContainerType* kf) { _keyframes = kf;}

virtual KeyframeContainer* getKeyframeContainer() { return _keyframes.get(); }

virtual const KeyframeContainer* getKeyframeContainer() const { return _keyframes.get();}

KeyframeContainerType* getKeyframeContainerTyped() { return _keyframes.get();}

const KeyframeContainerType* getKeyframeContainerTyped() const { return _keyframes.get();}

//安全地得到一个关键帧容器,建议在程序中使用该方法

KeyframeContainerType* getOrCreateKeyframeContainer()

{

if (_keyframes != 0)

return _keyframes.get();

_keyframes = new KeyframeContainerType;

return _keyframes.get();

}

double getStartTime() const

{

if (!_keyframes || _keyframes->empty())

return 0.0;

return _keyframes->front().getTime();

}

double getEndTime() const

{

if (!_keyframes || _keyframes->empty())

return 0.0;

return _keyframes->back().getTime();

}

protected:

FunctorType _functor;

osg::ref_ptr<KeyframeContainerType> _keyframes;

};

到这里我们已经可以将采样器应用到我们的程序中了,例如:自己定义更新回调,传入关键帧参数,根据关键帧计算每个时刻的值(比如物体姿态),并进行更新来达到动画效果。在osgAnimation中还进行了更高的封装,即Channel(动画频道的概念)

Channel

对应文件osgAnimation/Channel和osgAnimation/Channel.cpp

在一个Channel之中封装了采样器Sampler和执行对象Target,执行对象可以理解为将采样器计算的插值结果保存在这个对象之中,查看一下Target的实现如下:

[cpp] view
plain copy

template <class T>

class TemplateTarget : public Target

{

public:

inline void lerp(float t, const T& a, const T& b);

//TODO:怎么解释?

// 以下是我的理解:

//如果多个Channel共享一个执行对象Target,那么

//在调用update的过程中,必须按照优先级的顺序进行

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;

}

}

const T& getValue() const { return _target; }

void setValue(const T& value) { _target = value; }

protected:

//记录了最终的结果

T _target;

};

在Channel的实现中有同样有一个update成员函数,它的实现反应了Channel的作用,通过采样器计算得到Value值,然后再通过Target对象的更新update,最终将计算得到的结构存储在Target对象的成员变量_target之中以便后续使用。代码如下:osgAnimation/Channel

[cpp] view
plain copy

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); //得到采样器插值的值value

_target->update(weight, value, priority);//对value进行加权计算,并将结果保存在target对象之中

}

Animation

对应文件osgAnimation/Animation和osgAnimation/Animation.cpp

最后将这些频道整合起来的类是动画类Animation,代码如下:

[cpp] view
plain copy

class OSGANIMATION_EXPORT Animation : public osg::Object

{

public:

META_Object(osgAnimation, Animation)

Animation() : _duration(0), _weight(0), _startTime(0), _playmode(LOOP) {}

Animation(const osgAnimation::Animation&, const osg::CopyOp&);

enum PlayMode

{

ONCE,

STAY,

LOOP,

PPONG

};

void addChannel (Channel* pChannel);

ChannelList& getChannels();

const ChannelList& getChannels() const;

void setDuration(double duration);

void computeDuration();

double getDuration() const;

void setWeight (float weight);

float getWeight() const;

bool update (double time, int priority = 0);

void resetTargets();

void setPlayMode (PlayMode mode) { _playmode = mode; }

PlayMode getPlayMode() const { return _playmode; }

void setStartTime(double time) { _startTime = time;}

double getStartTime() const { return _startTime;}

protected:

double computeDurationFromChannels() const;

~Animation() {}

double _duration;

double _originalDuration;

float _weight;

double _startTime;

PlayMode _playmode;

ChannelList _channels;

};

将许多Channel整合在了一起,实现的过程也是调用Channel中的成员函数来实现,很容易理解。在Animation中可以设置播放的模式,播放的模式实际上是通过这些模式来计算时间

[cpp] view
plain copy

switch (_playmode)

{

case ONCE:

if (t > _originalDuration)

return false;

break;

case STAY:

if (t > _originalDuration)

t = _originalDuration;

break;

case LOOP:

if (!_originalDuration)

t = _startTime;

else if (t > _originalDuration)

t = fmod(t, _originalDuration);

// std::cout << "t " << t << " duration " << _duration << std::endl;

break;

case PPONG:

if (!_originalDuration)

t = _startTime;

else

{

int tt = (int) (t / _originalDuration);

t = fmod(t, _originalDuration);

if (tt%2)

t = _originalDuration - t;

}

break;

}

以上就是osgAnimation库中基础部分的介绍,后续还会记录在实际操作中如何使用这些类来完成一个完整的动画。

转载地址:http://blog.csdn.net/csxiaoshui/article/details/22175055
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: