[转][osg]深入理解osg::PagedLOD

转自封狼居胥: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();
}
View Code

其中只有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));
}
View Code

可以看到 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));
}
View Code

可以看到NodeVisitor里面出了对Node基本遍历做了处理,其他的节点均未实现,需要的什么功能,我们可以继承来解决,调用的时候有两种方式:

Node.accept(NodeVisitor);//最好的办法

NodeVisitor.apply(Node);//也行(不推荐)
View Code

所以结合以上的说了一大堆,我们明白一个节点的特性应该去他的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;
        }
View Code

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;
         }
   }
 }
View Code

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());
        }
    }

}
View Code

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
}
View Code

 

posted @ 2022-08-07 13:33  南水之源  阅读(650)  评论(0编辑  收藏  举报