Axi's Blog
Isaac Sim 一百讲(6):Collision 进阶Blur image

前言#

在此之前,我们已经了解了如何导入一个 Mesh 的 USD 资产,并且通过 Collsion 和 RigidBody 的 API 来让这些物体具有一定的物理属性,可以互相碰撞,并且可以被施加一个外力。然而事实上如你所见,假如你将大量 messy 的资产,如来自 Objaverse 的资产加入到仿真中,因为物理不真实等情况,导致物体依然在碰撞的过程中出现穿模或者弹飞的现象。

这一现象事实上是一切物理引擎的通病,在 Mujoco 中,读者依然需要调节物体的 stiffness 与 damping,来确保两个物体在接触之后,一个物体可以尽快弹出另一个物体,并且保持稳定,从而实现稳定的碰撞,而对于 Isaac,自然也不例外。本章节我们会介绍在大量调参之后得出的直接结论,这一系列的 API 以及经验可以帮到你,直接地尽可能避免这一现象。

现象描述#

在此处挑选了 Isaac Sim 中的一对资产,这对资产在 .glb 阶段之后经过了 coacd 的凸分解,并且将 Visual Mesh 与 Collsion Mesh 分离,其中 Collsion mesh 即 coacd 处理之后的 mesh,设置为 Invisible,并且 Collsion Type 设置为 ConvexHull。

资产的链接,读者可以点击 这里 下载,并且拖动到 Isaac 中使用。注意 scale 调节为 0.001,这是由于比例尺的问题。

在给二者添加默认的 Rigid Body,将盘子翻转,并且拖动到一定的高度之后 Play,可以观察到如下现象:

而这一切都是因为穿模以及关联的一系列原因引起的。

解决方案#

解决这一系列的问题,在大量的尝试之后,发现两个参数的同时修改可以显著缓解,其中之一是 Contact offset 参数,按照 Isaac 的文档 中的描述,其为:

The Contact Offset dictates how far from the collision geometry, irrespective of Rest Offset, the simulation engine starts generating contact constraints. The tradeoff for tuning the contact offset is performance vs. collision fidelity: A larger Contact Offset results in many contact constraints being generated which is more computationally expensive; a smaller offset can result in issues with contacts being detected too late, and symptoms include jittering or missed contacts or even tunneling (see notes on CCD above).

说白了就是隔多远就开始计算。

不过值得一提的是本段落中提到的 CCD,即 Continuous Collision Detection,文档中描述其为最有效的解决物理接触的方法,但是事实上在笔者大量的实践之后发现,在开启全局 CCD,并且某一物体的 RigidBody 的 Enable CCD 为 True 时,穿模极易导致机械臂的夹爪(接触位置)与本体分离,如视频中所示。

因此除非在确保 Isaac 5.0.0 版本已经修复本 Bug 之后,不建议使用 CCD 解决任何的物理问题,本 Bug 在 4.1.0/4.2.0/4.5.0 中均可以进行复现。

回到正题,于是可以使用 contact offset 来部分解决这一问题,设置到 0.02 即可。这是一个足够小,但是有效的数字,并且不会对性能产生太大的影响。同时读者在后续自己的实践中也需要注意,伴随着如 contact offset 等参数被设置,物理仿真的耗时会随着场景中的物体增多而增多,在物体密集的场景中,甚至或许是指数级的,因此在设置的过程中需要区分前景与背景物体,并且灵活地设置参数。

设置的代码如下,同时包括对于第五讲中代码在迭代后的升级版本:

from omni.isaac.core.utils.prims import get_prim_at_path  # type: ignore
from pxr import UsdPhysics  # type: ignore
from pxr import PhysxSchema  # type: ignore
from pxr import Sdf  # type: ignore

def remove_colliders(prim_path):
    prim = get_prim_at_path(prim_path)
    schema_list = prim.GetAppliedSchemas()
    if "PhysicsCollisionAPI" in schema_list:
        prim.RemoveAPI(UsdPhysics.CollisionAPI)
    if "PhysicsMeshCollisionAPI" in schema_list:
        prim.RemoveAPI(UsdPhysics.MeshCollisionAPI)
    if "PhysxConvexHullCollisionAPI" in schema_list:
        prim.RemoveAPI(PhysxSchema.PhysxConvexHullCollisionAPI)
    if "PhysxConvexDecompositionCollisionAPI" in schema_list:
        prim.RemoveAPI(PhysxSchema.PhysxConvexDecompositionCollisionAPI)
    if "PhysxSDFMeshCollisionAPI" in schema_list:
        prim.RemoveAPI(PhysxSchema.PhysxSDFMeshCollisionAPI)
    if "PhysxTriangleMeshCollisionAPI" in schema_list:
        prim.RemoveAPI(PhysxSchema.PhysxTriangleMeshCollisionAPI)
    for child in prim.GetAllChildren():
        remove_colliders(str(child.GetPath()))


def set_colliders_by_prim_path(
    prim_path, collision_approximation="convexDecomposition", convex_hulls=None
):
    prim = get_prim_at_path(prim_path)
    for child in prim.GetAllChildren():
        set_colliders_by_prim_path(
            str(child.GetPath()), collision_approximation, convex_hulls
        )
    if prim.GetTypeName() == "Mesh":
        collider = UsdPhysics.CollisionAPI.Apply(prim)
        mesh_collider = UsdPhysics.MeshCollisionAPI.Apply(prim)
        mesh_collider.CreateApproximationAttr().Set(collision_approximation)
        collider.GetCollisionEnabledAttr().Set(True)
        if collision_approximation == "convexDecomposition":
            collision_api = PhysxSchema.PhysxConvexDecompositionCollisionAPI.Apply(prim)
            collision_api.CreateHullVertexLimitAttr().Set(64)
            if convex_hulls is not None:
                collision_api.CreateMaxConvexHullsAttr().Set(convex_hulls)
            else:
                collision_api.CreateMaxConvexHullsAttr().Set(16)
            collision_api.CreateMinThicknessAttr().Set(0.001)
            collision_api.CreateShrinkWrapAttr().Set(True)
            collision_api.CreateErrorPercentageAttr().Set(0.1)
            collision_api = PhysxSchema.PhysxCollisionAPI.Apply(prim)
            collision_api.CreateContactOffsetAttr().Set(0.02)
        elif collision_approximation == "convexHull":
            collision_api = PhysxSchema.PhysxConvexHullCollisionAPI.Apply(prim)
            collision_api.CreateHullVertexLimitAttr().Set(64)
            collision_api.CreateMinThicknessAttr().Set(0.00001)
        elif collision_approximation == "sdf":
            collision_api = PhysxSchema.PhysxSDFMeshCollisionAPI.Apply(prim)
            collision_api.CreateSdfResolutionAttr().Set(1024)
    return prim

def set_colliders(
    prim_path, collision_approximation="convexDecomposition", convex_hulls=None
):
    remove_colliders(prim_path)
    prim = set_colliders_by_prim_path(prim_path, collision_approximation, convex_hulls)
    return prim
python

直接调用 set_colliders 即可。

然而本参数依然不能本质上解决这一问题,在设置了远一些就计算还不能解决的时候,一个想当然的补救措施自然而生,那就同时多算几次不就好了。控制 Position 的迭代次数的参数为 Rigid Body 的 position solver iterations。本参数设置到 32 即可。

def set_rigid_body_solver_position_iteration_count(prim_path, count):
    prim = get_prim_at_path(prim_path)
    rigid_body = PhysxSchema.PhysxRigidBodyAPI.Apply(prim)
    rigid_body.CreateSolverPositionIterationCountAttr().Set(count)
    return prim
python

此时再次播放,可以观察到如下现象:

One More Thing#

至此我们得到了一个几乎可以稍微一劳永逸的方案,可以解决大部分的穿模问题,并且不会对性能产生太大的影响。同时,摩擦力等内容不会单独花费一章节进行讲解,在这里进行简单的提及。

本质上如摩擦力以及弹力等内容,实际上是通过 Collsion 才可以传达的,因此在被设置了 Collison 的物体上,可以设置名为 Physics Material 的内容,即物理材质。Physics Material 在 Prim Path 下存在实体,而在物体的参数中则是标记关联的 Material 的 Prim Path,这意味着不同的 Prims 可以共用同一 Physics Material。当然,尽管如此,为了方便管理,一般来是每个物体设置一个 Physics Material。创建 Physics Material 的代码如下:

from pxr import PhysxSchema  # type: ignore
from omni.isaac.core.prims import GeometryPrim  # type: ignore
from omni.isaac.core.materials import PhysicsMaterial  # type: ignore


def add_physics_material(prim_path, static_friction=1.0, dynamic_friction=1.0):
    prim = str(prim_path)
    prim = GeometryPrim(prim_path)
    try:
        physics_material = PhysicsMaterial(
            prim_path=prim_path + "/physics_material",
            static_friction=static_friction,
            dynamic_friction=dynamic_friction,
            restitution=0.1,
        )
        prim.apply_physics_material(physics_material)
    except Exception as e:
        print("Physics material already exists")
    return prim
python

总结#

读者经过本章节,不难自己学习到如何调节合理的参数,并且设置 Physics Material。通过上述提供的 cup and plate 的素材,读者不难进行一个实践,使用代码,在 Isaac Sim 中导入二者调节初始位置,设置物理参数以及 Physics Material,并且进行仿真。任何问题欢迎留言或者致信 axihelloworld@gmail.com,愿你开心。

Isaac Sim 一百讲(6):Collision 进阶
https://axi-blog.pages.dev/blog/isaac-6
Author 阿汐
Published at August 17, 2025
Comment seems to stuck. Try to refresh?✨