好几年前做的东西,今天在这里总结一下,以免多年之后忘了。
说来也简单,采用的就是八叉树管理,跟我以前做的数字城市大同小异,只不过那个用四叉树就行了。父节点管理八个子节点,父节点点云范围是八个孩子的总和。
为了实现八叉树管理,需要自定义一种文件格式,这是为了满足渲染效率考虑的,当然可以做成实时抽稀的,这样也可以不用更改文件格式,如las,pcd等,这里为了极致的效率考虑,设计了一种NPC格式(想起了魔兽世界),分为头文件、八叉树、原始点云三个部分: 1)其中头文件共占用507字节,主要描述了点云的信息,如节点数量,包围盒信息等,如下表所示:
2)八叉树部分记录了八叉树的结构,是一个可序列化的数据结构,父节点是子节点的包络。值得注意的是,此处八叉树并不是完全八叉树。某个八叉树节点是否拥有子节点取决于它是否满足分割条件。 下图描述了如何由原始点云构建八叉树的原理,由于无法制作三维图例,因此用四叉树作为图例,八叉树是类似的。首先,将原始点云按体积等分为8份,对每一份点云判断是否小于切分阈值nmax,这里等于5,如果小于5则停止切分,否则继续切分,直到所有格网内的点云都小于5为止,这样就构建了一颗八叉树。
八叉树的数据结构如下表所示:
值得注意的是,节点描述中indices字段记录的是点云索引而不是点云坐标。除叶子节点记录的是全部原始点云的索引外,非叶子节点记录的是经过抽稀的点云索引。八叉树中每一个节点都有可视范围,可视范围跟包围盒半径是线性的关系,节点半径越大,则可视范围也越大,因此父节点的可视范围肯定比子节点可视范围大。这里有个技巧,为了使抽稀变得均匀(这样不会丧失细节),设计了一种网格化随机抽样算法,保证了点云特征的完整性,即随机抽取的点云能充满整个包络,点云是均匀抽稀的。 算法的步骤是:首先将非叶子节点所对应的正方体网格分为k*k*k个小立方体,这里k=4,因此立方体被等分为16个小立方体:
然后对每一个小立方体,如果其包含点云则随机抽取一个,如果小立方体中没有点云则不抽取,这样抽稀后点云数量小于等于16,因为抽稀是按空间切割后再抽稀的,因此抽稀后的点云是离散的,避免了由于抽稀结果过于集中将特征掩盖的问题,能很好地体现原始点云的特征。 如果节点是叶子节点,则不抽稀,直接取出全部点云即可。 3)第三部分就是占比最大的部分:原始点云。此部分实际上就是把原始文件搬过来就行了(这里汗一个)。包含最全面的点云信息,如点云坐标、强度信息、颜色信息等。每个点云都是有序号的,序号即是它在原始点云列表里所处的位置,从0开始计数。八叉树正是通过序号索引到原始点云的。
上面讲了NPC文件的格式,下面简要阐述一下基于八叉树的点云渲染过程:
在点云文件被加载进来以后,会在内存中重建如第一节所述的八叉树结构,节点是否被渲染取决于其中心与视点的距离。
随着视点距离的缩小,系统在每一帧都会判断当前节点集中的每一个非叶子节点与视点的距离是否小于其子节点的可视范围,如果小于,则八叉树节点开始分裂,即父节点从当前节点集中去掉,并从当前渲染场景中去掉,子节点加入当前节点集,并加入当前渲染场景,直到节点不能分裂为止。 这样随着视点的推进,点云细节会慢慢丰富,由于其视野是有限的,所以当前渲染场景中的点云始终保持一定数目,不会因为点云数量过多造成卡顿等现象。
接下来,贴一部分代码吧,这部分代码是仿照osg_earth插件管理的,有接触osg的同学看起来会容易懂一些,又想学习的同学可以私下联系我.
#include "StdAfx.h"
#include "XPCLGraph.h"
#include "XPCLDrawable.h"
#include <osg/Math>
#include <string>
#include <map>
#include <sstream>
#include <osgDB/ReaderWriter>
#include <stdio.h>
#include <osgDB/FileNameUtils>
#include <osg/PagedLOD>
#include <osgDB/Registry>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osg/Geometry>
#include <osg/PrimitiveSet>
#include <osg/LineWidth>
#include <iostream>
#include <osg/ShapeDrawable>
#include <osg/PolygonMode>
namespace
{
int _uid = 0;
OpenThreads::Mutex _fmgMutex;
typedef std::map<int, osg::observer_ptr<CXPCLGraph> > XYFRegistry;
XYFRegistry _XYFRegistry;
static std::string s_makeURI( int uid, unsigned lod, unsigned x, unsigned y, unsigned z )
{
std::stringstream buf;
buf << uid << "." << lod << "_" << x << "_" << y << "_" << z << ".pseudo_xyf";
std::string str;
str = buf.str();
return str;
}
}
struct XYFPseudoLoader : public osgDB::ReaderWriter
{
XYFPseudoLoader()
{
supportsExtension( "pseudo_xyf", "XYF pipe pseudo-loader" );
}
const char* className()
{ // override
return "XYF Pseudo-Loader";
}
ReadResult readNode(const std::string& uri, const Options* options) const
{
if ( !acceptsExtension( osgDB::getLowerCaseFileExtension(uri) ) )
return ReadResult::FILE_NOT_HANDLED;
int uid;
unsigned lod, x, y, z;
sscanf( uri.c_str(), "%u.%d_%d_%d_%d.%*s", &uid, &lod, &x, &y, &z );
osg::ref_ptr<CXPCLGraph> graph = getGraph(uid);
if ( graph.valid() )
{
return ReadResult( graph->load( lod, x, y, z, uid ) );
}
return ReadResult::ERROR_IN_READING_FILE;
}
static int registerGraph( CXPCLGraph* graph )
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _fmgMutex );
int key = ++_uid;
_XYFRegistry[key] = graph;
return key;
}
static void unregisterGraph( int uid )
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _fmgMutex );
_XYFRegistry.erase( uid );
}
static CXPCLGraph* getGraph( int uid )
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _fmgMutex );
XYFRegistry::const_iterator i = _XYFRegistry.find( uid );
return i != _XYFRegistry.end() ? i->second.get() : 0L;
}
};
REGISTER_OSGPLUGIN(pseudo_xyf, XYFPseudoLoader)
class CXPCLRenderSet : public osg::Geode
{
public:
CXPCLRenderSet(const boost::shared_ptr<ville3d::NavinfoPCD>& navinfoPCD, const pcl::octree::OctreeKey& subtile, unsigned depth, unsigned uid)
: _navinfoPCD(navinfoPCD)
{
const std::vector<ville3d::NavinfoPCD::NodeInfo>& nodeInfos = _navinfoPCD->getNodeInfo();
for (std::vector<ville3d::NavinfoPCD::NodeInfo>::const_iterator citr = nodeInfos.begin(); citr != nodeInfos.end(); citr++)
{
if (citr->key_x == subtile.x && citr->key_y == subtile.y && citr->key_z == subtile.z && citr->depth == depth)
{
ville3d::NavinfoPCD::NodeInfo info = *citr;
osg::BoundingSphered subtile_bs;
subtile_bs.expandBy(osg::Vec3d(citr->xmin, citr->ymin, citr->zmin));
subtile_bs.expandBy(osg::Vec3d(citr->xmax, citr->ymax, citr->zmax));
osg::BoundingBox bounding(info.xmin, info.ymin, info.zmin, info.xmax, info.ymax, info.zmax);
CXPCLDrawable* drawable = new CXPCLDrawable;
drawable->setName(_navinfoPCD->getFileName());
drawable->setPCLCloud(_navinfoPCD->getCloud());
drawable->setPCLIndices(info.indices);
addDrawable(drawable);
//addDrawable(createBoxForDebug(osg::Vec3(bounding.xMax(), bounding.yMax(), bounding.zMax()), osg::Vec3(bounding.xMin(), bounding.yMin(), bounding.zMin())));
}
}
}
const boost::shared_ptr<ville3d::NavinfoPCD>& getNavinfoPCD() const
{
return _navinfoPCD;
}
protected:
osg::Geometry* createBoxForDebug(const osg::Vec3& max, const osg::Vec3& min)
{
osg::Vec3 dir = max - min;
osg::ref_ptr<osg::Vec3Array> va = new osg::Vec3Array(10);
(*va)[0] = min + osg::Vec3(0.0f, 0.0f, 0.0f);
(*va)[1] = min + osg::Vec3(0.0f, 0.0f, dir[2]);
(*va)[2] = min + osg::Vec3(dir[0], 0.0f, 0.0f);
(*va)[3] = min + osg::Vec3(dir[0], 0.0f, dir[2]);
(*va)[4] = min + osg::Vec3(dir[0], dir[1], 0.0f);
(*va)[5] = min + osg::Vec3(dir[0], dir[1], dir[2]);
(*va)[6] = min + osg::Vec3(0.0f, dir[1], 0.0f);
(*va)[7] = min + osg::Vec3(0.0f, dir[1], dir[2]);
(*va)[8] = min + osg::Vec3(0.0f, 0.0f, 0.0f);
(*va)[9] = min + osg::Vec3(0.0f, 0.0f, dir[2]);
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
geom->setVertexArray(va.get());
geom->addPrimitiveSet(new osg::DrawArrays(GL_QUAD_STRIP, 0, 10));
osg::Vec4dArray* color = new osg::Vec4dArray;
color->push_back(osg::Vec4d(1.0, 1.0, 0.0, 1.0));
geom->setColorArray(color);
geom->setColorBinding(osg::Geometry::BIND_OVERALL);
geom->getOrCreateStateSet()->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE));
geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
return geom.release();
}
private:
boost::shared_ptr<ville3d::NavinfoPCD> _navinfoPCD;
};
CXPCLGraph::CXPCLGraph(const std::string& filename)
: _filename(filename), _navinfoPCD(new ville3d::NavinfoPCD)
{
_navinfoPCD->readNPC(filename);
_rootKey = osg::Vec4(0, 0, 0, 0);
_uid = XYFPseudoLoader::registerGraph(this);
osg::Node * node = setupPaging();
addChild( node );
}
CXPCLGraph::CXPCLGraph(const boost::shared_ptr<ville3d::NavinfoPCD>& navinfoPCD)
: _navinfoPCD(navinfoPCD)
{
_rootKey = osg::Vec4(0, 0, 0, 0);
_uid = XYFPseudoLoader::registerGraph(this);
osg::Node * node = setupPaging();
addChild(node);
}
CXPCLGraph::~CXPCLGraph(void)
{
}
osg::Node* CXPCLGraph::setupPaging()
{
std::string uri = s_makeURI(_uid, _rootKey.x(), _rootKey.y(), _rootKey.z(), _rootKey.w());
double xmax, ymax, zmax, xmin, ymin, zmin, xoffset, yoffst, zoffset;
_navinfoPCD->getMax(xmax, ymax, zmax);
_navinfoPCD->getMin(xmin, ymin, zmin);
_navinfoPCD->getOffset(xoffset, yoffst, zoffset);
osg::BoundingSphere bs;
bs.expandBy(osg::Vec3(xmax - xoffset, ymax - yoffst, zmax - zoffset));
bs.expandBy(osg::Vec3(xmin - xoffset, ymin - yoffst, zmin - zoffset));
float minRange = bs.radius() / 20;
osg::Group* pagedNode = createPagedNode(
bs,
uri,
0, FLT_MAX,
0, 1);
pagedNode->setInitialBound(bs);
return pagedNode;
}
osg::Group* CXPCLGraph::createPagedNode( const osg::BoundingSphere& bs, const std::string& uri, float minRange, float maxRange, float priOffset, float priScale )
{
osg::PagedLOD* p = new osg::PagedLOD();
p->setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
p->setCenter( bs.center() );
p->setRadius((float)bs.radius());
p->setRange( 0, minRange, maxRange );
p->setFileName( 0, uri );
p->setPriorityOffset( 0, priOffset );
p->setPriorityScale( 0, priScale );
return p;
}
osg::Group* CXPCLGraph::buildSubTilePagedLODs(const boost::shared_ptr<ville3d::NavinfoPCD>& navinfoPCD, unsigned parentLOD, unsigned parentTileX, unsigned parentTileY, unsigned parentTileZ, unsigned uid)
{
osg::ref_ptr<osg::Group> parent = new osg::Group;
pcl::octree::OctreeKey root(parentTileX, parentTileY, parentTileZ);
for(int i = 0; i < 8; i++)
{
pcl::octree::OctreeKey subtile = root;
subtile.pushBranch(i);
unsigned subtileLOD = parentLOD + 1;
const std::vector<ville3d::NavinfoPCD::NodeInfo>& nodeInfos = navinfoPCD->getNodeInfo();
for(std::vector<ville3d::NavinfoPCD::NodeInfo>::const_iterator citr = nodeInfos.begin(); citr != nodeInfos.end(); citr++)
{
if(citr->key_x == subtile.x && citr->key_y == subtile.y && citr->key_z == subtile.z && citr->depth == subtileLOD)
{
osg::BoundingSphere subtile_bs;
subtile_bs.expandBy(osg::Vec3(citr->xmin, citr->ymin, citr->zmin));
subtile_bs.expandBy(osg::Vec3(citr->xmax, citr->ymax, citr->zmax));
std::string uri = s_makeURI( uid, subtileLOD, subtile.x, subtile.y, subtile.z);
osg::Group* pagedNode = createPagedNode(
subtile_bs,
uri,
0, FLT_MAX,
0,1);
pagedNode->setInitialBound(subtile_bs);
parent->addChild( pagedNode );
}
}
}
return parent.release();
}
osg::Node* CXPCLGraph::load( unsigned lod, unsigned tileX, unsigned tileY, unsigned tileZ, unsigned uid )
{
osg::Node* result = 0L;
const std::vector<ville3d::NavinfoPCD::NodeInfo>& nodeInfos = _navinfoPCD->getNodeInfo();
for(std::vector<ville3d::NavinfoPCD::NodeInfo>::const_iterator citr = nodeInfos.begin(); citr != nodeInfos.end(); citr++)
{
if(citr->key_x == tileX && citr->key_y == tileY && citr->key_z == tileZ && citr->depth == lod)
{
ville3d::NavinfoPCD::NodeInfo info = *citr;
osg::BoundingSphered subtile_bs;
subtile_bs.expandBy(osg::Vec3d(citr->xmin, citr->ymin, citr->zmin));
subtile_bs.expandBy(osg::Vec3d(citr->xmax, citr->ymax, citr->zmax));
osg::BoundingBox bounding(info.xmin, info.ymin, info.zmin, info.xmax, info.ymax, info.zmax);
osg::ref_ptr<CXPCLRenderSet> geode = new CXPCLRenderSet(_navinfoPCD, pcl::octree::OctreeKey(tileX, tileY, tileZ), lod, uid);
if(info.isLeafNode)
{
result = geode.release();
}
else
{
osg::ref_ptr<osg::LOD> p = new osg::LOD;
p->setCenter(osg::Vec3d(info.xcenter, info.ycenter, info.zcenter));
p->setRadius(info.length);
p->addChild(geode, info.length * 15, FLT_MAX);
osg::Group* group = buildSubTilePagedLODs(_navinfoPCD, lod, tileX, tileY, tileZ, uid);
p->addChild(group, 0, info.length * 15);
result = p.release();
}
break;
}
}
return result;
}
boost::shared_ptr<ville3d::NavinfoPCD> CXPCLGraph::getNavinfoPCD(const std::string filename)
{
class FindPCLRenderSetVistor : public osg::NodeVisitor
{
public:
FindPCLRenderSetVistor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN){}
void apply(osg::Geode& geode)
{
CXPCLRenderSet* renderSet = dynamic_cast<CXPCLRenderSet *>(&geode);
if (renderSet)
{
_pclRenderSetList.push_back(renderSet);
}
}
const std::vector<CXPCLRenderSet *>& getPCLRenderSetList(){ return _pclRenderSetList; }
private:
std::vector<CXPCLRenderSet *> _pclRenderSetList;
};
boost::shared_ptr<ville3d::NavinfoPCD> navinfoPCD;
FindPCLRenderSetVistor fv;
this->accept(fv);
const std::vector<CXPCLRenderSet *>& renderSetList = fv.getPCLRenderSetList();
if (!renderSetList.empty())
{
navinfoPCD = renderSetList[0]->getNavinfoPCD();
}
else
{
navinfoPCD = boost::make_shared<ville3d::NavinfoPCD>();
navinfoPCD->readNPC(filename);
}
return navinfoPCD;
}