如果你做过机器人强化学习训练,大概率踩过一个坑:仿真环境里碰撞体太多,训练死活不收敛。Microduck 的 RL 仓库里藏着一个反直觉的设计——五个不同的 MJCF 碰撞模型,走路时把躯干和脑袋的碰撞检测全砍了,全碰撞模型造好了却没有任何任务在用。
这不是偷懒,是精心算计过的。
五个碰撞体,一个鸭子
Microduck 的 RL 训练仓库(pollen-robotics/microduck_rl)里,src/mjlab_microduck/robot/microduck/ 目录下放了五套 MJCF 模型,每套对应不同的碰撞策略:
- robot_walk.xml — 走路专用,剥离了躯干和头部的碰撞 geoms
- robot_groundcontact.xml — 精选碰撞集,只保留会接触地面的部位(站立、坐下、捡东西、踢球、翻滚用)
- robot_groundcontact_rollers.xml — 滚轮任务专用,加了被动轮子的碰撞
- robot_allcollisions.xml — 真正的全碰撞模型,每个零件都有 collision geom
- robot_*_backlash.xml — 背隙变体,在以上模型基础上加 passive 铰链
这套设计的核心理念很直白:碰撞检测不是免费的,不用的碰撞就是噪音。
走路时为什么把头砍掉
robot_walk.xml 的注释只有一句话:”stripped trunk/head contacts — falling is cheap”。翻译过来就是:走路任务里,躯干和头部碰不碰地面无所谓,因为摔倒是廉价信号——奖励函数已经会惩罚摔倒行为了,不需要碰撞检测再来一层。
这不是直觉,是训练效率的硬账。
MuJoCo 的碰撞检测每一步都要做 broadphase + narrowphase 配对。一只 25cm 高、15 个舵机的鸭子,全身有几十个 link,如果每个 link 都挂 collision geom,一个 50Hz 的仿真步里光碰撞检测就要吃掉大量计算。Microduck 的训练跑 4096 个并行环境(在 mjlab/MuJoCo Warp 上),每个环境每秒 50 步——碰撞体多一个,4096 个环境一起算,开销就是乘 4096。
更关键的是梯度信号。走路策略的奖励主要看速度跟踪和姿态保持。如果头部碰到地面产生一堆碰撞力反馈,这些力信号混进观测空间,策略网络会被干扰——它分不清”我在走路”和”我的头蹭到了什么东西”。砍掉头部碰撞,等于直接从观测空间里删掉了一整类噪音。
全碰撞模型为什么没人用
robot_allcollisions.xml 的描述写得很坦诚:”True full-collision model — every part has a collision geom. No task uses it yet”。
全碰撞模型造好了,放在仓库里,没有任何任务加载它。原因不是不能用,而是还不值得用。
Microduck 当前阶段的训练目标是基础运动技能:走路、坐下、站起来、踢球、捡东西。这些任务的核心交互面在脚底和地面之间,偶尔需要身体接触地面(摔倒、趴下)。手臂、头部、脖子的碰撞在当前任务里几乎不产生有用的信号。
但全碰撞模型留着是有原因的。一旦进入下一阶段——比如双臂抓取、推搡、碰撞避让——这些任务就需要手臂和身体的真实碰撞反馈。到时候 robot_allcollisions.xml 就是现成的基础设施。Pollen Robotics 的选择是:先造好,先不用,等任务需要了再切。
地面接触模型的精选逻辑
robot_groundcontact.xml 是用得最多的碰撞模型,服务于 VelStand、StandUp、SitStand、GroundPick、BallKick、Roulade 六个任务。它的碰撞集是”curated”的——人工挑选过的,不是简单开关。
哪些部位留碰撞?逻辑很清晰:任务过程中可能接触地面的部位才有 collision geom。具体来说:
- 脚底板:必须有,承重面
- 脚背:趴下时可能接触地面
- 膝盖:跪下/蹲下时接触
- 嘴尖(beak):GroundPick 任务需要嘴碰地面捡东西
- 腹部/胸部:StandUp 任务从面朝下趴着站起来,躯干前面要接触地面
- 背部:Roulade(前滚翻)需要背部接触地面滚动
哪些部位不留?脖子侧面、头顶、翅膀(如果有)。这些部位在当前任务里不会先接触地面,留碰撞只会增加计算开销和噪音。
这个精选过程是手动的。每加一个任务,工程师就坐下来想:这个任务里鸭子会以什么姿势接触地面?哪些部位是承力点?然后只给这些部位加 collision geom。不是自动推理,是工程判断。
背隙模型的碰撞处理
背隙变体(robot_*_backlash.xml)在碰撞模型上和基础模型一致,但多了一组 passive 铰链——每个舵机关节后面串了一个 unactuated 的 passive_
碰撞检测对背隙模型的影响更微妙。背隙铰链本身不需要 collision geom(它是抽象的间隙模型,不是物理碰撞体),但背隙会改变关节的实际位置——编码器读到的位置经过了背隙滤波。这意味着碰撞响应也会通过背隙传递:脚踩地,力传到舵机,但舵机”感觉”到的位置和实际输出端的位置有 2 度的差。
这个差值在 sim2real 时是致命的。真机的编码器在输出端,读到的就是带背隙的位置。如果仿真里碰撞检测用的是无背隙的关节位置,那碰撞响应就和真机不一致。所以 Microduck 的背隙模型里,碰撞检测读的关节位置也经过了背隙滤波(qpos[servo] + qpos[backlash]),保证观测和碰撞响应的一致性。
碰撞模型的工程取舍
把五个模型的设计逻辑串起来,能看到一条清晰的工程原则:
仿真精度不是越高越好,而是越匹配任务越好。
走路不需要头碰地,就把头的碰撞砍掉。站立需要身体接触地面,就只加接触面的碰撞。全碰撞模型留着但不用,是为了未来需要时不用从零开始。
每个多余的 collision geom 都在偷两样东西:计算时间和梯度质量。前者影响训练速度,后者影响策略质量。在 4096 个并行环境、50Hz 仿真、1-2 小时训练一个可用步态的规模下,省掉 20% 的碰撞检测可能就是 15 分钟训练时间和几个百分点的最终奖励差距。
Microduck 的碰撞模型策略本质上是一种仿真预算分配——把有限的计算力花在任务真正需要的物理交互上,其余的一律砍掉。这比”把所有东西都精确模拟”要有效得多。
下一篇会拆解 Microduck 的域随机化参数:电压跌落、指令延迟、摩擦系数抖动——那些让策略在真机上不裸奔的 DR 层到底调了什么。
