本文參考鏈接: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);