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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章