Unity NavMesh的Layer合并的问题

Unity的导航网格拥有32种不同的AreaType类型,我发现了这么一个情况,对于两个重叠在一起的不同AreaType类型的物体,最终的导航网格到底是按照哪一个?

如下图所示的例子,展示了第一种情况,较高的Cube为Walkable类型,较矮的为自定义的类型,设置完成后Bake导航网格,可以看到地面表面是什么物体,出现的就是什么类型的导航网格。

在这里插入图片描述

再来看看第二种情况,如果再把矮的Cube拉高一点,让二者高度更接近,如下图所示:
在这里插入图片描述
再去Build导航网格,会发现出现的都是较矮的Cube的导航网格类型,如下图所示:
在这里插入图片描述
至于为什么会这样,这涉及到了CellHeight的精度问题与底层体素合并的机制,由于Unity的导航网格是基于RecastDemo生成的,所以就拿RecastDemo的代码进行举例。

RecastDemo里是在每一列的空间里,对每一个三角形面进行体素化的,这里有两个Cube,每一个Cube有12个三角形面,一共是36个面,但是实际参与导航过程的只有一个Cube的上下面,如果取Cube的中间竖列来计算,则一共能生成4个体素(每个Cube上下面各对应一个体素),如下图所示,四个圈圈代表在中间列得到的四个体素:
在这里插入图片描述

拿第一种情况举例,得到的4个体素形成了一个链表,数据如下:
(0,2,0) -> (3,4,0) -> (80,81,69) -> (83,84,63)
解释一下,体素链表是空间上从往上生成的,第一个体素数据为(0,2,0),代表其下表面高度为0,上表面高度为2,AreaType为0,0代表着NULL_Area,因为该体素是正方体的底部,法向量判断该地方视作不存在体素

其他的AreaType,上面的63对应的Walkable,69对应的63 + 3*2 = 69,这里的3对应下面的序号差,每一个AreaType之间差为2,如下图所示:
在这里插入图片描述

所以在第一阶段体素化后,得到了链表体素数据(0,2,0) -> (3,4,0) -> (80,81,69) -> (83,84,63),由于四个体素之间没有交集

在体素化完成之后,RecastDemo里会有这么一个函数,代码如下所示:

	//如果某Span的上方Span与其间隔小于walkableHeight,则标记为span->area = RC_NULL_AREA
	if (m_filterWalkableLowHeightSpans)
		rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, *m_solid);

函数代码如下:

void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid)
{
    
    
	rcAssert(ctx);
	
	rcScopedTimer timer(ctx, RC_TIMER_FILTER_WALKABLE);
	
	const int w = solid.width;
	const int h = solid.height;
	const int MAX_HEIGHT = 0xffff;
	
	// Remove walkable flag from spans which do not have enough
	// space above them for the agent to stand there.
	for (int y = 0; y < h; ++y)
	{
    
    
		for (int x = 0; x < w; ++x)
		{
    
    
			for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next)
			{
    
    
				const int bot = (int)(s->smax);
				const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;
				if ((top - bot) <= walkableHeight)
					s->area = RC_NULL_AREA;
			}
		}
	}
}

可以看到,由于(80,81,69) -> (83,84,63)两个体素的距离为2,小于角色的高度,所以体素会被标记为NULL_AREA,最后的序列变为:
0,2,0) -> (3,4,0) -> (80,81,0) -> (83,84,63)

对于情况1,所以最后得到的颜色为正常Walkable的Layer


而第二种情况,两个正方体上表面距离更接近,其体素化的序列变为了:
(0,2,0) -> (1,2,0) -> (80,81,69) -> (81,82,63)

对于后面两个体素,明显二者是可以合并的,合并的代码如下:

static bool addSpan(rcHeightfield& hf, const int x, const int y,
					const unsigned short smin, const unsigned short smax,
					const unsigned char area, const int flagMergeThr)
{
    
    
	
	int idx = x + y*hf.width;
	
	rcSpan* s = allocSpan(hf);
	if (!s)
		return false;
	s->smin = smin;
	s->smax = smax;
	s->area = area;
	s->next = 0;
	
	// Empty cell, add the first span.
	if (!hf.spans[idx])
	{
    
    
		hf.spans[idx] = s;
		return true;
	}
	rcSpan* prev = 0;
	rcSpan* cur = hf.spans[idx];
	
	// Insert and merge spans.
	while (cur)
	{
    
    
		if (cur->smin > s->smax)
		{
    
    
			// Current span is further than the new span, break.
			break;
		}
		else if (cur->smax < s->smin)
		{
    
    
			// Current span is before the new span advance.
			prev = cur;
			cur = cur->next;
		}
		else
		{
    
    
			// Merge spans.
			if (cur->smin < s->smin)
				s->smin = cur->smin;
			if (cur->smax > s->smax)
				s->smax = cur->smax;
			
			// Merge flags.二者合并后会取更大的Area类型
			if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr)
				s->area = rcMax(s->area, cur->area);
			
			// Remove current span.
			rcSpan* next = cur->next;
			freeSpan(hf, cur);
			if (prev)
				prev->next = next;
			else
				hf.spans[idx] = next;
			cur = next;
		}
	}
	
	// Insert new span.
	if (prev)
	{
    
    
		s->next = prev->next;
		prev->next = s;
	}
	else
	{
    
    
		s->next = hf.spans[idx];
		hf.spans[idx] = s;
	}

	return true;
}

代码中可以看到,合并时会取更大的Area的值,所以合并后链表变为:
(0,2,0) -> (1,2,0) -> (80,82,69)
所以中间部分会显示红色,显示了自定义名为3的Layer.

结论

(1) 对于同一位置不同高度的表面,当高度差距较大时,谁在更上方,就选取谁作为最终的网格类型,不过当二者高度比较接近的时候,这个时候就要看导航网格的精度了。默认导航网格的CellHeight是CellSize的一半,当CellSize比较大的时候,两个高度接近的表面更容易发生Layer的合并。

也就是说,导航网格是否合并的高度差与CellHeight正相关,当VoxelSize设为2m时,高度差很大的情况下仍然合并了导航Layer,如下图所示,里面的小Cube边长为1m
在这里插入图片描述

(2)Unity一共提供了32个Layer(如果算上代码里的NULL_AREA则是33个Layer),如下图所示,其中UnWakable的Area的优先级是最大的,因为Unity对Unwalkable类型的区域做了导航网格剔除操作,其它的就按照正常顺序排列,其他31个类型里,Walkable类型的Area优先级最低,User31的Area优先级最高

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/alexhu2010q/article/details/108225077