如何渲染大规模点云

好几年前做的东西,今天在这里总结一下,以免多年之后忘了。

说来也简单,采用的就是八叉树管理,跟我以前做的数字城市大同小异,只不过那个用四叉树就行了。父节点管理八个子节点,父节点点云范围是八个孩子的总和。

为了实现八叉树管理,需要自定义一种文件格式,这是为了满足渲染效率考虑的,当然可以做成实时抽稀的,这样也可以不用更改文件格式,如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;
}




  • 10
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
在使用OSG进行内外存调度和渲染可视化大规模点数据时,在点去噪后快速更新LOD模型的本地IVE文件可以按照以下步骤进行: 1. 加载IVE文件:使用OSG的接口加载LOD模型的本地IVE文件到内存中。可以使用`osgDB::readNodeFile`函数来加载IVE文件,并将返回的节点存储在内存中。 2. 进行点去噪:使用适当的点去噪算法对大规模点数据进行去噪处理。这可以帮助减少噪声点,提高点数据的质量。 3. 更新LOD数据:对于LOD模型,其通常包含多个层次的细节模型。在点去噪后,需要将更新后的点数据更新到LOD模型中。 4. 替换细节模型:对于需要更新的LOD节点,可以使用OSG提供的方法替换其细节模型。可以通过创建新的`osg::Geometry`对象,并将更新后的点数据设置为几何体的顶点数据。然后使用`osg::LOD`的`setRange`和`addChild`等函数将新的几何体替换到LOD节点中。 5. 保存IVE文件:在更新完细节模型后,可以将更新后的模型节点重新保存为IVE文件,以便后续使用。可以使用`osgDB::writeNodeFile`函数将模型节点保存为IVE文件。 需要注意的是,在点去噪后,点数据的格式可能会发生变化,例如点的数量、顶点属性等。因此,在更新LOD模型时,需要根据实际情况进行相应的数据格式转换和更新操作。 另外,为了更好地进行内外存调度和渲染大规模点数据,还可以结合使用OSG的其他功能,如分块加载、级联LOD等,以提高渲染效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神气爱哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值