拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MuJoCo 接触力实战:在控制器回调里拿到当前帧接触力

MuJoCo 接触力实战:在控制器回调里拿到当前帧接触力

MuJoCo 接触力实战:在控制器回调里拿到当前帧接触力

【免费下载链接】mujocoMulti-Joint dynamics with Contact. A general purpose physics simulator.项目地址: https://gitcode.com/GitHub_Trending/mu/mujoco

在 MuJoCo 力反馈控制中,控制器回调里读到的接触力往往比姿态慢一帧,抓取力控制因此抖动难稳。读完这篇你能说清力读数滞后的原因,并用两段式 step 与efc_force字段取到当前帧力,直接上手机器人抓取力闭环。

现象与误区:力读数总比姿态慢一拍

做抓取控制时,最常见的抱怨是:回调里读到的接触位置、法向力总是"上一刻"的,据此算出的力闭环一会儿抓松一会儿抓破,物体掉下去又弹起来。

还有一个隐蔽的误区。不少代码直接拿contact[i].friction[0]当力幅值用——但按 include/mujoco/mjdata.h 里的定义,friction[5]存的是摩擦系数(切向1、切向2、自旋、滚转1、2),量级是 0.6、0.05 这种小数,根本不是牛顿。拿它闭环,读数自然忽大忽小。

力读数为什么总比姿态慢一帧?因为接触力在这条流水线里本来就产生得晚。

原理:接触力在正向动力学链路的哪个环节诞生

MuJoCo 的一帧由mj_step1+mj_step2组成(mj_step就是它俩的打包)。白话说:

  • step1 阶段:mj_fwdPosition更新运动学并做接触检测,填充d->ncon(当前接触数量)和d->contact数组;随后mj_fwdVelocity算速度相关量。
  • step2 阶段:mj_fwdAcceleration算平滑力,mj_fwdConstraint跑约束求解器,此时才产出真正的力——存在d->efc_force(约束空间里的力数组);最后积分状态,时间走一帧。

关键认知:mjContact数组只存"几何信息"——位置pos、法向所在的frame矩阵、双方几何体geom[2]。每个接触点还有一个efc_address字段,是它在efc_force中的下标;-1表示该接触在间隙内、没参与求解,也就没有力。求解器没跑之前,你看到的力只能是上一帧的旧值。

打个比方:接触检测像安防系统"上报哪里碰了",求解器才是"开力量罚单"的人。回调发生在开罚单之前,读到的自然是旧账单。

🔧 实操:两种取当前帧接触力的办法

方案 A:拆分主循环,在求解后读力(推荐,改动最小)

// 主循环: 把 mj_step 拆成两段, 在求解完成后读力 for (int i = 0; i < 10000; i++) { mj_step1(m, d); // 位置+速度阶段: 完成本帧接触检测 mj_step2(m, d); // 加速度+约束求解+状态积分 // 此刻 efc_force 就是刚模拟完这一帧的接触力 graspController(m, d); // 读当前帧力, 写出下一帧的 ctrl }

为什么这么改:接触力只在mj_step2的求解环节生成,把"读力"挪到它之后,数据就对应上一行刚走完的那一帧。要说明的是,力对应已模拟帧、新ctrl对应下一帧,这是一步延迟的标准力反馈结构,工程上完全够用。

拿到力向量还需要两步:按下标取三分量,再用接触帧矩阵转到世界坐标系。

// 取接触点 i 在世界系下的三维力, 写入 out void contactForce(mjData* d, int i, mjtNum out[3]) { mjContact* con = &d->contact[i]; if (con->efc_address < 0) { // 在间隙内, 未参与求解 out[0] = out[1] = out[2] = 0; return; } mjtNum f[3]; f[0] = d->efc_force[con->efc_address]; // 法向力 f[1] = d->efc_force[con->efc_address + 1]; // 摩擦力1 f[2] = d->efc_force[con->efc_address + 2]; // 摩擦力2 // frame 是 3x3 接触帧(法向在第一行), 转置相乘即变换到世界系 mju_mulMatTVec3(out, con->frame, f); }

方案 B:回调内部手动补一次求解

如果架构上只能在回调里拿力,就让它自己把"开罚单"环节补跑一遍:

// 回调内手动补跑 加速度+约束求解, 得到当前 (状态, ctrl) 组合下的力 void graspController(const mjModel* m, mjData* d) { mj_fwdAcceleration(m, d); // 按当前 ctrl 重算平滑力与加速度 mj_fwdConstraint(m, d); // 跑约束求解器, 刷新 efc_force mjtNum fn = 0; for (int i = 0; i < d->ncon; i++) { if (d->contact[i].efc_address >= 0) fn += d->efc_force[d->contact[i].efc_address]; // 累加法向力 } d->ctrl[0] = 0.5 + 0.01 * fn; // 比例反馈: 离目标越远抓得越紧 if (d->ctrl[0] < 0.0) d->ctrl[0] = 0.0; // 限幅 if (d->ctrl[0] > 1.0) d->ctrl[0] = 1.0; }

代价是每帧多一次约束求解,仿真会变慢——所以默认先用方案 A,方案 B 留给"回调必须自给自足"的场景。

⚠️ 避坑:常见问题与对策

错误现象原因对策
力读数总比姿态慢一帧在约束求解完成前读了力拆 step1/step2,step2 后读;或回调内手动补求解
抓取力忽大忽小、控制震荡接触状态开关与求解器迭代带来逐帧波动对力做一阶低通滤波,或改用触觉传感器输出
力值是不到 1 的小数把contact[i].friction[0](摩擦系数)当成力用efc_force[efc_address]取力
方案 B 后仿真明显变慢每帧多跑一次约束求解降频读力(每 N 帧一次),或改用方案 A
多接触点合力对不上每行 efc 是一个接触点的力,方向在接触局部系按geom对筛选累加,经frame矩阵转到世界系

接触力是正向动力学的输出而不是输入,抓住"检测在前、求解出力、积分收尾"这个顺序,回调里取力就不会再对错位。方案 A 一行改动就能消除一帧滞后,方案 B 覆盖必须自包含的回调场景,配合限幅与滤波即可搭起稳定的抓取力闭环。

延伸阅读(仓库内路径):

  • include/mujoco/mjdata.h:mjData、mjContact、efc_force字段定义
  • doc/programming/simulation.rst:仿真流程与正向动力学文档
  • sample/basic.cc:主循环与回调的完整示例
  • model/tactile/tactile.xml:触觉传感器模型示例

【免费下载链接】mujocoMulti-Joint dynamics with Contact. A general purpose physics simulator.项目地址: https://gitcode.com/GitHub_Trending/mu/mujoco

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表