UE 5 NavMesh 烘培 逻辑流程
- 在向场景重拖入一个NavMeshBoundsVolume时(或者修改时). 会调用
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605032-1533814267.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605013-1434349619.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605089-1175334979.png)
- 在增加NavMeshBoundsVolume后的调用堆栈如下. 可以看到最后是把请求放到了PendingDirtyTiles里了. 走异步烘培
- 异步烘培的驱动堆栈如下, 可以看到是从World的Tick到ProcessTileTasksAsyncAndGetUpdatedTiles函数里. 这个函数里取出来PendingDirtyTiles里记录的异步请求. 执行
- 在ProcessTileTasksAsyncAndGetUpdatedTiles函数里创建了一个TileTask(调用CreateTileGenerator函数构造一个Generator初始化了Task)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604911-1992788295.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605072-1295325335.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605084-1271200588.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605019-1514127442.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605078-699271060.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605043-48027931.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604894-1765584748.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604911-1266726552.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604948-1728046466.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604890-669732835.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604949-1703188860.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605014-508268838.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604926-1680935333.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605072-1396695056.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604911-237840282.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605071-930908261.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604893-89368694.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604911-414461850.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605037-1518423265.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604895-420122037.png)
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175605025-1469756901.png)
- UE的NavMesh烘培代码入口在 bool FRecastTileGenerator::GenerateTile()
- 主要烘培代码:
- 烘培完成后数据存储:
![0](https://img2023.cnblogs.com/blog/971383/202308/971383-20230816175604906-1195543818.png)
- 逻辑流程参考Recast是走的Sample_TempObstacles流程, 烘培区域选择的算法是RC_REGION_WATERSHED
2024.4.3 补充
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
RebuildAll 逻辑流程.
- 在NavigationSystemV1 Build函数里调用NavData的RebuildAll, 实际会转发调用到FRecastNavMeshGenerator::RebuildAll()
- z在Generator的RebuildAll里先根据Bounds 标记所有的DirtyArea: MarkNavBoundsDirty()
- 然后通过EnsureBuildCompletion函数(图1, 图3)确保收集每次要处理的Tile任务和确保这些Tile任务执行EnsureCompletion, 完成Build (图3)
- 然后在Task的EnsureCompletion里, 调用的就是Task的DoWork, Task可以参考前面的分析.
过一遍后续的烘培流程 (以Tile为单位, 整个NavMesh是由n*m个tile组成. 多线程烘培. 同时最多进行MaxTileGeneratorTasks个Tile烘培任务):
从这里开始 bool FRecastTileGenerator::DoWork() → bool FRecastTileGenerator::GenerateTile()
具体:
- 先调用GenerateCompressedLayers 生成CompressedLayers, 这里有完整的烘培流程的前半部分
- 构造高度场 CreateHeightField
- 栅格化并标记NavModify. ComputeRasterizationMasks
- 栅格化网格, RasterizeTriangles → RasterizeGeometry / RasterizeGeometryRecast → rcRasterizeTriangles
- 过滤一些无效的span. ApplyVoxelFilter + GenerateRecastFilter → rcFilterLowHangingWalkableObstacles + rcFilterLedgeSpans + rcFilterWalkableLowHeightSpans
- 构造Compact高度场 BuildCompactHeightField
- Erode 一下寻路Span. RecastErodeWalkable → rcErodeWalkableAndLowAreas + rcErodeWalkableArea
- 重头戏, 烘培nav Layer: RecastBuildLayers → RC_REGION_WATERSHED → rcBuildDistanceField + rcBuildHeightfieldLayers → rcGatherRegionsNoFilter
- 构造dtBuildTileCacheLayer, 压缩Layer: RecastBuildTileCache
- 在得到CompressedLayers之后, 我们只是完成了NavMesh烘培的前半部分, 即完成BuildRegion阶段. 后续还需要做区域优化, 形成轮廓, 简化轮廓, 构建PolyMesh, 生成DetailPolyMesh
- 上面提到的步骤,紧接着GenerateCompressedLayers之后调用GenerateNavigationData实现.
- GenerateNavigationData()
- GenerateNavigationDataLayer
- 解压缩 dtDecompressTileCacheLayer
- Rasterize obstacles 处理动态阻挡. MarkDynamicAreas(*GenerationContext.Layer);
- 重新烘培区域, 类似上面的步骤g: dtBuildTileCacheDistanceField + dtBuildTileCacheRegions → filterSmallRegions
- 构建轮廓 dtBuildTileCacheContours
- 构建PolyMesh, dtBuildTileCachePolyMesh
- 构建PolyMeshDetail, dtBuildTileCachePolyMeshDetail
- 处理offmeshLinks
- 最后完成NavMeshData的构建: dtCreateNavMeshData(&Params, &NavData, &NavDataSize))
- 构建UE的NavMesh对象FNavMeshTileData: GenerationContext.NavigationData.Add(FNavMeshTileData(NavData, NavDataSize, LayerIdx, CompressedData.LayerBBox));
从上面可以看出来, Recast的Sample_TempObstacles和Sample_TileMesh关键的区别就在于. TempObstacles会先把烘培的前半部分数据Compress一下, 会有CompressedLayers
这部分数据是场景Mesh得到的原始数据,再加上动态阻挡的部分之后. 再重新烘培区域, 简化区域, 烘培细节网格. 得到最终的NavMesh.
这样做的原因不难理解: 场景不变的数据, 先烘培好压缩保存起来, 如果这里出现了动态阻挡, 则把场景不变的数据解压出来, 叠加动态阻挡, 再重新烘培一下区域. 形成更新的Navmesh.
所以这就是Sample_TempObstacles烘培的是支持动态阻挡的Dynamic NavMesh和Sample_TileMesh烘培的不支持动态阻挡的Static NavMesh 之间的区别. UE亦是如此.