转自封狼居胥:https://blog.csdn.net/qq_16123279/article/details/82665053
1.先看看继承关系:
PagedLOD继承了LOD继承了Group继承了Node;
2.简单说说OSG的每一帧干的事:
OSG其实很简单就是封装了一个循环,在这个循环里面,osg不断调用各种NodeVisitor,去处理加入场景的各个Node。
void ViewerBase::frame(double simulationTime) { if (_done) return; // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl; if (_firstFrame) { viewerInit(); if (!isRealized()) { realize(); } _firstFrame = false; } advance(simulationTime); eventTraversal(); updateTraversal(); renderingTraversals(); }
其中只有renderingTraversals();是viewerBase自己实现的,eventTraversal()和 updateTraversal();交由CompositeViewer和Viewer实现。
3.访问器工作原理
这三大遍历函数里面又是很多具体的实现,其中各种访问器(NodeVisitor的apply(Node* pNode))和各种Node(Node的traverse(NodeVisitor* pVisitor))在里面扮演了重要的角色。首先apply某一个node,然后apply里面可以写对应的操作,然后node的traverse可以应用一个遍历器写上某个具体node的功能,node还有Node::accept(NodeVisitor& nv)里面如果不写什么的话基本就是nv.apply(*this);我们来看一下具体的代码:
Node里面遍历相关函数
/** Visitor Pattern : calls the apply method of a NodeVisitor with this node's type.*/ virtual void accept(NodeVisitor& nv); /** Traverse upwards : calls parents' accept method with NodeVisitor.*/ virtual void ascend(NodeVisitor& nv); /** Traverse downwards : calls children's accept method with NodeVisitor.*/ virtual void traverse(NodeVisitor& /*nv*/) {} void Node::accept(NodeVisitor& nv) { if (nv.validNodeMask(*this)) { nv.pushOntoNodePath(this); nv.apply(*this); nv.popFromNodePath(); } } void Node::ascend(NodeVisitor& nv) { std::for_each(_parents.begin(),_parents.end(),NodeAcceptOp(nv)); }
可以看到 accpt就是调用一个visitor的apply()函数,traverse函数没有实现,因为这个函数代表了某个节点的特性,比如Group节点,LOD节点,PagedLOD节点等等,这些节点的traverse实现都是不同的。而ascend会像父节点遍历,是traverse的反向。向孩子或向父节点遍历是visitor说了算。
NodeVisitor
inline void NodeVisitor::traverse(Node& node) { if (_traversalMode==TRAVERSE_PARENTS) node.ascend(*this); else if (_traversalMode!=TRAVERSE_NONE) node.traverse(*this); } void NodeVisitor::apply(Node& node) { traverse(node); } void NodeVisitor::apply(Drawable& drawable) { apply(static_cast<Node&>(drawable)); } void NodeVisitor::apply(Geometry& drawable) { apply(static_cast<Drawable&>(drawable)); } void NodeVisitor::apply(Geode& node) { apply(static_cast<Group&>(node)); } void NodeVisitor::apply(Billboard& node) { apply(static_cast<Geode&>(node)); } void NodeVisitor::apply(Group& node) { apply(static_cast<Node&>(node)); }
可以看到NodeVisitor里面出了对Node基本遍历做了处理,其他的节点均未实现,需要的什么功能,我们可以继承来解决,调用的时候有两种方式:
Node.accept(NodeVisitor);//最好的办法 NodeVisitor.apply(Node);//也行(不推荐)
所以结合以上的说了一大堆,我们明白一个节点的特性应该去他的traverse函数里面去看,然后我们来看看PagedLOD的特性:
LOD特性
详细代码可以自己去对应的源码部分看,这里只是说一下其算法步骤:
1.根据用户设定的距离模式(视点到包围球中心距离,在屏幕上占有的像素大小),计算一个距离;
2.判断_rangeList的尺寸与numChildren,如果小于孩子数量就让numChildren与_randgelist的数量相等
3.遍历numChildren,在范围内的就accep(nv),不accept(nv)的节点将不会被渲染遍历到就不会被渲染出来
相关代码: switch(nv.getTraversalMode()) { case(NodeVisitor::TRAVERSE_ALL_CHILDREN): std::for_each(_children.begin(),_children.end(),NodeAcceptOp(nv)); break; case(NodeVisitor::TRAVERSE_ACTIVE_CHILDREN): { float required_range = 0; if (_rangeMode==DISTANCE_FROM_EYE_POINT) { required_range = nv.getDistanceToViewPoint(getCenter(),true); } else { osg::CullStack* cullStack = nv.asCullStack(); if (cullStack && cullStack->getLODScale()) { required_range = cullStack->clampedPixelSize(getBound()) / cullStack->getLODScale(); } else { // fallback to selecting the highest res tile by // finding out the max range for(unsigned int i=0;i<_rangeList.size();++i) { required_range = osg::maximum(required_range,_rangeList[i].first); } } } unsigned int numChildren = _children.size(); if (_rangeList.size()<numChildren) numChildren=_rangeList.size(); for(unsigned int i=0;i<numChildren;++i) { if (_rangeList[i].first<=required_range && required_range<_rangeList[i].second) { _children[i]->accept(nv); } } break; }
PagedLOD
pagedLOD继承了LOD其traveser比LOD复杂得多,先看看算法步骤:
1.如果遍历器类型是CULL_VISITOR,就把该遍历器上一帧遍历完成后的帧数记录下来(意思就是保存上一帧是第几帧)。
2.对于每一个孩子计算其距离(required_range),跟LOD的相同。
3.循环_rangeList(这个东西保存了所有孩子的范围),判断刚才计算到的required_range是否在某个个_rangeList里面,如果范围链的尺寸没有超过当前孩子的数量就accept(显示) 。
bool updateTimeStamp = nv.getVisitorType()==osg::NodeVisitor::CULL_VISITOR; int lastChildTraversed = -1; bool needToLoadChild = false; for(unsigned int i=0;i<_rangeList.size();++i) { if (_rangeList[i].first<=required_range && required_range<_rangeList[i].second) { if (i<_children.size()) { if (updateTimeStamp)//如果是裁剪遍历器 { _perRangeDataList[i]._timeStamp=timeStamp;//记录相对时间 _perRangeDataList[i]._frameNumber=frameNumber;//记录帧数 } //如果请求的距离在节点的可见范围内,且该节点存在, //那么渲染该节点,且记录该节点所在的索引 _children[i]->accept(nv); lastChildTraversed = (int)i; } else { //如果请求距离在节点范围内,但是该组下没有这个节点那么就加载 needToLoadChild = true; } } }
4.加载之前消失的节点
if (needToLoadChild) { unsigned int numChildren = _children.size(); //lastChildTraversed记录了最后一个被for遍历渲染节点的索引 //这个判断的意思就是: //1.组里面有节点 //2.组里面被渲染的最后一个节点, //但不是组里面最后一个节点。(这里可能是上面accept会漏然后做的补充) //条件1、2成立为真 if (numChildren > 0 && ((int)numChildren - 1) != lastChildTraversed) { //如果访问器是筛选访问器 if (updateTimeStamp) { //记录渲染开始到此刻的总时间 _perRangeDataList[numChildren - 1]._timeStamp = timeStamp; //记录渲染开始到此刻的总帧数 _perRangeDataList[numChildren - 1]._frameNumber = frameNumber; } //渲染组里面现有的最后一个节点,因为lastChildTraversed不是组里的最后一个 _children[numChildren - 1]->accept(nv); } //从磁盘加载节点 //@@_disableExternalChildrenPaging用户可以通过设置这个变量来禁用加载 //@@nv.getDatabaseRequestHandler()默认就是osgDB::DatabasePager //@@numChildren < _perRangeDataList.size()有被卸载掉的节点 if (!_disableExternalChildrenPaging && nv.getDatabaseRequestHandler() && numChildren < _perRangeDataList.size()) { // compute priority from where abouts in the required range the distance falls. float priority = (_rangeList[numChildren].second - required_range) / (_rangeList[numChildren].second - _rangeList[numChildren].first); // invert priority for PIXEL_SIZE_ON_SCREEN mode if (_rangeMode == PIXEL_SIZE_ON_SCREEN) { priority = -priority; } // modify the priority according to the child's priority offset and scale. priority = _perRangeDataList[numChildren]._priorityOffset + priority * _perRangeDataList[numChildren]._priorityScale; //_databasePath一个字符串存放文件夹路径, //就是说需要用pagedLOD加载的模型节点,可以统一放到一个文件夹下 //然后设置了_databasePath直接用模型的文件名就行了,减少内存占用 if (_databasePath.empty()) { nv.getDatabaseRequestHandler()->requestNodeFile(_perRangeDataList[numChildren]._filename, nv.getNodePath(), priority, nv.getFrameStamp(), _perRangeDataList[numChildren]._databaseRequest, _databaseOptions.get()); } else { // 将DabasePath预置到子文件名。 //requestNodeFile是个重点,下面分析 nv.getDatabaseRequestHandler()->requestNodeFile(_databasePath + _perRangeDataList[numChildren]._filename, nv.getNodePath(), priority, nv.getFrameStamp(), _perRangeDataList[numChildren]._databaseRequest, _databaseOptions.get()); } } }
requestNodeFile
void DatabasePager::requestNodeFile(const std::string& fileName, osg::NodePath& nodePath, float priority, const osg::FrameStamp* framestamp, osg::ref_ptr<osg::Referenced>& databaseRequestRef, const osg::Referenced* options) { osgDB::Options* loadOptions = dynamic_cast<osgDB::Options*>(const_cast<osg::Referenced*>(options)); if (!loadOptions) { loadOptions = Registry::instance()->getOptions(); } else { // OSG_NOTICE<<"options from requestNodeFile "<<std::endl; } if (!_acceptNewRequests) return; if (nodePath.empty()) { OSG_NOTICE << "Warning: DatabasePager::requestNodeFile(..) passed empty NodePath, so nowhere to attach new subgraph to." << std::endl; return; } osg::Group* group = nodePath.back()->asGroup(); if (!group) { OSG_NOTICE << "Warning: DatabasePager::requestNodeFile(..) passed NodePath without group as last node in path, so nowhere to attach new subgraph to." << std::endl; return; } osg::Node* terrain = 0; for (osg::NodePath::reverse_iterator itr = nodePath.rbegin(); itr != nodePath.rend(); ++itr) { if ((*itr)->asTerrain()) terrain = *itr; } double timestamp = framestamp ? framestamp->getReferenceTime() : 0.0; unsigned int frameNumber = framestamp ? framestamp->getFrameNumber() : static_cast<unsigned int>(_frameNumber); // #define WITH_REQUESTNODEFILE_TIMING #ifdef WITH_REQUESTNODEFILE_TIMING osg::Timer_t start_tick = osg::Timer::instance()->tick(); static int previousFrame = -1; static double totalTime = 0.0; if (previousFrame != frameNumber) { OSG_NOTICE << "requestNodeFiles for " << previousFrame << " time = " << totalTime << std::endl; previousFrame = frameNumber; totalTime = 0.0; } #endif // search to see if filename already exist in the file loaded list. bool foundEntry = false; if (databaseRequestRef.valid()) { DatabaseRequest* databaseRequest = dynamic_cast<DatabaseRequest*>(databaseRequestRef.get()); bool requeue = false; if (databaseRequest) { OpenThreads::ScopedLock<OpenThreads::Mutex> drLock(_dr_mutex); if (!(databaseRequest->valid())) { OSG_INFO << "DatabaseRequest has been previously invalidated whilst still attached to scene graph." << std::endl; databaseRequest = 0; } else { OSG_INFO << "DatabasePager::requestNodeFile(" << fileName << ") updating already assigned." << std::endl; databaseRequest->_valid = true; databaseRequest->_frameNumberLastRequest = frameNumber; databaseRequest->_timestampLastRequest = timestamp; databaseRequest->_priorityLastRequest = priority; ++(databaseRequest->_numOfRequests); foundEntry = true; if (databaseRequestRef->referenceCount() == 1) { OSG_INFO << "DatabasePager::requestNodeFile(" << fileName << ") orphaned, resubmitting." << std::endl; databaseRequest->_frameNumberLastRequest = frameNumber; databaseRequest->_timestampLastRequest = timestamp; databaseRequest->_priorityLastRequest = priority; databaseRequest->_group = group; databaseRequest->_terrain = terrain; databaseRequest->_loadOptions = loadOptions; databaseRequest->_objectCache = 0; requeue = true; } } } if (requeue) _fileRequestQueue->add(databaseRequest); } if (!foundEntry) { OSG_INFO << "In DatabasePager::requestNodeFile(" << fileName << ")" << std::endl; OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_fileRequestQueue->_requestMutex); if (!databaseRequestRef.valid() || databaseRequestRef->referenceCount() == 1) { osg::ref_ptr<DatabaseRequest> databaseRequest = new DatabaseRequest; databaseRequestRef = databaseRequest.get(); databaseRequest->_valid = true; databaseRequest->_fileName = fileName; databaseRequest->_frameNumberFirstRequest = frameNumber; databaseRequest->_timestampFirstRequest = timestamp; databaseRequest->_priorityFirstRequest = priority; databaseRequest->_frameNumberLastRequest = frameNumber; databaseRequest->_timestampLastRequest = timestamp; databaseRequest->_priorityLastRequest = priority; databaseRequest->_group = group; databaseRequest->_terrain = terrain; databaseRequest->_loadOptions = loadOptions; databaseRequest->_objectCache = 0; _fileRequestQueue->addNoLock(databaseRequest.get()); } } if (!_startThreadCalled) { OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_run_mutex); if (!_startThreadCalled) { OSG_INFO << "DatabasePager::startThread()" << std::endl; if (_databaseThreads.empty()) { setUpThreads( osg::DisplaySettings::instance()->getNumOfDatabaseThreadsHint(), osg::DisplaySettings::instance()->getNumOfHttpDatabaseThreadsHint()); } _startThreadCalled = true; _done = false; for (DatabaseThreadList::const_iterator dt_itr = _databaseThreads.begin(); dt_itr != _databaseThreads.end(); ++dt_itr) { (*dt_itr)->startThread(); } } } #ifdef WITH_REQUESTNODEFILE_TIMING totalTime += osg::Timer::instance()->delta_m(start_tick, osg::Timer::instance()->tick()); #endif }