本节摘要:关节是引擎里"用约束方程把两个刚体绑起来"的机制,接触本质也是约束(不许穿透)。约束求解是数值近似最集中的环节,产生抖动、穿透、漂移、爆炸四大典型症状。本节给出关节类型对照、SDF 配置实例与症状-病因-处方对照表。
真实铰链靠机械结构限位,仿真里靠数学:一个 revolute 关节 = 一个方程"两刚体绕该轴的相对转角自由,其余五个自由度锁死"。引擎每步在解一个带约束的力学问题——约束力是拉格朗日乘子,求解器在摩擦、限位、电机力矩多边条件下迭代出满足所有约束的力。近似就藏在这些迭代里。
SDF 的关节类型速查:
| 类型 | 自由度 | 典型用途 | 关键参数 |
|---|---|---|---|
| revolute / hinge | 绕轴转 1 | 关节臂、舵机关节 | <axis><limit>、damping、friction |
| prismatic | 沿轴滑 1 | 导轨、升降机构 | 行程限位、阻尼 |
| ball | 球面 3 | 肩关节 | 通常配阻尼防漂 |
| fixed | 0 | 刚性连接 | 慎用(见下) |
| revolute2 | 串联双转 | 车辆转向轮 | 两轴参数 |
<!-- 一个带物理性格的 revolute 关节 --> <joint name="elbow" type="revolute"> <parent>upper_arm</parent> <child>forearm</child> <axis> <xyz>0 1 0</xyz> <limit> <lower>-2.2</lower> <upper>2.2</upper> <effort>60</effort> <!-- 力矩上限, 超过即打滑式失效 --> <velocity>3.0</velocity> </limit> <dynamics> <damping>0.5</damping> <!-- 粘性阻尼: 抑制振荡 --> <friction>0.1</friction> <!-- 库仑摩擦: 消耗启停能量 --> <spring_reference>0</spring_reference> <spring_stiffness>0</spring_stiffness> </dynamics> </axis> </joint>
注意 <limit> 里藏着两本容易被忽略的账:effort 与 velocity 是控制器的能力上限。设成 1e16(很多人图省事这么干),等于告诉引擎"电机无限强"——仿真里什么都能推动,上真机全还回来。
⚠️ 常见坑:fixed 关节不是免费的。有些引擎把 fixed 关节处理为真实约束(每步迭代),大量 fixed 关节链会明显拖慢仿真并引入数值误差。建模时尽量在 CAD/Mesh 阶段合并固定件,而不是用几十个 fixed 关节拼机器人。
地面不许穿透车轮——这也是一个约束方程,加上摩擦不等式后,求解器每步要解一个线性互补问题(LCP):接触力要么为零(分离),要么满足不穿透与摩擦锥(接触),两者互补。这就是 2.1 节 <kp>/<kd> 的岗位:把"硬约束"软化成"很硬的弹簧",让迭代求解更容易收敛。代价是软——太软会穿透,太硬会抖,这是调接触参数的永恒权衡。
| 症状 | 高概率病因 | 处方 |
|---|---|---|
| 抖动(静止物体高频颤动) | 接触刚度/阻尼不匹配;步长偏大;多接触点冲突 | 降 kp、加 kd;减小步长;简化碰撞几何 |
| 穿透(物体陷入地面) | 步长太大或碰撞体太薄 | 减小步长;给薄板加厚度;开启 CCD(若引擎支持) |
| 漂移(约束缓慢松开,关节"漏"角度) | 迭代次数不足;约束冲突(冗余约束) | 提高求解器迭代数;删冗余 fixed/ball 关节 |
| 爆炸(速度发散飞出场景) | 惯性张量量级错误;NaN 传染(effort 无限 + 硬限位冲突) | 核对惯性;给 effort/velocity 合理上限 |
口诀:先核惯性,再动步长,然后接触,最后几何。顺序反了(上来就乱调 kp),参数会互相掩盖,调出一个"碰巧能跑"但物理意义不明的配置。
💡 关键直觉:网格碰撞体是性能与稳定性的双料杀手。视觉网格可以百万面,碰撞体永远用基本几何体(盒、球、圆柱、凸包)近似——"视觉好看、碰撞简单"是物理仿真的基本修养。
"effort 设成 1e16 等于电机无限强"这句话值得跑一遍数字。给 3.2 节那条机械臂肘关节配置两组对照——effort=60(真实舵机规格)与 effort=1e16(图省事写法),末端挂 5 公斤负载做抬臂动作,记录关节实际力矩峰值:
# 打开关节状态话题里的力矩字段(Wrench/位姿带力矩需插件记录) gz topic -e -t /model/arm/joint_state | grep -E "effort|position" > arm_torque.log # 分别在 effort=60 与 effort=1e16 两个世界文件下各跑 20 秒仿真
# 对账:真实上限下抬得动吗?无限上限下用掉了多少"假力矩"? import re def peak(path): effs = [float(m) for m in re.findall(r"effort: ([0-9.]+)", open(path).read())] return max(effs) if effs else 0.0 p_real, p_inf = peak("arm_torque.log"), peak("arm_torque_inf.log") print(f"真实上限组力矩峰值 {p_real:.1f} Nm(被 effort=60 钳住)") print(f"无限上限组力矩峰值 {p_inf:.1f} Nm —— 是真机的 {p_inf/60:.1f} 倍需求")
典型结果:无限上限组为克服启动瞬态可能调用 200 Nm 以上,是真实舵机的三倍还多——这条轨迹直接搬到真机必然堵转。对账结论应写回控制器:把轨迹规划的加速度限制压到 effort=60 可承受的范围内重跑仿真,直到力矩峰值留出 20% 裕度。这类"把能力上限当真"的实验,是把仿真从演示工具变成验证工具的分水岭。
物理这本账记完。下一章转向机器人脚下与身边的世界——SDF 如何把地形、建筑、模型资源写成机器可校验的语义契约。