• [转][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
  • 相关阅读:
    字符串
    决策树
    结构体实验
    指针实验
    打印杨辉三角
    P176 1.2.3
    第七次作业
    第六次作业
    第五次作业
    第四次修改
  • 原文地址:https://www.cnblogs.com/lyggqm/p/16558941.html
Copyright © 2020-2023  润新知