RecastNavigation------体素化和高度场生成解析

本文参考链接:http://critterai.org/projects/nmgen_study/voxelization.html,如有错误,欢迎批评指正。

像素化是将平面上的2D图像转化为一个个小正方形,与此类似,RecastNavigation的体素化过程是把空间几何体转换为一个个小长方体的组合(与游戏:我的世界相似)
体素化过程如下:

一. 将整个场景体素化

对于任何一个在欧几里得座标系里的场景,都可以找到一个完全包含场景的三边与xyz轴平行的最小长方体,组成长方体的体素的长和宽相同,均为cellSize,对应的高为cellHeight(以下简称cs和ch),可将场景对应的长方体进行体素化,如下图所示:
在这里插入图片描述

二.对组成场景内每一个物体的三角形面,进行体素化。

三维空间的每一个物体,由面组成,对于一个用三角形面组成的模型。模型的体素化过程,可以转换成对众多三角形面的体素化过程,三角形的体素化如图所示:
在这里插入图片描述
下面具体分析将三角形面体素化的过程。

2.1 对于任意三角形面,找到其在xz平面上的投影,基于此投影三角形,找出其对应的xz平面上的column的集合(一个体素的竖直列叫做一个column)
在这里插入图片描述

2.2 遍历每一个蓝色格点对应的column,检测与三角形是否相交,若相交则找出与三角形相交的部分,如图所示,利用立方体与面的相交算法,找出其相交的多边形,取得多边形在高度上的最大值和最小值,根据这两个值可以决定出column上应该产生几个固体体素。
在这里插入图片描述

三.基于体素化后的场景,生成对应的高度场

介绍下什么是Span,Span由column对应的一竖列的体素组成的,下图所示的两个连续的体素,可以组成一个Span,如下图所示:
在这里插入图片描述

联系上述体素化过程,生成高度场的基于以下三个条件:

  • 高度场的每一个Span会在三角形面投影下的volumn中生成,即,Span的生成是在三角面的遍历上产生的
  • 生成的Span的最小值为该volumn与三角形相交多边形的最低点,最大值为多边形的最高点
  • 对三角形截面的坡度与角色设置的爬坡高度进行比较,标记span是否为traversable

由于场景的面可能会很多,势必会产生很多有交叉重叠的Span,为了减少重复, Span的生成和合并策略如下:

  • 若Span在竖直方向上没有任何与其相接触的其他Span,则创建该Span
  • 若Span在竖直方向上有与其相接触的其他Span,则合并两个Span,具体合并Span很简单,只要把两个Span空间结合起来就可以了

对于Span,角色只可能在其上表面行走,所以每一个Span均有一个判断参数,用来判断Span是否为上表面可通行的(Traversable),比如角色爬坡角度为45度,若Span的上表面对应的多边形的竖直方向的倾斜度小于45度,则该Span是可通行的。

关于其traversable参数的合并规则如下,假设原有Span为A,新的Span为B,:
1.若A与B的上表面高度相同,则Span上高度不变,只要A和B有一个为traversable,则该Span为traversable。
2.若A上表面高度大于B上表面高度,新的Span的traversable参数与A保持一致。
3.若A上表面高度小于B上表面高度,新的Span的traversable参数与B保持一致。
如图所示:
在这里插入图片描述

四. 额外的体素筛选操作

对于如下图所示的两个Span,虽然两个Span没有相接触,但是如果两个Span的距离太小,小于角色高度,角色实际上是不可能进入该区间的。。
对于这种情况:RecastNavigation中将下面蓝色的Span的traversable参数设置为false。
在这里插入图片描述

另一种可选择的边缘体素筛除操作:
An optional filter involves ledge detection. If stepping from the top of the span down to an axis-neighbor exceeds a configurable value, then the span is considered a ledge and not traversable.
在这里插入图片描述

整个过程的大致代码如下:

   //get all trunks node which interract with the chukyMesh
	float tbmin[2], tbmax[2];
	tbmin[0] = m_cfg.bmin[0];
	tbmin[1] = m_cfg.bmin[2];
	tbmax[0] = m_cfg.bmax[0];
	tbmax[1] = m_cfg.bmax[2];
	int cid[512];// TODO: Make grow when returning too many items.
	
	//获取所有与整个NavMesh场景对应的长方体相接触的所有体素长方体,取其id记录在cid中
	const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512);	//chunkyMesh是一个BVH树,里面的叶子节点对应着每一个空间上单独的obj
	if (!ncid)
		return 0;
	
	m_tileTriCount = 0;
	
    //traverse node 
	for (int i = 0; i < ncid; ++i)
	{
	    //获取节点
		const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
		//获取节点对应的三角形
		const int* ctris = &chunkyMesh->tris[node.i*3];
		//获取节点包含的三角形的个数
		const int nctris = node.n;
		
		m_tileTriCount += nctris;
		
		memset(m_triareas, 0, nctris*sizeof(unsigned char));//建立一个三角形数组,用来标记里面的walkable三角形
		
		///遍历节点下所有的三角形,根据三角形对应面的斜率,与角色的爬坡角度进行对比
		///若大于该角度,不作处理,否则改变该三角形的areaId, 标记为walkable area,存储在m_triareas里
		rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle,
								verts, nverts, ctris, nctris, m_triareas);
		
		//将所有的三角形体素化,并且生成Span加到heightFiled里
		if (!rcRasterizeTriangles(m_ctx, verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb))
			return 0;
	}
	
	if (!m_keepInterResults)
	{
		delete [] m_triareas;
		m_triareas = 0;
	}
	
	// Once all geometry is rasterized, we do initial pass of filtering to
	// remove unwanted overhangs caused by the conservative rasterization
	// as well as filter spans where the character cannot possibly stand.
	if (m_filterLowHangingObstacles)
		rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, *m_solid);
	if (m_filterLedgeSpans)
		rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid);
	if (m_filterWalkableLowHeightSpans)
		rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, *m_solid);
发布了70 篇原创文章 · 获赞 11 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章