|
作者:王锐
第一日
好了,在开始第一天的行程之前,请先打开您最惯用的编程工具吧:VisualStudio?CodeBlocks?UltraEdit?SourceInsight?Emacs?Vim?或者只是附件里那个制作低劣的记事本……总之请打开它们,打开OpenSceneGraph-2.6.0的源代码文件夹,打开osgViewer/ViewerBase.cpp这个文件……同样的话就不再重复了。但是如果您没有这样做,而仅仅是一边聊着QQ,一边在电话里和女朋友发着牢骚,一边还要应付突然冲进来的老板,一边打开这篇教程的话——对不起,我想您会在1分钟以内就对其感到厌烦,因为它就像天书一样,或者就像一坨乱七八糟的毛线团,只有家里那只打着哈欠的小猫可能会过来捅上一下。
不过不用担心,如果您已经耐着性子读到了这里,并且已经看到了ViewerBase.cpp中总共752行规规矩矩的代码,那么我会尽全力协助您继续走完漫长的一帧的旅程,并在其中把自己所知所得(不仅仅局限于那个该死的frame函数,而是遍及OSG的方方面面)全数灌输给您。当然也希望您尽全力给我以打压、批评和指正,我只是一个普普通通的梦想着先找个女朋友成家立业的毛头小子而已,如果我的话全都是对的,那么那些真正的图形学权威们一定都住在寒风刺骨的珠穆朗玛峰顶上。
好了,废话不再多说。我们这就开始。
当前位置:osgViewer/ViewerBase.cpp第571行,osgViewer:: ViewerBase:: frame()
frame函数的内容本身几乎一眼就可以看完。不过要注意的是,这个函数是ViewerBase类的成员函数,而非Viewer类。因此,无论对于单视景器的Viewer类,还是多视景器的CompositeViewer类,frame函数的内容都是相同的(因为它们都没有再重写这个函数的内容)。
该函数所执行的主要工作如下:
- 如果这是仿真系统启动后的第一帧,则执行viewerInit();此时如果还没有执行realize()函数,则执行它。
- 执行advance函数。
- 执行eventTraversal函数,顾名思义,这个函数将负责处理系统产生的各种事件,诸如鼠标的移动,点击,键盘的响应,窗口的关闭等等,以及摄像机与场景图形的事件回调(EventCallback)。
- 执行updateTraversal函数,这个函数负责遍历所有的更新回调(UpdateCallback);除此之外,它的另一个重要任务就是负责更新DatabasePager与ImagePager这两个重要的分页数据处理组件。
- 执行renderingTraversals函数,这里将使用较为复杂的线程处理方法,完成场景的筛选(cull)和绘制(draw)工作。
下面我们就按照1~5的顺序,开始我们的源代码解读之旅。
当前位置:osgViewer/View.cpp第227行,osgViewer:: View:: init()
Viewer:: viewerInit函数只做了一件事,就是调用View:: init()函数,而这个init函数的工作似乎也是一目了然的:无非就是完成视景器的初始化工作而已。
不过在我们离开这个函数,继续我们的旅程之前,还是仔细探究一下,这个初始化工作到底包含了什么?
阅读某个函数的源代码过程中,如果能够大致知道这个函数的主要工作,并了解其中用到的变量的功能,那么即使只有很少的注释内容,应该也可以顺利地读完所有代码。如果对一些命名晦涩的变量不甚理解,或者根本不知道这个函数于运行流程中有何用途,那么理解源代码的过程就会麻烦很多。
View:: init函数中出现了两个重要的类成员变量:_eventQueue和_cameraManipulator,并且还将一个osgGA:: GUIEventAdapter的实例传入后者的初始化函数。
代码如下:- osg::ref_ptr<osgGA::GUIEventAdapter> initEvent = _eventQueue->createEvent();
- initEvent->setEventType(osgGA::GUIEventAdapter::FRAME);
- if (_cameraManipulator.valid())
- _cameraManipulator->init(*initEvent, *this);
复制代码 从变量的名称可以猜测出_eventQueue的功能,它用于储存该视景器的事件队列。OSG中代表事件的类是osgGA:: GUIEventAdapter,它可以用于表达各种类型的鼠标、键盘、触压笔和窗口事件。在用户程序中,我们往往通过继承osgGA:: GUIEventHandler类,并重写handle函数的方法,获取实时的鼠标/键盘输入,并进而实现相应的用户代码(参见osgkeyboardmouse)。
_eventQueue除了保存一个GUIEventAdapter的链表之外,还提供了一系列对链表及其元素的操作函数,这其中,createEvent函数的作用是分配和返回一个新的GUIEventAdapter事件的指针。
随后,这个新事件的类型被指定为FRAME事件,即每帧都会触发的一个事件。
那么,_cameraManipulator呢?没错,它就是视景器中所用的场景漫游器的实例。通常我们都会使用setCameraManipulator来设置这个变量的内容,例如轨迹球漫游器(TrackballManipulator)可以使用鼠标拖动来观察场景,而驾驶漫游器(DriveManipulator)则使用类似于汽车驾驶的效果来实现场景的漫游。
上面的代码将新创建的FRAME事件和Viewer对象本身传递给_cameraManipulator的init函数,不同的漫游器(如TrackballManipulator、DriveManipulator)会重写各自的init函数,实现自己所需的初始化工作。如果读者希望自己编写一个场景的漫游器,那么覆写并使用osgGA:: MatrixManipulator:: init就可以灵活地初始化自定义漫游器的功能了,它的调用时机就在这里。
那么,回到viewerInit函数……很好,这次似乎没有更多的内容了。没想到一个短短的函数竟然包含了那么多的信息,看来草率地阅读还真是使不得。
解读成果:
osgGA::EventQueue::createEvent,osgGA::MatrixManipulator::init,osgViewer::View::init,osgViewer::Viewer::viewerInit。
[ 本帖最后由 array 于 2008-8-14 14:53 编辑 ] |
|