先记住一句话
机器人仿真的核心是一个离散闭环:用当前状态和控制生成接触与约束,求解满足动力学的速度/冲量,积分到下一状态,再由传感器和渲染生成观测。
1. 先分清“模型”和“状态”
| 部分 | 典型内容 | 变化速度 |
|---|---|---|
| 结构模型 | 刚体、质量/惯量、关节拓扑、碰撞形状、材质、执行器 | 通常在 build 后固定 |
| 动态状态 | 位置/姿态、关节角、线/角速度、粒子位置 | 每个 physics step 更新 |
| 控制 | 力矩、力、位置/速度目标、外部 wrench | 按 control rate 更新 |
| 派生量 | 接触点/法向、Jacobian、加速度、传感器读数 | 从模型与当前状态计算 |
结构模型回答“世界由什么组成”;状态回答“这一瞬间世界在哪里、怎样运动”。高吞吐引擎通常只保存一份共享结构,再给每个并行环境保存一份状态。
2. 刚体动力学在求什么
对关节化机器人,常用广义坐标写成:
M(q) q̈ + C(q, q̇) + g(q) = τ + J(q)ᵀ λτ 是执行器输入,λ 是接触、摩擦、关节限位等约束产生的力或冲量。最难的往往不是自由空间里的 Mq̈,而是接触集合会离散变化、摩擦带不光滑、多个约束还要同时近似满足。
3. 一次 step 的七个阶段
control u_t + state x_t
↓
1. 清力;施加重力、执行器和外力
2. broad phase:筛出可能碰撞的形状对
3. narrow phase:计算接触点、法向和间隙
4. 组装关节、接触、摩擦、限位等约束
5. solver:迭代求速度、冲量或位置修正
6. integrator:推进到 x_{t+1}
7. sensor / render / reward / termination / reset
引擎可以交换第 4–6 步的数值方法,但整体数据依赖不会消失。Newton 明确把 CollisionPipeline.collide() 与 solver.step() 分开;Genesis 在每个 substep 运行活跃 solvers,再由 coupler 处理跨求解器交互;Isaac Sim 则让后端推进物理,再把状态送到 Fabric、Tensor API 和渲染路径。
4. 广义坐标与最大坐标
广义 / reduced coordinates
只保存关节自由度 q,树结构约束天然满足。对多关节机器人紧凑高效,但闭环结构和任意约束处理更复杂。
最大 / maximal coordinates
每个刚体保存 6D 位姿,再用约束把关节“锁”起来。表达通用,但状态更大,约束求解负担更重。
这不是精度高低的简单排序。Newton 同时提供两类 solver;PhysX articulation 典型使用 reduced coordinates;不同表述会改变状态布局、求解器和可微分路径。
5. 三个时钟必须分开
- physics dt:物理积分步长,例如 1/240 s。
- control dt:策略/控制器多久更新一次,常为若干 physics steps。
- render/sensor dt:相机、LiDAR、IMU 各自采样频率和相位。
若控制频率为 50 Hz、物理为 200 Hz,那么一个动作通常保持 4 个 physics steps。改变 substeps、decimation 或 actuator gain,等价于改变闭环系统;不能一边改一边宣称只换了仿真器。
6. 为什么 GPU 并行有效
单个机器人难以填满 GPU。并行环境把相同模型的多份状态堆成 batch,让碰撞、动力学和 reset kernel 一次处理数千个 world。真正的吞吐取决于:
- 场景是否同构,碰撞对和接触数是否有容量上限;
- 数据是否一直留在 GPU,还是每步同步回 CPU/USD;
- 能否用 CUDA Graph 或类似机制减少 kernel launch 开销;
- 是否渲染,以及渲染分辨率、相机数和同步策略。
7. 最小的仿真环境伪代码
model = build_or_import_scene(asset, physics_params)
state = allocate_state(batch=B)
while training:
action = policy(observe(state))
control = actuator_model(action, state)
for _ in range(control_decimation):
contacts = collide(model, state)
state = solver_step(model, state, control, contacts, physics_dt)
obs = sensors(state, contacts)
reward, done = task(obs, action)
state[done] = reset(seed, domain_randomization)
换引擎时最值得对齐的不是类名,而是这里每个变量的语义、单位、顺序、频率和 reset 行为。
8. 四个仿真 step 手算
例 1:半隐式 Euler
1 kg 物体受 2 N 恒力,$dt=0.1$ s,初始 $x=0,v=0$。$a=F/m=2$;先更新 $v'=0+2×0.1=0.2$ m/s,再 $x'=0+0.2×0.1=0.02$ m。
例 2:控制 decimation
Physics 240 Hz、control 60 Hz,decimation=$240/60=4$。一个动作保持 4 个 physics steps,每步 4.167 ms,总计 16.67 ms。
例 3:碰撞冲量
1 kg 球沿法向以 -2 m/s 撞静止墙,恢复系数 0.5。目标离开速度 +1 m/s,冲量 $J=m(v^+-v^-)=1×(1-(-2))=3$ N·s。
例 4:并行状态内存
4096 个环境,每个状态 256 个 fp32 数,内存 $4096×256×4=4{,}194{,}304$ bytes,约 4 MiB;结构模型共享,否则复制大型 mesh 会昂贵得多。
更小的 dt、更多 solver iterations 往往提高稳定性,但不会自动让模型“更真实”。错误的碰撞几何、质量、摩擦、执行器或传感器延迟,会稳定地产生错误结果。
自测
1. 为什么接触力不只是由穿透深度唯一决定?
接触是与质量、速度、摩擦、其他约束和数值求解共同决定的;许多实时引擎求的是满足一组离散约束的冲量或修正。
2. 为什么从 120 Hz 改成 240 Hz 后必须重新检查控制器?
离散时间闭环变了;驱动、阻尼、动作保持时间与接触求解的有效行为都可能变化。
3. 为什么不能只用“FPS”选仿真器?
FPS 依赖场景、环境数、solver 参数、碰撞复杂度、渲染和硬件;还没有衡量物理误差、传感器质量与工作流成本。
一手资料
Newton Core Concepts 给出 Model/State/Control/Contacts 的标准循环;Genesis Physics Engine 说明 solver/coupler substep;PhysX Simulation 解释约束迭代与离散更新。