array 发表于 2008-8-28 13:54:00

OSG原创教程:最长的一帧(14)

当前位置:osgDB/DatabasePager第250行,osgDB:: DatabasePager::updateSceneGraph()
好了,请回到第十一日的故事中来,毕竟我们主要的任务还远未结束。现在我们需要重新审视DatabasePager::updateSceneGraph的工作了。

1、DatabasePager::removeExpiredSubgraphs:用于去除已经过期的场景子树。

我们首先遍历DatabasePager::_pagedLODList这个成员变量,并执行其中每个PagedLOD对象的removeExpiredChildren函数,取得其中已经过期的子节点并记录到一个列表里。将这些过期节点标记为“可删除”,并传递给_fileRequestQueue->_childrenToDeleteList成员,也就是前文所述的“待删除列表”,同时唤醒DatabaseThread线程。

下一步,将过期节点从_pagedLODList中删除,由于它们已经被传递到“待删除列表”当中,因此ref_ptr引用计数不会减到零,也就不会在主仿真循环中触发内存释放(delete)动作。

最后还要执行SharedStateManager::prune函数。这里的osgDB::SharedStateManager指的是一个渲染状态共享管理器,它负责记录分页数据库中各个节点的渲染属性(StateAttribute),并判断节点之间是否共享了同一个渲染属性,从而节省加载和预编译的时间。prune函数的工作是从SharedStateManager中剔除那些没有被共享的渲染属性。
如果希望启用SharedStateManager(默认是关闭的,其性能目前可能没有想象的那么好),需要在进入仿真循环之前执行:osgDB::Registry::instance()->getOrCreateSharedStateManager();2、DatabasePager::addLoadedDataToSceneGraph:用于向场景图形中添加新载入的数据。

这里首先取得“待合并列表”_dataToMergeList,并遍历其中每一个DatabaseRequest对象。

遍历过程中,首先执行SharedStateManager::share函数,将新加载节点_loadedModel的渲染属性保存到SharedStateManager管理器中。

随后执行DatabasePager::registerPagedLODs,在加载的节点及其子树中搜索PagedLOD节点,并添加到刚刚提到的_pagedLODList列表中。

最后,判断DatabaseRequest::_groupForAddingLoadedSubgraph对象(也就是新加载节点在场景中的父节点)是否合法,并将DatabaseRequest::_loadedModel添加为它的子节点。

这里反复提到的PagedLOD节点,实际上揭开了关于DatabasePager的又一个关键问题的帷幕,那就是:什么情形下我们才会用到DatabasePager?

答案是使用osg:: PagedLOD和osg:: ProxyNode节点的时候。下面我们就分别阐述一下这两种类型的节点的功能和用法。

首先是内容较为简单的ProxyNode节点,从名字我们大概可以猜测出,它的功能是“代理节点”,就像网络上的代理服务器那样,使用代理节点可以指向某个已经存在的模型文件,并在需要的时候加载它。

事实正是如此,当我们希望在场景仿真循环开始之后才加载某个模型文件时,可以使用ProxyNode节点来指定要加载的文件名,并在场景筛选(Cull)的过程中加载模型,加载后的新节点将作为ProxyNode节点的子节点。示例代码如下:osg::ProxyNode* proxyNode = new osg:: ProxyNode;
proxyNode->setFileName(0, “nodefile.osg”);这里setFileName的两个参数分别是新载入的子节点在ProxyNode下的位置,以及对应模型文件的名称。我们还可以使用ProxyNode::setLoadingExternalReferenceMode来设置加载的时机,例如首先设置为不自动加载(NO_AUTOMATIC_LOADING),在适当的时候再设置为立即加载(LOAD_IMMEDIATELY)。

然后是PagedLOD节点。

有关场景的LOD(细节层次,Level of Detail)技术,以及osg:: LOD节点的使用,本教程不会做过多的阐述。osg:: PagedLOD类事实上继承自osg:: LOD,它的工作同样是按照用户的可视范围,将多个子节点作为同一场景的多个细节层次。这样可以在视点靠近物体时呈现较多的物体细节,而在远离时则仅仅呈现一个简化的模型,从而降低了运算和绘制的负担。但是,对于LOD节点而言,由于其子树的规模往往是十分复杂而宏伟的,因此在加载时很可能耗费过长的时间,且占用了庞大的系统资源,不利于其它工作的开展。因此OSG中为用户提供了PagedLOD节点:它运用了分页数据库的功能,将多个模型数据分批加载到场景图形中(作为PagedLOD的子节点);并根据用户当前的可视范围,将那些一段时间内均无法被看到的PagedLOD子节点剔除出场景图形,以节约系统资源;当然,如果用户移动了视点之后,被剔除的节点又重新进入视野,那么OSG的分页数据库线程将重新加载它。

以上所述就是我们使用DatabasePager的时机。假设场景中需要四级Node节点及其子树来表达某个场景(例如一座城市,一片地形图,或者一架设计精良的战斗机)在不同视距下的细节层次,且这些模型已经保存为诸如“node_1.osg”的文件形式。那么可以采用这样的代码来加载场景:osg::PagedLOD* pagedLOD = new osg::PagedLOD;
pagedLOD->setFileName(0, “node_1.osg”);
pagedLOD->setRange(0, minRange1, maxRange1);
pagedLOD->setFileName(1, “node_2.osg”);
pagedLOD->setRange(1, minRange2, maxRange2);
……上述代码的含义是:指定PagedLOD的各级子节点,0级为node_1.osg的内容,其可视范围为(minRange1,maxRange1);1级-4级以此类推。此时OSG将自动使用DatabasePager的处理能力,分批次加载场景数据,并根据用户当前视野和过期时间来决定是否剔除或再次加载场景数据。

DatabasePager::setExpiryDelay用于决定视野外对象的过期时间。此外,我们还可以运用PagedLOD::setPriorityOffset来设置子节点的优先级,setCenter来设置中心点等等。

注意下图中两种不同的场景结构。第一种结构是前面有关PagedLOD的代码所呈现的,也是适合于分页数据库运作的;而后一种结构虽然也可以达到同样的目的(通过setRange实现),但它也可能给程序带来不必要的运算负担。
附图1

最后,我们讨论一下ProxyNode和PagedLOD的区别:ProxyNode的功能主要是在运行时加载一个或多个模型文件作为子节点;而PagedLOD虽然可以实现相同的功能,但它还有另外一项重要的工作,那就是根据用户的视点范围来实现场景树的“修剪”——剔除对场景长期没有助益的节点,加载用户可见的节点。这也是这几日以来我们一直强调的“分页”的精髓所在了吧。

解读成果:
DatabasePager::updateSceneGraph,ProxyNode与PagedLOD节点。
悬疑列表:
如何调度和实现OSG的多线程机制?什么是“渲染目标实现方式”?如何使用compileGLObjects完成预编译工作?Operation对象在线程中的应用时机是什么?

shengrendan2 发表于 2008-9-27 15:56:03

坐了

liangyou-2008 发表于 2009-3-11 22:26:20

好东西,牛啊牛
页: [1]
查看完整版本: OSG原创教程:最长的一帧(14)