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
}