RecastNavigation 路径寻路实战笔记:从体素栅格化到瓦片热更新
RecastNavigation 路径寻路实战笔记:从体素栅格化到瓦片热更新
【免费下载链接】recastnavigationIndustry-standard navigation-mesh toolset for games项目地址: https://gitcode.com/GitHub_Trending/re/recastnavigation
RecastNavigation 是游戏行业用得最广的导航网格(NavMesh)工具集,一套库同时覆盖导航网格构建、路径寻路和人群模拟三件事。这篇笔记不堆 API,而是沿着"一条三角形进来、一条路径出去"的数据流走一遍,重点记录踩过的坑和参数取舍。
一条三角形怎么变成可寻路的导航网格
先说结论:Recast 的构建顺序是固定的,栅格化 → 紧凑高度场 → 距离场 → 区域划分 → 轮廓提取 → 多边形生成,每一步都吃上一步的输出。顺序不能换,因为后一级的输入粒度依赖前一级已经收敛的分辨率。这套逻辑的入口在 Recast 核心构建。
核心调用链是这样的,每一步只传上一步的产物:
rcRasterizeTriangles(ctx, verts, nverts, tris, triareas, ntris, solid, walkableClimb); rcBuildCompactHeightfield(ctx, walkableHeight, walkableClimb, solid, chf); rcBuildDistanceField(ctx, chf, 0); rcBuildRegions(ctx, chf, borderSize, minRegionArea, mergeRegionArea); rcBuildContours(ctx, chf, maxError, maxEdgeLen, cset); rcBuildPolyMesh(ctx, cset, maxVertsPerPoly, pmesh);栅格化为什么必须走在最前面
栅格化把连续的三角形投影到一个固定分辨率的体素格子上。它必须在最前面,因为后面所有算法(高度场、区域、轮廓)都假设"世界已经被量化成整数坐标"。三角形本身带着浮点坐标和任意拓扑,直接拿去做区域划分没有任何意义——你得先有一张"像素图"。这一步也是耗时大头,因为三角形数量是输入规模的上界,三角形再多、再碎,体素格子的总数是被 cellSize 锁死的,所以它天然可被分辨率摊薄。
区域划分卡在 watershed 的"走廊病"
区域划分把可行走体素切成一块块连通域,每块后续会变成一个多边形。这里有个坑:默认的 watershed 算法在斜向走廊里容易切出"比必要更小"的碎片区域,走廊越斜碎得越厉害。源码注释里写得很直白,它会在水道处"overflow"再简化。两个参数负责兜底:minRegionArea过滤掉太小的碎片,mergeRegionArea把紧邻的小块合并回去。如果你发现走廊里多边形碎得像马赛克,先调大mergeRegionArea,别急着改分辨率。想彻底避开走廊碎片,可以切到rcBuildRegionsMonotone的单调划分,代价是多边形会更"横平竖直"。
从轮廓到多边形:谁决定了寻路走廊的粒度
轮廓提取先沿着区域边界描出一条"原始轮廓",再按maxError做简化,得到带拐角的折线。rcBuildPolyMesh再把这些折线拼成多边形,maxVertsPerPoly控制单个多边形最多几个顶点——这个值直接决定寻路走廊的粒度。值越小,单个多边形覆盖范围越大,findPath返回的走廊多边形数量越少、寻路越快,但高度细节丢失越多。简单场景给 6,室内外混合、需要贴地更准的场景才往上加。
单网格扛不住之后,瓦片网格怎么接管
单网格把整个场景一次性栅格化,小场景很爽。但场景一大,内存和重建时间会一起爆,而且任何一处改动都得重建整张网格。瓦片网格把场景切成固定大小的块,每块独立构建、独立更新,这就是 Detour 瓦片缓存 要解决的流式加载与热更新问题。
瓦片边界重叠量 borderSize 到底给多少
先问一句:那瓦片交界处的路径为什么会断?因为每个瓦片只栅格化自己边界内的三角形,靠边界的几何在相邻瓦片里"看不到",两边各建各的多边形,交界就裂开了。解法是让每个瓦片向外多算一圈 padding。
m_cfg.tileSize = (int)m_tileSize; m_cfg.borderSize = m_cfg.walkableRadius + 3; // 关键:半径+余量 m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2; m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2; m_cfg.bmin[0] -= m_cfg.borderSize * m_cfg.cs; // 四边各外扩rcBuildContours在输出时会把这个 padding 从边界里扣掉,所以最终多边形仍然严格落在瓦片真实范围内,但构建时的体素是带重叠的,交界处的多边形才能对得上、连得上。
为什么 borderSize 要跟着 walkableRadius 走
walkableRadius是体素化时按角色半径做的"腐蚀"距离——离障碍太近(小于半径)的格子直接判为不可走。如果 padding 小于这个腐蚀距离,交界处的可行走区域会在两个瓦片里被腐蚀掉一部分,留下走不过去的窄缝。所以borderSize = walkableRadius + 3里的+3是安全余量,walkableRadius是硬底线。角色半径越大,padding 必须跟着变大,这是硬约束,不是经验值。
单网格和瓦片网格的取舍,我一般这么记:
| 维度 | 单网格 | 瓦片网格 |
|---|---|---|
| 实现复杂度 | 低,一次构建 | 高,要管坐标与连接 |
| 局部更新 | 要重建整张 | 只重建该瓦片 |
| 内存峰值 | 随场景线性涨 | 可流式加载卸载 |
| 适用规模 | 中小型静态场景 | 开放世界、动态障碍 |
路径寻路的两种输出:多边形走廊与直线
Detour 查询接口 里有个容易误解的点:findPath并不直接返回一串坐标点,它返回的是一条"多边形走廊"——一串有序的多边形引用(polyRef)。真正的坐标是第二步findStraightPath沿着这条走廊做的视线投影,输出带DT_STRAIGHTPATH_*标志的拐点数组。
query->findPath(startRef, endRef, startPos, endPos, filter, path, &pathCount, maxPath); // 第一步:多边形走廊 query->findStraightPath(startPos, endPos, path, pathSize, straight, flags, refs, &count, maxSP, DT_STRAIGHTPATH_AREA_CROSSINGS); // 第二步:投影成拐点分两步的意义在于:走廊是拓扑结果,和具体坐标无关,可以缓存、可以做局部修正;而投影出的拐点才是渲染和驱动角色用的。把两者拆开,你才能在"目标变了但还没重算走廊"的中间态先做局部避障。
人群模拟为什么把寻路切成切片
DetourCrowd 人群模拟 里上百个 agent 每帧都要重新寻路,如果每个都同步跑完整findPath,主线程直接卡死。所以它用了 sliced(切片)寻路:initSlicedFindPath初始化,updateSlicedFindPath每帧跑有限迭代,finalizeSlicedFindPath取结果,再配合dtPathQueue把异步寻路排队摊到多帧。dtCrowd::update里还能看到它把拓扑优化、邻域网格、避障、分离、位移拆成一串小步骤,每步都能单独开关——这套拆解就是为了把"一帧内的寻路预算"做平。
构建耗时卡在哪个阶段,参数怎么救
rcContext给每个阶段都挂了独立计时器(RC_TIMER_RASTERIZE_TRIANGLES、RC_TIMER_BUILD_CONTOURS等),DebugUtils 调试工具 能把每段耗时打出来,所以"耗时卡在哪"不用猜,先看报告。经验上栅格化和轮廓简化是两个大头,而它们对同一组参数敏感。
cellSize 和 agentHeight 的换算关系
先记死这两条换算,错了一切都错:walkableRadius = ceil(agentRadius / cellSize),walkableHeight = ceil(agentHeight / cellHeight)。cellSize越大,体素总数越少,构建和内存都降,但代价是可行走区域被"量化"得更粗,细窄通道可能整条消失。agentHeight必须小于实际可通行净高,否则高度场会把本该能走的格子判成不可走。一个常见误区是把cellSize一味调大到 1.0 以上来换速度,结果窄门、台阶全被吞掉。我的习惯是先用 0.25~0.3 保证几何保真,确认导航网格质量后再逐步放大压耗时,而不是反过来。
所以选型其实就三个判据:场景能不能一次装进内存、有没有动态障碍、寻路预算卡不卡主线程。三者都"否",单网格 + 直连findPath最省心;只要有动态障碍或超大场景,就上瓦片网格 + 切片寻路,再按角色半径把borderSize卡到walkableRadius + 3。参数调优永远先看计时报告定位瓶颈,再去动cellSize和mergeRegionArea,别在没数据的地方瞎试。
【免费下载链接】recastnavigationIndustry-standard navigation-mesh toolset for games项目地址: https://gitcode.com/GitHub_Trending/re/recastnavigation
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考