From ab430d75f77fd835ab0048e2f0d96bb0f19fbb2e Mon Sep 17 00:00:00 2001 From: "JSD\\13999" <1399945104@qq.com> Date: Sat, 13 Jun 2026 15:32:05 +0800 Subject: [PATCH] Document avoidance algorithm principles --- FishROV/Docs/rvo2-core-principles.md | 148 +++++++++++ ...ampling-local-avoidance-core-principles.md | 234 ++++++++++++++++++ FishROV/Docs/unity-navmesh-core-principles.md | 150 +++++++++++ 3 files changed, 532 insertions(+) create mode 100644 FishROV/Docs/rvo2-core-principles.md create mode 100644 FishROV/Docs/sampling-local-avoidance-core-principles.md create mode 100644 FishROV/Docs/unity-navmesh-core-principles.md diff --git a/FishROV/Docs/rvo2-core-principles.md b/FishROV/Docs/rvo2-core-principles.md new file mode 100644 index 0000000..432464f --- /dev/null +++ b/FishROV/Docs/rvo2-core-principles.md @@ -0,0 +1,148 @@ +# RVO2 / ORCA 核心原理 + +## 一句话结论 + +RVO2 的核心是 **在速度空间里选择一个尽量接近期望速度、同时在未来一段时间内不会和邻居相撞的速度**。它不是先规划一条全局路径,而是每帧根据周围动态体的位置、速度、半径,实时修正当前速度。 + +在 FishROV benchmark 中,`Rvo2Adapter` 直接调用 `RVO.Simulator`,把 Unity 的 `x/z` 平面映射到 RVO2 的 `x/y` 平面。 + +## 要解决的问题 + +普通 steering 常见做法是: + +```text +朝目标走 +遇到近邻就加一个远离力 +最后合成速度 +``` + +这种方法容易出现几个问题: + +- 只看当前距离,不看未来是否会碰撞。 +- 多个 agent 同时互相躲避时,可能左右摇摆。 +- 高密度时排斥力叠加会让速度很乱。 +- 很难证明“这个速度在未来一小段时间内安全”。 + +RVO2/ORCA 换了一个视角:不直接在位置空间里推开物体,而是在速度空间里判断哪些速度会导致未来碰撞。 + +## Velocity Obstacle + +假设有两个 agent:A 和 B。 + +- A 的位置是 `pA`,速度是 `vA`,半径是 `rA`。 +- B 的位置是 `pB`,速度是 `vB`,半径是 `rB`。 +- 如果未来某个时间内,两者中心距离小于 `rA + rB`,就会碰撞。 + +Velocity Obstacle 的意思是: + +```text +对 A 来说,有一片速度区域是危险的。 +如果 A 选择这片区域内的速度, +在假设 B 继续按当前速度运动的情况下, +A 会在未来撞到 B。 +``` + +这片危险区域通常可以理解成速度空间中的一个锥形区域。 + +## RVO:双方共同承担避让 + +普通 VO 假设“我来躲别人”。如果所有 agent 都这样算,容易过度避让。 + +RVO 的思路是: + +```text +A 和 B 都是主动体。 +发生潜在碰撞时,不应该让 A 单独承担全部避让。 +双方各承担一部分速度修正。 +``` + +这让多 agent 场景更自然,尤其适合鱼群、机器人群、行人群这类对等个体。 + +## ORCA:把避障变成线性约束 + +ORCA 是 RVO 的改进形式。它会为每个邻居构造一个半平面约束: + +```text +为了避免和这个邻居碰撞, +当前 agent 的新速度必须落在这条线的一侧。 +``` + +多个邻居会产生多个约束。最终问题变成: + +```text +在所有安全半平面约束内, +找一个距离期望速度最近的速度。 +``` + +这就是 RVO2 高效的关键:每帧不是做复杂全局搜索,而是在低维速度空间里做局部约束求解。 + +## 每帧流程 + +RVO2 的典型帧流程是: + +1. 更新每个 agent 的当前位置。 +2. 给每个 agent 设置期望速度。 +3. 使用空间结构查找邻居。 +4. 为每个邻居构造 ORCA 约束。 +5. 求出新的安全速度。 +6. 用新速度推进 agent。 + +在当前 benchmark 中,推进 Transform 由统一的 `BenchmarkAgent.Integrate` 负责,RVO2 adapter 只负责把期望速度改成避障后的速度。 + +## 关键参数 + +- `radius`:agent 半径,直接决定碰撞距离。 +- `neighborDist`:考虑多远范围内的邻居。 +- `maxNeighbors`:最多考虑多少邻居。 +- `timeHorizon`:预测未来多久内的碰撞。 +- `maxSpeed`:最大速度。 +- `timeStep`:仿真步长。 + +这些参数会明显影响表现: + +- `neighborDist` 太小:反应太晚,容易碰撞。 +- `neighborDist` 太大:邻居过多,性能下降,也可能过度保守。 +- `timeHorizon` 太短:贴脸才躲。 +- `timeHorizon` 太长:提前过度避让,可能绕不开或变慢。 +- `maxNeighbors` 太低:高密度时会漏掉重要邻居。 + +## 优点 + +- 对多动态 agent 很合适。 +- 速度空间建模清晰,可解释。 +- 不需要全局路径。 +- 高密度局部避障比简单排斥力稳定。 +- 理论上比纯启发式 steering 更有安全约束感。 + +## 局限 + +- RVO2-CS 是 2D 算法,当前项目的 3D 模式会投影到 XZ 平面。 +- 它不负责全局规划,遇到死胡同或复杂障碍仍可能局部最优。 +- 只考虑局部未来,不能理解长远路线收益。 +- 参数不合适时仍会碰撞、停滞或过度保守。 +- 如果 benchmark 初始位置已经重叠,它需要时间把重叠解开。 + +## 在 FishROV 中的观察重点 + +测试 RVO2 时重点看: + +- `XZ重叠对` 是否能稳定下降。 +- `3D重叠对` 是否比 XZ 少,判断 2.5D 高度层是否只是视觉重叠。 +- 高密度场景中 `模拟耗时` 是否随数量近似线性或亚二次增长。 +- 是否出现互相卡住、绕圈或过早减速。 + +## 对自研算法的启发 + +RVO2 最值得吸收的不是具体代码,而是这个思想: + +```text +不要只问“障碍在哪里”, +还要问“我现在可选的速度里,哪些会在未来导致碰撞”。 +``` + +后续自研算法可以借鉴: + +- 用预测碰撞时间代替单纯距离。 +- 在速度空间做候选速度打分。 +- 对不同邻居按风险程度加权。 +- 把“接近期望速度”和“避碰安全”放到同一个优化目标里。 diff --git a/FishROV/Docs/sampling-local-avoidance-core-principles.md b/FishROV/Docs/sampling-local-avoidance-core-principles.md new file mode 100644 index 0000000..e4e5330 --- /dev/null +++ b/FishROV/Docs/sampling-local-avoidance-core-principles.md @@ -0,0 +1,234 @@ +# 采样避障学习版核心原理 + +## 一句话结论 + +采样避障学习版的核心是 **不直接求解析最优解,而是在一批候选速度中打分选择当前最合适的速度**。它是项目内自写的学习基线,目标是可读、可改、可调参,而不是证明无碰撞。 + +实现位置: + +```text +Assets/Scripts/Benchmark/FrameworkAdapters/SamplingLocalAvoidanceAdapter.cs +``` + +## 为什么写这个版本 + +RVO2/ORCA 很成熟,但内部数学和约束求解不容易快速改。 + +NavMeshAgent 工程成熟,但它是完整导航系统,不是纯局部避障算法。 + +学习版采样避障的目的不同: + +- 给你一个完全可控的 baseline。 +- 让每个评分项都能看懂、能改权重。 +- 方便测试“如果我改变风险函数,会发生什么”。 +- 为后续写更好的自研算法提供实验平台。 + +## 基本思路 + +每帧对每个 agent 做三步: + +```text +1. 找附近邻居 +2. 生成候选速度 +3. 给候选速度打分,选择分数最低的速度 +``` + +它不是像 RVO2 那样构造半平面约束,而是把避障变成一个启发式评分问题。 + +## 空间哈希找邻居 + +如果每个 agent 都和所有其他 agent 比较,复杂度是: + +```text +O(n²) +``` + +5000 条鱼时会非常贵。 + +所以学习版先把 agent 放进空间哈希格子: + +```text +世界坐标 x/z + -> 根据 cellSize 计算格子坐标 + -> 每个格子保存一批 agent + -> 查询时只看自己附近几个格子 +``` + +这样大多数情况下只需要检查附近 agent,而不是全量扫描。 + +## 候选速度 + +候选速度主要来自: + +- 当前期望速度。 +- 零速度。 +- 以期望方向为基准旋转出的多个方向。 +- 不同速度倍率,例如全速和半速。 + +可以理解为: + +```text +我想往目标方向走, +但也试试稍微偏左、偏右、减速、停下, +看哪个风险最低。 +``` + +当前版本每帧采样固定数量方向,优先保持实现简单和可读。 + +## 评分函数 + +每个候选速度都会计算一个分数: + +```text +总分 = + 目标偏差惩罚 + + 速度平滑惩罚 + + 预测碰撞惩罚 + + 当前重叠惩罚 + + 边界惩罚 +``` + +分数越低,速度越适合当前帧。 + +## 目标偏差惩罚 + +agent 不能为了避障完全忘记目标。 + +如果候选速度和期望速度差得很远,就加惩罚: + +```text +goalPenalty = |candidate - preferred|² +``` + +这个项越大,agent 越倾向继续朝目标走。 + +## 速度平滑惩罚 + +如果每帧都选一个差异很大的速度,视觉上会抖。 + +所以学习版会惩罚候选速度和上一帧速度的差异: + +```text +smoothPenalty = |candidate - lastVelocity|² +``` + +这个项越大,运动越平滑,但反应也可能变慢。 + +## 当前重叠惩罚 + +如果两个 agent 当前已经重叠,必须强烈惩罚继续贴近的速度。 + +学习版会计算: + +```text +当前距离 - 两者半径和 +``` + +如果小于 0,说明已经重叠。重叠越深,惩罚越高。 + +这个项主要用来处理初始生成过密、或上一帧已经挤在一起的情况。 + +## 预测碰撞惩罚 + +只看当前距离不够。两个 agent 现在很远,但如果高速对冲,很快也会撞。 + +学习版会估算候选速度下的最近接近时刻: + +```text +relativePosition = otherPosition - selfPosition +relativeVelocity = candidateVelocity - otherVelocity +closestTime = clamp(-dot(relativePosition, relativeVelocity) / |relativeVelocity|², 0, timeHorizon) +``` + +然后计算在 `closestTime` 时两者距离是否小于安全半径。 + +如果预测未来会撞,就加惩罚;越快撞、越深撞,惩罚越高。 + +这一步是学习版最接近 RVO 思想的地方:它不只问“现在近不近”,而是问“这个速度会不会导致未来碰撞”。 + +## 边界惩罚 + +benchmark 有 arena 半径。候选速度如果把 agent 推向边界外,就加惩罚。 + +这能减少 agent 因避障一路逃出场地边缘,然后被 `BenchmarkAgent.Integrate` 夹回边界导致的异常运动。 + +## 每帧流程 + +学习版每帧流程是: + +1. adapter 收集每个 agent 的期望速度。 +2. 构建空间哈希。 +3. 对每个 agent 生成候选速度。 +4. 对候选速度逐个打分。 +5. 选择最低分速度。 +6. 写回 `BenchmarkAgent.ApplyVelocity`。 +7. benchmark 统一积分移动。 + +## 优点 + +- 完全自写,许可证清晰。 +- 很容易读懂和调参。 +- 可以快速增加新的评分项。 +- 可以观察每个启发式对结果的影响。 +- 适合作为自研算法实验起点。 + +## 局限 + +- 不保证无碰撞。 +- 候选速度数量有限,可能错过真正好速度。 +- 权重依赖场景,换场景可能需要重新调。 +- 高密度时仍可能局部最优或互相卡住。 +- 当前实现是 CPU 主线程版本,还没有 Job/Burst 优化。 +- 它是启发式算法,不是完整 ORCA。 + +## 和 RVO2 的区别 + +| 维度 | RVO2 / ORCA | 采样避障学习版 | +| --- | --- | --- | +| 核心方法 | 速度空间半平面约束求解 | 候选速度打分 | +| 理论性 | 更强 | 较弱 | +| 可解释性 | 数学清晰,但实现复杂 | 每个评分项直接可读 | +| 可调性 | 参数较少但影响大 | 权重和候选集都可改 | +| 无碰撞保证 | 更接近可证明安全 | 不保证 | +| 学习价值 | 学习速度障碍理论 | 学习工程化评分和调参 | + +## 和 NavMesh 的区别 + +| 维度 | Unity NavMeshAgent | 采样避障学习版 | +| --- | --- | --- | +| 主要目标 | 地图导航和路径跟随 | 局部动态避障 | +| 地图理解 | 有 NavMesh 可达性 | 没有地图理解 | +| 动态群体 | 可做但不是最专门 | 专门面向局部邻居 | +| 3D 体积 | 主要平面 | 当前也主要平面,可扩展 | +| 可改性 | 内部黑盒较多 | 完全可改 | + +## 后续改进方向 + +这个学习版后续可以逐步升级: + +- 增加候选速度自适应采样,而不是固定 16 方向。 +- 把邻居风险按距离、相对速度、角色类型加权。 +- 引入最大加速度和最大转向角约束。 +- 增加“右侧通行/左侧通行”偏置,减少对称死锁。 +- 增加队形或鱼群凝聚项。 +- 使用 Job System / Burst 优化空间哈希和评分。 +- 把评分函数输出到调试 HUD,观察每项惩罚占比。 +- 借鉴 RVO2,把明显危险的速度直接剔除,再对剩余速度打分。 + +## 对自研算法的启发 + +如果要写更好的 FishROV 避障算法,可以把学习版当成骨架: + +```text +目标速度 + -> 候选速度生成 + -> 风险预测 + -> 多目标评分 + -> 速度平滑和运动约束 +``` + +后续真正要优化的,是三个核心点: + +- 更准确的风险函数。 +- 更聪明的候选速度生成。 +- 更稳定的冲突解除策略。 diff --git a/FishROV/Docs/unity-navmesh-core-principles.md b/FishROV/Docs/unity-navmesh-core-principles.md new file mode 100644 index 0000000..ea8bda7 --- /dev/null +++ b/FishROV/Docs/unity-navmesh-core-principles.md @@ -0,0 +1,150 @@ +# Unity NavMeshAgent 核心原理 + +## 一句话结论 + +Unity NavMesh 的核心是 **先把可行走区域烘焙或构建成导航网格,再在网格上规划路径,并用 NavMeshAgent 做沿路径移动和局部避让**。它更像“路径导航系统”,局部避障只是其中一部分。 + +在 FishROV benchmark 中,`UnityNavMeshAdapter` 会运行时构建一张平面 NavMesh,然后给 agent 添加 `NavMeshAgent` 作为对照组。 + +## NavMesh 解决的问题 + +RVO2 主要解决动态体之间的局部避障,但它不理解复杂地图。 + +NavMesh 重点解决的是: + +```text +哪里能走? +从当前位置到目标点,应该沿哪些可行区域走? +遇到墙、洞、不可通行区域时,怎么绕过去? +``` + +所以 NavMesh 更偏全局可达性和路径规划。 + +## 导航网格 + +NavMesh 会把场景中的可行走区域转换成多边形网格。 + +可以理解为: + +```text +原始场景几何 + -> 提取可走区域 + -> 合并成多边形网格 + -> agent 在网格上找路径 +``` + +在普通地面游戏里,这非常适合角色在地形、房间、走廊之间移动。 + +## 路径规划 + +NavMeshAgent 的目标通常不是“直接向目标点走”,而是: + +1. 找到自己所在的 NavMesh 多边形。 +2. 找到目标所在的 NavMesh 多边形。 +3. 在多边形图上搜索路径。 +4. 得到一串路径拐点。 +5. 沿着路径拐点移动。 + +这部分通常接近 A* 图搜索思想,只是图节点不是格子,而是 NavMesh 多边形或多边形边界。 + +## 局部避障 + +NavMeshAgent 除了路径跟随,还会做一定局部避障: + +- 根据 agent 半径避免互相穿透。 +- 根据 `avoidancePriority` 决定谁更应该让路。 +- 根据 `obstacleAvoidanceType` 决定避障质量和开销。 +- 根据速度、加速度、角速度调整移动。 + +但要注意:NavMeshAgent 的局部避障不是完整的 ORCA/RVO2 替代品。它主要服务于“角色沿 NavMesh 路径移动”的工程场景。 + +## 每帧流程 + +典型 NavMeshAgent 流程是: + +1. agent 位于 NavMesh 上。 +2. 设置目标点或期望移动。 +3. NavMeshAgent 更新路径跟随和局部避障。 +4. 输出新位置或速度。 +5. Unity 自动或外部代码同步 Transform。 + +当前 benchmark 为了公平比较,没有让 NavMeshAgent 独占 Transform,而是: + +- 让 NavMeshAgent 计算避障速度。 +- 把速度交给 `BenchmarkAgent.ApplyVelocity`。 +- 再由统一的 `BenchmarkAgent.Integrate` 推进 Transform。 + +这能保持各方案共用同一套移动入口,但也会带来适配层同步问题。 + +## 为什么可能抖动 + +NavMeshAgent 在 benchmark 中抖动更明显,通常有几个原因: + +- NavMeshAgent 自己有内部位置 `nextPosition`,可视 Transform 又由 benchmark 积分推进,二者需要同步。 +- 高密度场景中 agent 互相挤压,NavMeshAgent 会频繁修正速度。 +- 如果目标方向和避障方向来回冲突,就会出现左右摆动。 +- NavMesh 是平面系统,2.5D 或 3D 表现会被压回平面理解。 +- 当前场景是压力测试,不是典型 NavMesh 地图导航场景。 + +当前代码已经对 NavMeshAgent 输出速度做了按 `acceleration` 的平滑,减少单帧速度尖峰,但它仍可能比 RVO2 更容易抖。 + +## 关键参数 + +- `radius`:agent 在 NavMesh 上的占位半径。 +- `height`:agent 高度。 +- `speed`:最大速度。 +- `acceleration`:速度变化上限。 +- `angularSpeed`:转向速度。 +- `avoidancePriority`:局部避让优先级。 +- `obstacleAvoidanceType`:避障质量。 +- `autoBraking`:到达目标前是否自动减速。 + +在高密度鱼群测试里,最敏感的通常是: + +- `radius` +- `acceleration` +- `avoidancePriority` +- `obstacleAvoidanceType` + +## 优点 + +- 工程成熟,Unity 内置。 +- 对静态地图、走廊、房间、地形导航很实用。 +- 能处理全局可达性,不只是局部避障。 +- 有现成 Editor/Runtime API。 +- 适合作为游戏角色导航的基础对照。 + +## 局限 + +- 主要是平面导航系统,不适合完整 3D 水下体积避障。 +- 局部避障不是它最强的部分。 +- 高密度动态 agent 容易抖动或互相挤压。 +- 对纯动态鱼群避障,可能不如专门的 RVO/ORCA 方法自然。 +- 如果只是空旷平面上的多 agent 避障,NavMesh 的全局路径能力发挥不出来。 + +## 在 FishROV 中的观察重点 + +测试 NavMesh 时重点看: + +- `XZ重叠对` 是否比 RVO2 高。 +- 抖动是否主要发生在高密度对向穿流、圆阵高压场景。 +- `模拟耗时` 是否稳定。 +- 速度平滑后是否减少视觉抖动,但仍保持避障响应。 +- 2.5D/3D 模式下是否只是平面避障导致视觉穿插。 + +## 对自研算法的启发 + +NavMesh 的价值不在于局部避障算法本身,而在于“全局可达性层”: + +```text +全局层:告诉 agent 应该往哪里走,避开不可通行区域。 +局部层:在当前速度附近躲开动态邻居。 +``` + +后续更好的 FishROV 避障系统可以考虑分层: + +- 全局目标/航线:NavMesh、路径图、流场、A*。 +- 局部动态避障:RVO2、采样速度、势场、学习策略。 +- 运动表现层:速度平滑、朝向约束、最大加速度。 + +这样能避免把所有问题都塞进一个局部避障算法里。