15 KiB
MuJoCo Web 仿真平台
基于 MuJoCo 官方 @mujoco/mujoco JavaScript/WASM 包的中文桌面仿真平台。物理引擎使用默认单线程入口,模型和资源只写入浏览器内的 Emscripten MEMFS,不上传到服务器。
MVP 功能
- 导入单个/多个 MJCF(XML)、URDF 和关联资源
- 兼容常见 ROS URDF:规范化重复 material、解析
package://工程内资源路径 - URDF 导入时可选为 hinge/slide 关节生成 motor 驱动器,并可用 kp/kv 调整对应 MJCF 关节刚度与阻尼,并将可调位置/朝向的摄像头固连到指定机器人 Body
- 导入保留相对路径的文件夹或 ZIP 工程
- 多模型入口选择、中文加载和编译错误
- Three.js primitive、mesh、材质/贴图显示与对象选择
- 播放、暂停、单步、重置、0.25×–4× 速度
- actuator 滑杆、hinge/slide 关节拖动、动态 body 外力拖拽
- 内置平地、坡道、楼梯、可复现随机障碍物及 9 类系统参数化地形,可配置尺寸、摩擦、难度、种子与高度场采样精度
- 导入单文件
.py控制器,通过本地 Pyodide 在mj_step前按仿真时间同步执行 - 导入 mjlab 导出的
policy.onnx,在浏览器本地执行 Go2-W 平衡/速度策略推理 - 从图形界面向本机训练桥接服务发起 mjlab 强化学习训练、查看进度/日志、停止任务并导入训练生成的 ONNX;可在独立 TensorBoard 风格页面运行 DeepSeek 奖励函数自调参
- 可配置仿真遥测记录,实时查看速度、机身姿态、位置、驱动力等指标并导出 CSV/JSON
- FPS、物理耗时和主线程步进预算提示
开发
从仓库根目录运行:
npm install
npm run dev
打开 Vite 输出的 HTTP 地址。应用不支持通过 file:// 直接打开,也不注册 Service Worker/PWA。
质量检查
npm run typecheck
npm run lint
npm test
npm run build
npm run test:e2e
# 或执行除 E2E 外的完整检查
npm run check
E2E 默认使用系统安装的 Google Chrome。若没有 Chrome,可修改 playwright.config.ts 或运行 npx playwright install chromium 后移除 channel: 'chrome'。
生产构建与本地静态部署
npm run build
python3 -m http.server 8080 --directory web-platform-dist
访问 http://127.0.0.1:8080/。构建使用 base: './',可部署到任意静态子目录。静态服务器需将 .wasm 返回为 application/wasm;Python 3.11+、Vite preview、Nginx 等常见服务器均支持。
工程导入约定
- 文件夹和 ZIP 中必须至少有一个根元素为
<mujoco>或<robot>的.xml/.urdf。 - 只有一个入口时自动加载;多个入口优先根目录
model.xml、scene.xml或唯一 URDF,否则弹窗选择。 - XML 的
include、mesh 和贴图路径必须相对于入口/编译器配置可解析。 - 路径穿越、绝对路径、加密 ZIP、重复路径会被拒绝。
- 默认限制:2000 个文件、单文件 128 MiB、总解压大小 512 MiB、ZIP 文件 128 MiB。
- 文件夹或 ZIP 中的
.py会显示在“控制 → Python 控制器”;也可以在加载模型后单独导入不超过 1 MiB 的.py。
地图模块
模型加载后打开右侧“地图”标签,即可使用统一地图资产库。工程地图、认证资产、内置程序地图和系统参数化地形共用一条“点击/拖放 → 轻量预览 → 场景树管理 → 一次应用”链路;任何来源的增删或参数修改都先进入同一个场景草稿,不会隐式触发 MuJoCo 编译。参数化地形包括离散障碍、沟壑、倒金字塔阶梯、深坑、金字塔阶梯、轨道、随机粗糙、踏石和波浪地形;相同参数与随机种子会确定性生成相同碰撞层。卡片可直接拖到三维画布,落点按 0.1 m 吸附;所有内置程序地图都可在视口选择、移动和绕 Z 旋转。认证资产的“自动落位(重力)”会查询程序地图真实表面,可落到坡道、楼梯、坑底或高度场,而不是固定假设 z=0。最后点击“一次编译应用”才组合 MJCF;编译失败时保留上一个可用仿真会话和完整草稿,普通模型重载也不会提前提交草稿。
工程地图由 map.json、静态 MJCF 碰撞层和可选的自包含 GLB 视觉层组成:
maps/warehouse/
├── map.json
├── physics/world.xml
├── physics/meshes/*.obj
├── visuals/scene.glb
└── authoring/map.scene.json # V3 可选创作层
最小描述示例:
{
"schemaVersion": 1,
"id": "warehouse",
"name": "仓库",
"coordinateSystem": { "units": "m", "up": "Z", "forward": "+X" },
"physics": { "source": "physics/world.xml" },
"visual": { "source": "visuals/scene.glb" },
"spawnPoints": [{ "id": "main", "name": "主入口", "position": [0, 0, 0.35], "yawDeg": 0 }]
}
物理地图仅允许静态 worldbody 以及 mesh、heightfield、texture、material 等基础 asset,不允许 joint、mocap body、actuator、sensor、include 或 default class。OBJ/STL 应使用简化碰撞模型;高精度模型只放入 GLB。GLB 必须是 2.0 自包含文件,外部 URI 会被拒绝。地图统一使用米制、Z-up、+X 前向坐标系。
V3 可编辑地图使用 schemaVersion: 2,并增加 "authoring": { "source": "authoring/map.scene.json" }。创作层支持方盒、圆柱、胶囊、坡道、楼梯和出生点。“场景 · 资产库”中的认证资产可点击添加或拖到画布落位;没有可编辑地图时,首个资产会创建临时 Schema V2 场景,但不触发 MuJoCo 重编译,“放弃场景更改”会同时移除临时实例和三份临时工程文件。新增对象支持自动贴地、沿世界 -Z 自动重力落位及锁定位姿;属性面板可编辑位置、绕 Z 旋转、原语尺寸、摩擦、颜色和透明度。工程地图源与场景实例分离:同一来源可以重复放置,实例拥有独立 XY/绕 Z 位姿,物理层、GLB 视觉层和出生点同步变换;编辑 authoring 源内容会明确同步到同源实例。视口支持吸附、拾取、空白取消选择、复制、删除及对齐地面;W/E/S 切换移动、旋转和缩放工具,Delete 删除,Ctrl+Z/Ctrl+Y 撤销重做。浏览器不会直接写回原目录,可使用“导出地图 ZIP”下载当前已提交地图包。没有 authoring.source 的 V1/V2 地图默认只读;仅由 box、cylinder、capsule 构成且不含 asset、材质、碰撞过滤或隐藏姿态语义的静态 MJCF,可通过“创建可编辑副本”显式升级;任何不可逆语义都会导致整体拒绝,不会静默丢失内容。
物理地图超过 2000 个 geom 会产生性能警告,超过 10000 个会被拒绝;GLB 超过 100 万三角面会警告,超过 300 万会被拒绝。当前原生 URDF 模式不支持地图,请切换到“转换为 MJCF”。
数据记录
加载模型后打开右侧“数据”标签,可以选择需要跟踪的 Body、采样频率和样本上限。内置通道包括世界系位置/速度、水平与三维速度、机身侧倾/俯仰/偏航角及角速度、累计里程、接触数、控制输入 RMS、驱动力 RMS、绝对驱动功率和广义速度 RMS。仿真重置不会删除已有数据,而是创建新分段,避免跨重置计算出错误速度;切换记录 Body 或采样配置会清空不兼容的旧数据。
记录只保留在当前浏览器会话中,达到样本上限后自动停止,可导出带稳定列名的 CSV 或包含通道元数据、摘要和样本的 Schema V1 JSON。SimulationSession/PhysicsAdapter 保留 configureDataRecorder、startDataRecording、stopDataRecording、clearDataRecording、exportDataRecording 接口;还可通过 registerDataChannel({ key, label, unit, read }) 在开始记录前注册业务自定义标量通道。数据源通过 TelemetrySource 抽象与 MuJoCo 解耦,后续可复用于 Worker 或远端仿真。
Python 控制器
Python 控制器是可信的单文件脚本,必须同步定义 step(ctx, state);可选定义 NAME、CONTROL_HZ(限制为 1–500 Hz)、init(api)、command(name, state)、reset(state) 和 dispose(state)。init 可用 api.joint(name)、api.actuator(name)、api.sensor(name)、api.body(name) 预解析 ID;step 可用 ctx.qpos(id)、ctx.qvel(id)、ctx.sensor(id)、ctx.body_quat(id)、ctx.body_position(id) 读取状态,并用 ctx.set_control(id, value) 写入经过有限值检查和 actuator 限幅的控制量。定义 command 后,界面会显示停止、前进、后退、左转、右转和起跳按钮,并分别传入 stop、forward、backward、turn_left、turn_right、jump。所有回调都必须同步;异常会自动停止控制器或显示诊断,运行期异常还会暂停仿真并清零 ctrl。
当前 Python 与 MuJoCo 都运行在主线程,以保证闭环调用严格位于 mj_step 前。仅运行可信脚本;死循环仍可能阻塞页面。Pyodide 及 Python 标准库由 npm 包随生产构建离线发布,不从 CDN 下载;暂不支持第三方 Python 包、pip 或多文件 import。
本地强化学习训练
训练仍由本机 Python/mjlab 进程执行,但可以从右侧“控制 → 本地强化学习训练”直接发起和管理。仓库已经内置默认 Go2 任务的训练代码与资产;先按 training_server/README.md 安装训练依赖,再使用对应 Python 启动本地桥接服务:
npm run training-server -- \
--trainer-python /path/to/training-env/bin/python
服务启动时会在终端输出一个随机访问令牌;在界面中填写该令牌后连接。令牌仅保存在当前标签页的 sessionStorage。界面默认连接 http://127.0.0.1:8765,可选择服务端允许的任务、并行环境数、训练迭代、随机种子、CPU/GPU、GPU 编号和实验记录方式。W&B 默认为本地离线模式,无需登录或 API Key;也可完全禁用,只有明确选择在线模式时才会联网登录。训练期间页面轮询迭代进度与最近日志,可以停止任务;训练成功后点击“导入策略”,生成的 policy.onnx 会进入现有 ONNX 加载流程。普通训练还可以选择自调参产生的命名 reward preset,而不会改写仓库默认配置。
连接服务后点击“打开自调参 Agent 工作台”会打开独立 tuning.html。该页面采用 Cyber-Industrial 三栏控制台:左侧展示 Session/ASHA 晋级树和 Trial 对比选择,中间使用 uPlot 叠加多 Trial 增量收敛曲线(金线标记历史最优)及六维物理评分,右侧以可折叠因果时间线展示 rationale、expected impact、置信度和评估结果。工具栏支持自动/逐轮审批切换、一次一 Trial 的调度令牌、服务端参数范围/固定值护栏、回滚历史最优或复现任意安全 Trial;Reward Merge Patch 与回滚差异由按需加载的 Monaco Diff 审查。scalar 以 1 Hz 非重入方式增量轮询,进入固定容量环形缓冲并由 requestAnimationFrame 合批后调用 uPlot.setData,不会在每次轮询时重建图表。新标签页 URL 不包含 token;同源 opener 会一次性交接凭据,直接打开页面时也可手工输入。DeepSeek key 始终由本地 Python 服务的 DEEPSEEK_API_KEY 环境变量读取,浏览器不会接触该 key。
桥接服务只监听本机回环地址,并检查 Host、Origin 和 Bearer Token;仅接受允许列表中的任务和经过范围校验的参数,不执行前端提供的 Shell 命令;一次只运行一个训练进程。默认任务使用仓库内置的 Go2 机器人资产与环境配置,不会自动把浏览器中临时编辑的 MJCF/URDF 作为训练环境。自定义浏览器模型训练需要在兼容的外部训练工程中注册 task,并通过服务的 --trainer-root 指定该工程。服务配置、接口和安全边界见 ../training_server/README.md。
ONNX 强化学习策略
当前内置任务兼容 unitree_rl_mjlab Go2 velocity 的部署观测顺序:
base_ang_vel(3) + projected_gravity(3) + velocity_command(3)
+ gait_phase(2) + joint_pos_rel(12) + joint_vel_rel(12) + last_action(12)
= 47 维观测
策略必须具有一个 float32 输入和至少一个 float32 输出,输入末维为 47、输出末维为 12。动作按 FL、FR、RL、RR 的 hip/thigh/calf 顺序解释,转换为 default_joint_pos + 0.25 * action 的关节目标。对于 motor 模型,平台使用与部署配置一致的 kp/kd 执行位置 PD;对于 position actuator,直接写入目标位置。Go2-W 的四个轮电机在此首版腿式策略中保持零力矩。
使用步骤:
- 导入浮动基座 Go2-W MJCF/URDF,确保腿部关节与 actuator 使用 Unitree 标准命名;
- 打开右侧“控制 → ONNX 强化学习策略”;
- 从工程中选择或单独导入
policy.onnx; - 加载策略,设置前向、侧向和偏航速度,启用策略后播放仿真。
ONNX Runtime Web 的推理接口是异步的。物理循环会在每个 mj_step 前持续施加最近一次已完成的动作,并以 50 Hz 提交新观测;界面会显示推理耗时和次数。ONNX 与 Python 控制器互斥,启用其中一个会停止另一个。
Important
当前内置契约是参考 mjlab Go2 的 12 腿关节策略,不是包含四个轮电机动作的 16 自由度 Go2-W 专用策略。若训练 Go2-W 轮式策略,需要后续同时扩展训练端部署配置和浏览器任务清单,确保观测、动作及归一化完全一致。
示例
fixtures/ 包含(用于测试和手工验收,不会打进生产构建):
mjcf_include/:MJCF include、OBJ/STL mesh 和 PNG texture;urdf_mesh/:引用 OBJ 的 URDF;python_controller/:倒立摆模型及balance.pyPD 控制器;invalid.xml:无效模型;missing-resource.xml:缺失资源错误示例。
在“打开文件夹”中选择夹具目录即可加载。
当前限制
- 仅面向桌面版 Chrome、Edge、Firefox;未适配手机和平板。
- 物理运行在主线程、单线程 WASM。超出每帧预算时限制追帧并提示。
- ONNX Runtime Web 当前使用单线程 WASM;策略必须将观测归一化包含在导出的 ONNX 图内,平台不会额外加载训练 checkpoint 的运行均值。
- 不支持 Xacro、账号或云端保存;Python 控制器暂不支持第三方包和不可信代码隔离。
- 关节拖动只支持 hinge/slide;ball/free joint 只读。
- MuJoCo WASM 本身不支持 DAE mesh。平台会移除 DAE visual,并以 collision 几何显示;DAE collision 会替换为半径 0.05 m 的占位球体并在界面警告。高精度仿真应先将 DAE 转为 OBJ/STL 或改为 URDF primitive。
- 导入工程只存在当前页面内存,刷新页面后需重新导入。