Document avoidance algorithm principles

This commit is contained in:
JSD\13999
2026-06-13 15:32:05 +08:00
parent a99b600371
commit ab430d75f7
3 changed files with 532 additions and 0 deletions

View File

@@ -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
假设有两个 agentA 和 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
不要只问“障碍在哪里”,
还要问“我现在可选的速度里,哪些会在未来导致碰撞”。
```
后续自研算法可以借鉴:
- 用预测碰撞时间代替单纯距离。
- 在速度空间做候选速度打分。
- 对不同邻居按风险程度加权。
- 把“接近期望速度”和“避碰安全”放到同一个优化目标里。

View File

@@ -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
目标速度
-> 候选速度生成
-> 风险预测
-> 多目标评分
-> 速度平滑和运动约束
```
后续真正要优化的,是三个核心点:
- 更准确的风险函数。
- 更聪明的候选速度生成。
- 更稳定的冲突解除策略。

View File

@@ -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、采样速度、势场、学习策略。
- 运动表现层:速度平滑、朝向约束、最大加速度。
这样能避免把所有问题都塞进一个局部避障算法里。