OSG動畫庫Animation解析(一)
1. 概述
OpenSceneGraph(OSG)庫在核心庫的基礎上寫了許多擴充套件,這其中就有一個庫專門用來處理場景中的動畫的庫osgAnimation,這一系列的文章主要介紹一下osgAnimation庫的原理和實現,記錄下來方便日後查閱。本文對應的OSG版本是3.4.0
2. 動畫的原理
大家一定看過一個畫面,將許多動作各異的一個小人的畫像,通過快速翻頁,就可以看到這個小人在運動。在電影中也是如此,螢幕上運動的畫面就是通過拍攝大量的圖片,然後以每秒24幀的頻率把他們投影到螢幕上來實現的。每一幀移動到鏡頭的後一個位置,接著快門開啟,然後這一幀便顯示。在影片切換到下一幀的瞬間,快門關閉,然後又開啟以顯示下一幀,以此類推。儘管觀眾所看到的是每秒24幀切換的不同的畫面,但是大腦會把他們混合成一段平滑的動畫。在看奧斯卡頒獎禮的時候,提到影片的時候,使用的詞是Motion Picture,非常的貼切。
按照上面的描述,為了獲得動畫的效果,可以在繪製的每一幀中通過不斷的修改模型位置和姿態,已達到連續動畫的效果。在OSG的一幀繪製中,包含以下幾個過程:
void ViewerBase::frame(double simulationTime)
{
if (_done) return;
if (_firstFrame)
{
viewerInit();
if (!isRealized())
{
realize();
}
_firstFrame = false ;
}
advance(simulationTime);
eventTraversal(); //事件遍歷
updateTraversal(); //更新遍歷
renderingTraversals(); //渲染遍歷
}
在更新遍歷中去修改模型的位置和姿態是一個不錯的選擇(其實選在哪個遍歷中來修改都是可以的,但是一般來說事件遍歷是用來處理和外設的互動,如滑鼠和鍵盤等;渲染遍歷是用來真正的繪製模型,比如會呼叫glVertex等這樣的函式, 更新遍歷恰好調整的位置和姿態來用的,因此一般就在更新遍歷中完成位置和姿態的調整)
3. 實現
在OSG中每一個節點都包含一個更新回撥的成員,可以使用
inline void addUpdateCallback(Callback* nc)
來新增更新回撥,OSG的更新遍歷會遍歷整個場景中每一個節點的更新回撥,並呼叫更新回撥的operator方法,需要做的事情是在更新回撥中改變姿態即可。下面的程式通過使用一個更新回撥函式,讓場景中的模型實現繞Z軸的旋轉
#include <osgViewer/Viewer>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osg/Callback>
class RotateCallback : public osg::NodeCallback
{
public:
RotateCallback(osg::Vec3d axis, double rotAngular) :
_axis(axis), _speed(rotAngular), _currentRotation(0.0)
{
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
_currentRotation += _speed;
if (_currentRotation > (2 * osg::PI))
{
_currentRotation -= (osg::PI * 2);
}
osg::Quat rotQuat(_currentRotation, _axis);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
protected:
osg::Vec3d _axis;
double _speed;
double _currentRotation;
};
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
root->addChild(cowNode);
root->addUpdateCallback(new RotateCallback(osg::Z_AXIS, 0.01));
viewer->setUpViewInWindow(200, 200, 800, 600);
viewer->setSceneData(root);
return viewer->run();
}
4. 修改泛化
以上的程式碼就是實現一個動畫效果的全部邏輯和原理,osgAnimation這個庫圍繞著這一基本的原理進行了抽象和豐富,通過Animation中的各個類,共同完成了這一看似簡單的實現,下面針對具體的osgAnimation的類來改造這一程式,最終完成一個osgAnimation版本的動畫程式。
4.1 關鍵幀
在動畫程式中,有一個很重要的概念是關鍵幀(Key Frame)。一段漸變動畫的起點和終點一般都會設定為關鍵幀。 一般來說作為關鍵幀的影象應該是具有某種特徵,能夠反映出變化的一幀,這也是為什麼稱之為“關鍵”幀的原因。比如說在直線上的運動,突然要開始轉彎,在轉彎處的一幀就有必要作為一個關鍵幀,它反映出了運動的一個突然的變化。
關鍵幀的型別是多種多樣的,可能是浮點數的變化、也可能是四元數的變化等,一般使用模板引數來描述各種各樣的變換型別,關鍵幀的每一個值應該有一個與之對應的時間點,也就是說在某一個時間點t,這時候的關鍵幀的值是value。
//虛擬碼,非c++定義
TemplateKeyframe< T = osg::Quat>
{
double _time;
T _value;
}
關鍵幀一般會放在一個容器中統一管理,這個關鍵幀的容器在osgAnimation中的類是osgAnimation::TemplateKeyframeContainer,它是一個儲存容器的std::vector。
引入關鍵幀概念之後,可以修改之前的程式碼。由於程式是一個簡單的旋轉動畫,可以使用旋轉角度作為關鍵幀,也可以使用四元數來表示旋轉的關鍵幀,為了簡單,這裡使用旋轉角度作為關鍵幀,也就是關鍵幀使用DoubleKeyframe,由於旋轉是連續性的,可以把旋轉角度0作為關鍵幀的起點的關鍵幀,旋轉角度2
#include <osgViewer/Viewer>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osg/Callback>
#include <osgAnimation/Keyframe>
class RotateCallback : public osg::NodeCallback
{
public:
RotateCallback(osgAnimation::DoubleKeyframeContainer *kfc)
{
_keyframes = kfc;
_startTick = osg::Timer::instance()->tick();
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
double currentTick = osg::Timer::instance()->tick();
double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);
//計算關鍵幀之間的中間幀的值
// Value = currentframe.time / keyframe_end.time - keyframe_start.time;
double frameStartTime = _keyframes->back().getTime();
double frameEndTime = _keyframes->front().getTime();
double frameTimeDuration = frameEndTime - frameStartTime;
double currentRot = (elaspedTime / frameTimeDuration) * osg::PI * 2;
osg::Quat rotQuat(currentRot, osg::Z_AXIS);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
protected:
osgAnimation::DoubleKeyframeContainer *_keyframes;
double _startTick;
};
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
root->addChild(cowNode);
osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI*2);
osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
keyframeContainer->push_back(keyframe1);
keyframeContainer->push_back(keyframe2);
root->addUpdateCallback(new RotateCallback(keyframeContainer));
viewer->setUpViewInWindow(200, 200, 800, 600);
viewer->setSceneData(root);
return viewer->run();
}
定義了關鍵幀還不能形成動畫,需要知道關鍵幀中間幀的值,這需要插值來處理。operator()函式中有一段計算關鍵幀中間值的程式碼,使用的其實就是線性的插值。osgAnimation把插值的過程也寫成了一個類,這就是下面要提到的插值器(Interpolator)
4.2 插值器
插值器的作用是利用各種插值方式計算關鍵幀中間幀資料。插值器進行插值的資料型別很顯然必須和關鍵幀中儲存的值型別是一致的。插值器類提供了getValue的方法,用來獲取某個時間點中間幀的值,例如線性的插值器定義的getValue的實現如下:
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;
}
//計算到當前時間點所處在哪兩個關鍵幀之間,i值是兩個關鍵幀中時間點較小的哪一個關鍵幀,有點類似於C++標準庫中的lower_bound函式返回的值
int i = this->getKeyIndexFromTime(keyframes,time);
float blend = (time - keyframes[i].getTime()) / ( keyframes[i+1].getTime() - keyframes[i].getTime());
const TYPE& v1 = keyframes[i].getValue();
const TYPE& v2 = keyframes[i+1].getValue();
//線性的插值演算法
result = v1*(1-blend) + v2*blend;
}
在引入了插值器之後,再次修改上面的程式如下:
class RotateCallback : public osg::NodeCallback
{
public:
RotateCallback(osgAnimation::DoubleKeyframeContainer *kfc)
{
_keyframes = kfc;
_startTick = osg::Timer::instance()->tick();
_interpolator = new osgAnimation::DoubleLinearInterpolator();
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
double currentTick = osg::Timer::instance()->tick();
double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);
double currentRot;
_interpolator->getValue(*_keyframes, elaspedTime, currentRot);
osg::Quat rotQuat(currentRot, osg::Z_AXIS);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
protected:
osgAnimation::DoubleKeyframeContainer *_keyframes;
osgAnimation::DoubleLinearInterpolator *_interpolator;
double _startTick;
};
觀察上面修改的程式碼,在引入插值器之後,僅僅使用一行程式碼代替了之前程式碼的計算(這些計算由插值器在它的實現中代勞了)。需要注意的是在執行程式之後會發現場景中的模型在旋轉一圈之後就停下來了【想一想為什麼?】後續會作進一步處理。
4.3 取樣器
這裡的關鍵幀容器和插值器是獨立開的,osgAnimation將這二者組合起來,構成一個取樣器(Sampler),取樣器類的實現僅僅是將二者組合在一起,並沒有額外的內容,在使用取樣器之後,再次修改程式:
class RotateCallback : public osg::NodeCallback
{
public:
RotateCallback(osgAnimation::DoubleLinearSampler *dls)
{
_startTick = osg::Timer::instance()->tick();
_sampler = dls;
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
double currentTick = osg::Timer::instance()->tick();
double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);
double currentRot;
_sampler->getValueAt(elaspedTime, currentRot);
osg::Quat rotQuat(currentRot, osg::Z_AXIS);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
protected:
osgAnimation::DoubleLinearSampler* _sampler;
double _startTick;
};
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
root->addChild(cowNode);
osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI*2);
osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
keyframeContainer->push_back(keyframe1);
keyframeContainer->push_back(keyframe2);
osg::ref_ptr<osgAnimation::DoubleLinearSampler> sampler = new osgAnimation::DoubleLinearSampler();
sampler->setKeyframeContainer(keyframeContainer.get());
root->addUpdateCallback(new RotateCallback(sampler.get()));
viewer->setUpViewInWindow(200, 200, 800, 600);
viewer->setSceneData(root);
return viewer->run();
}
通過上面介紹的關鍵幀、插值器、取樣器,其實可以處理大部分的動畫了。但是要實現更為集中的動畫資料管理,以及為功能的整合提供良好的操作介面,依然需要一個封裝了關鍵幀取樣器,並負責關聯動畫效果與場景物件的工具,這就是下面將要介紹的“動畫頻道”(Channel)
4.4 頻道
在上面實現的簡單動畫程式碼中,使用取樣器計算得到的值,直接設定給了場景節點MatrixTransform。這樣做缺少了一些靈活性。比如當有兩個取樣器計算的結果都需要對最後的姿態產生影響時,應該使用哪一個呢?這兩個效果可能有一個權重,最後得到的Matrix值是二者的加權計算的結果,然後再把這一結果設定給MatrixTransform,最終反應到模型姿態的變化上。
也就是說,有必要把取樣器計算得到的結果進行某些處理,osgAnimation使用的方式是:把這一計算過程分離出來,取樣器計算得到的結果,可以用一個類來管理起來,這個類決定了取樣器計算結果的權重(weight)、優先順序(priority),打個簡單的比方:
在上學的時候大家的作業都是直接交給老師的,也就是說大家寫好的作業老師直接能看到。現在老師覺得學生太多,想挑一部分來看,於是讓學習委員把大家的作業收集起來,先看一遍,然後挑一些內容再上交給老師看。學習委員可以有各種許可權,可以修改一些作業、可以刪除一些作業等等,也就是說老師看到的最終版本是被處理過的。
這裡面涉及到的學習委員這個角色,在osgAnimation裡面就是osgAnimation::Target, 這個類就是用來處理各個取樣器得到的結果的(類似於學生上交的作業),它可以賦予每個計算結果優先順序、權重等,從而影響最終的結果。Channel類的update函式會在每一幀中被呼叫,它的實現如下:
virtual void update(double time, float weight, int priority)
{
// skip if weight == 0
if (weight < 1e-4)
return;
typename SamplerType::UsingType value;
//第一部和前面Sampler中的實現一樣,使用Samper中的插值器計算得到中間幀的值,並儲存在value中
_sampler->getValueAt(time, value);
//把這個value的值傳給Target(例子中提到的學習委員,讓他更加權重再處理value值,並把最終的結果儲存在Target類的成員中)
_target->update(weight, value, priority);
}
也就是說,外部三維場景節點拿到的插值變換的結果是經過Target處理過的,換言之Target類儲存著外部場景需要的最終想要的值,可以呼叫Target函式的getValue方法來獲得這個值。引入頻道之後,繼續修改之前的程式碼:
class RotateCallback : public osg::NodeCallback
{
public:
RotateCallback(osgAnimation::DoubleLinearChannel *dls)
{
_startTick = osg::Timer::instance()->tick();
_channel = dls;
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
double currentTick = osg::Timer::instance()->tick();
double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);
if (!_channel)
return;
//每次回撥需要重置前一次的權重為0
_channel->getTarget()->reset();
_channel->update(elaspedTime, 1.0, 0);
auto doubleTarget = dynamic_cast<osgAnimation::DoubleTarget*>(_channel->getTarget());
double currentRot;
if (doubleTarget)
currentRot = doubleTarget->getValue();
osg::Quat rotQuat(currentRot, osg::Z_AXIS);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
protected:
osgAnimation::DoubleLinearChannel* _channel;
double _startTick;
};
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
root->addChild(cowNode);
osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI * 2);
osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
keyframeContainer->push_back(keyframe1);
keyframeContainer->push_back(keyframe2);
osg::ref_ptr<osgAnimation::DoubleLinearSampler> sampler = new osgAnimation::DoubleLinearSampler();
sampler->setKeyframeContainer(keyframeContainer.get());
osg::ref_ptr<osgAnimation::DoubleLinearChannel> channel = new osgAnimation::DoubleLinearChannel();
channel->setSampler(sampler.get());
root->addUpdateCallback(new RotateCallback(channel.get()));
viewer->setUpViewInWindow(200, 200, 800, 600);
viewer->setSceneData(root);
return viewer->run();
}
通過上面的分析,可以總結上面描述各種物件之間的相互關係如下:
4.5 動畫
假設使用者已經設定了多個頻道,要將這些效果混合起來,最終賦予要執行動態效果的場景物件,則可以將該過程稱為一場動畫的實現。在osgAnimation中使用Animation類來管理Channels。Animation類提供了管理多個Channel的方式非常簡單,它提供了一個儲存多個Channel陣列的變數,除此之外Animation多了一個播放模式,可以讓動畫實現一次播放、暫停播放、迴圈播放以及來回播放(正向與反向)。
在前面引入插值器的4.2中,當修改程式使用插值器之後,發現場景在旋轉一圈之後就暫停了,這是因為當傳入的時間點的值大於關鍵幀最後一幀後,之後返回的取值就一直保持不變了(都是最後一幀的取值)。Animation通過提供的幾種播放方式,可以使用LOOP的播放方式,讓動畫一直來回的迴圈。關於Animation的處理方式的程式碼如下:
bool Animation::update (double time, int priority)
{
// ...
switch (_playmode)
{
case ONCE:
if (t > _originalDuration)
{
for (ChannelList::const_iterator chan = _channels.begin();
chan != _channels.end(); ++chan)
(*chan)->update(_originalDuration, _weight, priority);
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;
}
ChannelList::const_iterator chan;
for( chan=_channels.begin(); chan!=_channels.end(); ++chan)
{
(*chan)->update(t, _weight, priority);
}
return true;
}
前面寫程式碼的方式應該就是這裡面所描述的ONCE,當t值大於_originalDuration時,獲取的是最後一個關鍵幀的取值,導致場景停留在最後一個關鍵幀處。
STAY模式,效果和ONCE一樣
LOOP模式, 當場景時間點大於起始關鍵幀和結束關鍵幀之差時,對時間點t求模,得到的結果是場景的時間點又會進入到起始關鍵幀和結束關鍵幀之中,從而插值器可以得到正確的取值
PPONG模式,是來回模式,從實現中可以看到,根據時間點對關鍵幀時間長度求模,同時根據得到值的奇偶性來判斷是來還是回,計算正確的時間t,實現也很好理解。在瞭解了這些之後,可以改進之前的程式,讓模型旋轉可以迴圈起來:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
double currentTick = osg::Timer::instance()->tick();
double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);
//到時間點超過最後一個關鍵幀時,通過求模讓時間點再次落入起始和終點關鍵幀時間點之內
double startKeyframeTime = _channel->getStartTime();
double endKeyframeTime = _channel->getEndTime();
double keyframeTimeLength = endKeyframeTime - startKeyframeTime;
if (elaspedTime > keyframeTimeLength)
elaspedTime = fmod(elaspedTime, keyframeTimeLength);
if (!_channel)
return;
//每次回撥需要重置前一次的權重為0
_channel->getTarget()->reset();
_channel->update(elaspedTime, 1.0, 0);
auto doubleTarget = dynamic_cast<osgAnimation::DoubleTarget*>(_channel->getTarget());
double currentRot;
if (doubleTarget)
currentRot = doubleTarget->getValue();
osg::Quat rotQuat(currentRot, osg::Z_AXIS);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
```
同樣的,在引入了Animation之後,更新程式,使用Animation來重新改造程式。
class RotateCallback : public osg::NodeCallback
{
public:
RotateCallback(osgAnimation::Animation *dls)
{
_startTick = osg::Timer::instance()->tick();
_animation = dls;
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt)
{
double currentTick = osg::Timer::instance()->tick();
double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);
_animation->resetTargets();
_animation->setPlayMode(osgAnimation::Animation::LOOP);
_animation->update(elaspedTime);
_animation->setWeight(1.0);
osgAnimation::ChannelList cls = _animation->getChannels();
double currentRot = dynamic_cast<osgAnimation::DoubleTarget*>(cls.at(0)->getTarget())->getValue();
osg::Quat rotQuat(currentRot, osg::Z_AXIS);
osg::Matrix rotMatrix(rotQuat);
mt->setMatrix(rotMatrix);
}
traverse(node, nv);
}
protected:
osgAnimation::Animation* _animation;
double _startTick;
};
int main()
{
osg::ref_ptr viewer = new osgViewer::Viewer;
osg::ref_ptr cowNode = osgDB::readNodeFile(“cow.osg”);
osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
root->addChild(cowNode);
osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI * 2);
osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
keyframeContainer->push_back(keyframe1);
keyframeContainer->push_back(keyframe2);
osg::ref_ptr<osgAnimation::DoubleLinearSampler> sampler = new osgAnimation::DoubleLinearSampler();
sampler->setKeyframeContainer(keyframeContainer.get());
osg::ref_ptr<osgAnimation::DoubleLinearChannel> channel = new osgAnimation::DoubleLinearChannel();
channel->setSampler(sampler.get());
osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation();
animation->addChannel(channel.get());
root->addUpdateCallback(new RotateCallback(animation.get()));
viewer->setUpViewInWindow(200, 200, 800, 600);
viewer->setSceneData(root);
return viewer->run();
}
“`
通過上面的分析,已經寫出了osgAnimation幾個主要類之間的關係和它們的使用方式了。前面寫的程式碼不是osgAnimation的最佳實踐做法,甚至是非常糟糕的寫法,為的僅僅是闡述osgAnimation主要類的原理以及為什麼作者要這樣設計。
在寫上面程式碼過程中,特別是Channel和Animation類引入之後的寫法,我個人使用獲取Target的方式非常的不爽,最終計算結果儲存在Target之中,我獲取到Target然後設定給MatrixTransform。在Animation類中甚至都沒有獲取Target的函式,上面的程式碼強行從Animation中獲取Channel然後再獲取Target的方式非常的奇怪。程式碼寫到這裡也應該很清楚了,這個Target肯定和場景的回撥之間有一些關聯,不需要我們使用Channel強行獲取這個值再去設定,應該會在內部完成整件事情。
接下來會繼續討論Target的更新以及對Animation的一些管理,請參考下一篇文章《OSG動畫庫Animation解析(二)》。
參考文獻:
- 王銳 錢學雷 《OpenSceneGraph三維渲染引擎設計與實踐》[清華大學出版社]