隐式 MPM#
Newton 的隐式物质点法(MPM)求解器用于模拟沙、雪等颗粒材料。Isaac Lab 为其提供 Newton 求解器配置、粒子资产类型,以及与刚体求解器的可选耦合。MPM 支持和刚体–MPM 耦合目前均处于实验阶段。
先从 示例与任务 中运行场景。分辨率、收敛、材质设置及 MPM 的近刚性极限见 MPM 求解器。
示例与任务#
这些示例和演示介绍 MPM 的构建与交互,默认使用 Newton GL 进行交互式查看。启动器默认使用 cuda:0;可通过 --device cuda:N 选择其他 GPU。
uv run isaaclab example mpm-granular
uv run isaaclab example mpm-two-way-coupling
uv run isaaclab demo snowball-smash
uv run isaaclab demo teapot-fill --fluid_render_mode particles
Franka 倒料任务#
IsaacContrib-Franka-Pour 任务从重置数据文件恢复回合,该文件包含相互衔接的 14 阶段重置分布。首次使用时,会从标准 Isaac Lab 资产根目录下载含 20,000 行的标准数据文件,因此训练无需另行准备该文件:
uv run isaaclab train --rl_library rsl_rl --task IsaacContrib-Franka-Pour \
--num_envs 2048
仓库中的生成器是复现或自定义该分布的可执行参考。在 L40S 级别的 GPU 上约需两分钟,它会写入一个可显式选择的本地数据文件:
uv run python scripts/tools/generate_franka_pour_reset_dataset.py
uv run isaaclab train --rl_library rsl_rl --task IsaacContrib-Franka-Pour \
--num_envs 2048 \
env.reset_dataset_path=datasets/franka_pour/reset_dataset.pt
任务会自动验证数据中保存的内容摘要。设置 ISAACSIM_ASSET_ROOT 可将标准数据文件的查找位置改为兼容的本地或自托管资产目录树。自定义的可复现实验仍可固定摘要。
源容器使用不参与碰撞的解析填充体积,其高度由 env.source_fill_level 控制,取值范围为 (0, 1] 。默认 0.70 表示填充至约 70% 的杯高;粒子数量随指定填充比例变化,而体素大小和粒子间距保持固定。 env.pour_target_frac 独立控制当前粒子总量中必须到达接收容器的比例。
使用标准任务配置,在 Kit 中运行检查点;无需外部回调或粒子覆盖配置:
uv run --extra isaacsim isaaclab play --rl_library rsl_rl --task IsaacContrib-Franka-Pour \
--checkpoint /path/to/model.pt --num_envs 1 --visualizer kit
与刚体耦合#
单独使用时,MPM 求解器只推进粒子。刚体充当碰撞体,其运动由其他机制提供,例如写入运动学位姿;材料不会对这些刚体施加反作用力。
双向交互使用 isaaclab_contrib.coupling 中的实验性耦合器。CouplerProxyCfg 将同一个 Newton 模型划分为若干命名条目:
刚体条目,例如 MJWarp,负责机器人或物体的刚体;
MPM 条目负责粒子,必须设置
in_place=True;代理映射将选定的刚体作为碰撞体提供给 MPM 条目,并将产生的冲量反馈给刚体条目。
mpm-two-way-coupling 示例、snowball-smash 演示和 Franka Pour 任务均使用这一方式。代理耦合、ADMM 耦合、数据交换模式及调优见 耦合求解器 。
数据模型#
配置与 USD#
MPM 材料是一种场景资产。MPMObjectCfg 扩展了可变形对象配置,并使用粒子生成器:
MPMGridCfg在局部坐标系中轴对齐的盒体内填充粒子点阵,分辨率由voxel_size和particles_per_cell决定。MPMPointsCfg将粒子放置在明确指定的局部位置,可选配速度、质量和半径。
两种生成器都会写入符合 schema 的 UsdGeom.Points 仿真几何体。网格配置仍先在 Isaac Lab 中生成点阵,再写入各点的位置、宽度、速度和质量。逐粒子的材料参数来自 MPMParticleMaterialCfg ,通过 NewtonMPMMaterialAPI 写入。阻尼参数的单位为 [s],写入值为 damping * young_modulus,单位为 [Pa·s]。critical_fraction 等求解器全局设置位于 MPMSolverCfg 中;其中可用 schema 表达的设置通过 NewtonMPMSceneAPI 写入物理场景 prim。
MPM 对象通过 Newton 的复制机制克隆。网格随机扰动在资产局部坐标系中使用固定种子生成一次,因此 USD 克隆共享相同的局部粒子分布。每个环境需要独立分布时,应使用重置事件或域随机化。
求解器网格与粒子生成器应使用相同的体素尺寸。和其他声明式资产一样,将生成的对象加入 InteractiveSceneCfg :
import isaaclab.sim as sim_utils
from isaaclab_newton.assets import MPMObjectCfg
from isaaclab_newton.physics import MPMSolverCfg, NewtonCfg
from isaaclab_newton.sim.spawners.mpm import MPMGridCfg
voxel_size = 0.02
sim_cfg = sim_utils.SimulationCfg(
dt=1.0 / 100.0,
physics=NewtonCfg(
solver_cfg=MPMSolverCfg(
voxel_size=voxel_size,
max_iterations=100,
tolerance=1.0e-4,
),
num_substeps=2,
),
)
media = MPMObjectCfg(
prim_path="{ENV_REGEX_NS}/Media",
spawn=MPMGridCfg(
lower=(-0.1, -0.1, 0.0),
upper=(0.1, 0.1, 0.2),
voxel_size=voxel_size,
particles_per_cell=2.0,
particle_placement="cell_center",
),
)
运行时资产#
MPMObject 实现 Isaac Lab 的可变形对象接口,因此场景重置、更新和状态操作可以像处理其他可变形体一样处理它。每个环境实例的粒子数相同,可通过 particles_per_object 获取。MPMObjectData 通过 particle_pos_w、particle_vel_w 和 particle_state_w 提供仿真世界坐标系中的粒子位置、速度和组合状态,可变形体的 nodal_* 属性是这些属性的别名。root_pos_w 和 root_vel_w 分别是每个实例内所有粒子位置和速度的平均值。位置、速度和状态的写入会立即生效,不支持可变形体运动学目标。
配置参考#
字段及默认值以自动生成的 MPMSolverCfg 参考为准。各字段可分为以下几组:
类别 |
字段 |
作用 |
|---|---|---|
分辨率 |
|
背景网格和粒子的分辨率,决定精度、活跃网格单元数量和内存占用。 |
网格存储 |
|
稀疏、稠密或固定网格的分配方式、预留容量,以及是否为每个仿真世界分配独立的局部网格。 |
流变求解 |
|
迭代上限、收敛容差、求解器或热启动流程,以及热启动数据来源。 |
离散化 |
|
粒子与网格之间的数据传递、形函数支撑域,以及各场使用的基函数。 |
材料与背景 |
|
求解器全局的屈服面坍缩参数,以及空区域的数值阻力。 |
碰撞体 |
|
碰撞体速度和法线的计算方式,以及每个子步结束后将粒子投影到碰撞体外部的可选操作。 |
隐式求解已负责处理碰撞体接触。project_outside_colliders 额外在步进后强制校正仍处于碰撞体内部的粒子。仅在确实需要这种几何校正时使用它,不能用它替代正确的初始状态、碰撞几何体或稳定的时间步长。
CUDA 图捕获#
仅当网格存储满足图捕获的稳定性要求时,MPM 步进才支持 CUDA 图捕获:
使用
"fixed"网格,且max_active_cell_count为正值;或者使用
"sparse"网格,max_active_cell_count必须为正,grid_padding=0,速度基函数为"Q1"或"B2"。应变基函数必须以"pic"开头,或为"P0"、"P1d"、"Q1d"、"Q1"之一;碰撞体基函数必须以"pic"开头,或为"Q1"、"S2"、"S3"之一。
稠密网格和未设置容量上限的网格不使用图捕获。
重置#
单独使用 MPM 求解器时,资产自动重置不会清除求解器历史,因为共享同一网格的仿真世界无法分别重置各自的历史。使用耦合器时,自动重置会调用耦合求解器的重置方法,将逐世界的重置掩码传递给包括 MPM 在内的所有条目。Franka Pour 等需要完整重置历史的任务,会使用独立的 MPM 仿真世界(separate_worlds=True),在写入完整状态后调用 reset_solver_state() 。在耦合运行中,这也会重置耦合求解器的历史。
可以先试试 交互式 MPM 演示 :反复进行相同的粒子下落实验,对比参考材料,并调整刚度、内聚力、摩擦和硬化参数。
渲染粒子表面#
MPM 仿真状态始终是一组粒子。表面重建是可选的可视化步骤,不会改变粒子运动、碰撞或材质行为。运行茶壶演示可对比可用模式:
# Reconstructed surface
uv run isaaclab demo teapot-fill \
--visualizer newton_gl --fluid_render_mode surface
# Surface and source particles together
uv run isaaclab demo teapot-fill \
--visualizer newton_gl --fluid_render_mode both
# Path-traced translucent surface
uv run --extra ovrtx isaaclab demo teapot-fill \
--visualizer newton_rtx --fluid_render_mode surface
# Isaac Sim Kit surface
uv run --extra isaacsim isaaclab demo teapot-fill \
--visualizer kit --fluid_render_mode surface
茶壶演示默认显示重建表面。选择 particles 或 both,可在 Kit、Newton GL 或 Newton RTX 中查看原始 MPM 状态。
Kit、Newton GL 和 Newton RTX 可视化器均支持表面渲染。
这段录制展示的是渲染外观,并非另一种本构模型。
茶壶演示在 sim.reset() 后配置 newton.geometry.ParticleSurface ,并在 sim.render() 前更新网格。启用 CUDA 捕获时,其渲染器会捕获表面提取操作,并在查看器的帧生命周期内暂存动态网格。
重建参数应独立于仿真调节:
voxel_size控制表面细节和内存用量,可以小于 MPM 求解器的体素大小。kernel_radius控制每个粒子对表面的影响范围。建议从粒子间距的三倍左右开始。max_grid_cells为 CUDA 图捕获提供固定容量的存储。如果重建区域超出预留网格,请增大该值。各向异性核能更好地保留薄片和拉长的流体特征,但开销高于各向同性核。
要在自己的场景中渲染表面,请在 sim.reset() 后将配置好的 newton.geometry.ParticleSurface 传给 ParticleSurfaceRenderer。在 sim.render() 前调用 update(),提取并准备当前网格。渲染器会处理 CUDA 图捕获、空表面、非活跃粒子和动态拓扑:
ParticleSurfaceRenderer 的实现
class ParticleSurfaceRenderer:
"""Extract one particle surface and stage its latest mesh for active viewers.
Configure a Newton ``ParticleSurface`` and construct this renderer after
resetting the simulation. Call :meth:`update` before rendering each frame
to reconstruct the surface from the current particle state.
"""
def __init__(
self,
visualizers: Iterable[BaseVisualizer],
surface: ParticleSurface,
*,
path: str,
color: tuple[float, float, float],
opacity: float,
roughness: float = 0.1,
use_cuda_graph: bool = True,
backend_manager: NewtonManager | None = None,
) -> None:
"""Allocate reusable extraction storage and optionally capture its CUDA graph.
Args:
visualizers: Active visualizers; Kit and Newton viewers are supported.
surface: Configured Newton surface extractor.
path: Viewer mesh path.
color: Surface RGB color in [0, 1].
opacity: Surface opacity in [0, 1].
roughness: Surface roughness in [0, 1].
use_cuda_graph: Capture extraction on CUDA when enabled.
backend_manager: Newton manager whose particle state is reconstructed. Defaults to the active
simulation's manager (deprecated; pass it explicitly).
Raises:
RuntimeError: If no Kit, Newton GL or RTX visualizer is active.
"""
from isaaclab_visualizers.kit import KitVisualizer
visualizers = tuple(visualizers)
self._visualizers = tuple(
visualizer
for visualizer in visualizers
if isinstance(visualizer, (NewtonGLVisualizer, NewtonRTXVisualizer))
)
self._kit_mesh = None
if any(isinstance(visualizer, KitVisualizer) for visualizer in visualizers):
from pxr import UsdGeom
from isaaclab.sim import PreviewSurfaceCfg, bind_visual_material, get_current_stage, spawn_preview_surface
self._kit_mesh = UsdGeom.Mesh.Define(get_current_stage(), path)
self._kit_mesh.CreateSubdivisionSchemeAttr(UsdGeom.Tokens.none)
self._kit_mesh.CreateDoubleSidedAttr(True)
self._kit_mesh.SetNormalsInterpolation(UsdGeom.Tokens.vertex)
material_path = f"{path}/material"
spawn_preview_surface(
material_path,
PreviewSurfaceCfg(diffuse_color=color, opacity=opacity, roughness=roughness),
)
bind_visual_material(path, material_path)
if not self._visualizers and self._kit_mesh is None:
raise RuntimeError("Particle surface rendering requires a Kit, Newton GL or RTX visualizer.")
from isaaclab_newton.physics import NewtonManager
if backend_manager is None:
backend_manager = NewtonManager._deprecated_active("ParticleSurfaceRenderer()")
self._model = backend_manager.get_model()
self._state = backend_manager.get_state_0()
self._surface = surface
self._path = path
self._color = color
self._opacity = opacity
self._roughness = roughness
self._empty_points = wp.empty(0, dtype=wp.vec3, device=self._model.device)
self._empty_indices = wp.empty(0, dtype=wp.int32, device=self._model.device)
self._empty_normals = wp.empty(0, dtype=wp.vec3, device=self._model.device)
self._surface_mesh = None
self._surface_graph = None
if use_cuda_graph and self._model.device.is_cuda:
self._surface_mesh = self._extract_surface()
with wp.ScopedCapture(device=self._model.device) as capture:
self._surface_mesh = self._extract_surface()
self._surface_graph = capture.graph
def update(self) -> int:
"""Stage the latest surface mesh and return its triangle count."""
if self._surface_graph is None:
self._surface_mesh = self._extract_surface()
else:
wp.capture_launch(self._surface_graph)
vertices, indices, normals = self._surface_mesh.to_arrays()
hidden = vertices is None or vertices.shape[0] == 0
triangle_count = 0 if hidden else indices.shape[0] // 3
if hidden:
vertices = self._empty_points
indices = self._empty_indices
normals = self._empty_normals
if self._kit_mesh is not None:
from pxr import UsdGeom, Vt
points = Vt.Vec3fArray.FromNumpy(vertices.numpy())
self._kit_mesh.GetPointsAttr().Set(points)
self._kit_mesh.GetFaceVertexIndicesAttr().Set(Vt.IntArray.FromNumpy(indices.numpy()))
self._kit_mesh.GetFaceVertexCountsAttr().Set(Vt.IntArray(triangle_count, 3))
self._kit_mesh.GetNormalsAttr().Set(Vt.Vec3fArray.FromNumpy(normals.numpy()))
if not hidden:
self._kit_mesh.GetExtentAttr().Set(UsdGeom.PointBased.ComputeExtent(points))
self._kit_mesh.GetVisibilityAttr().Set(UsdGeom.Tokens.invisible if hidden else UsdGeom.Tokens.inherited)
for visualizer in self._visualizers:
visualizer.log_mesh(
self._path,
vertices,
indices,
normals=normals,
hidden=hidden,
backface_culling=False,
color=self._color,
roughness=self._roughness,
metallic=0.0,
dynamic=True,
opacity=self._opacity,
)
return triangle_count
def _extract_surface(self):
"""Extract the surface from the current Newton particle state."""
return self._surface.extract(
self._state.particle_q,
self._model.particle_radius,
particle_flags=self._model.particle_flags,
particle_world=self._model.particle_world if self._surface.world_count > 1 else None,
)
MPM 如何接入 Newton 后端#
将 NewtonCfg 的 solver_cfg 设为 MPMSolverCfg ,会启用 NewtonMPMManager 。这是 Newton 管理器的专用实现,负责构建 Newton 的 SolverImplicitMPM。求解器将材料表示为粒子,并在背景体素网格上隐式求解其流变行为。MPM 管理器会对 Newton 管理器作如下配置:
在同一个 Newton 状态对象中原地更新粒子,而不对状态使用双缓冲。
在隐式求解过程中处理碰撞体接触,不使用 Newton 的碰撞处理管线。
将刚体几何体视为碰撞体。它不是刚体或关节系统的动力学求解器,也不接受外部刚体力。模型构建完成前,运动学刚体的质量会被设为零,凸网格则以三角网格形式传入求解器。
限制#
耦合的 MPM 条目必须原地步进,不支持
project_outside_colliders。