3. USD与场景构建

在Isaac Sim中,场景构建是一切仿真工作的起点。无论是搭建一个简单的物体掉落实验,还是构建完整的仓储物流环境,都离不开对USD(Universal Scene Description,通用场景描述)的理解。USD最初由皮克斯动画工作室开发,用于解决电影制作中大规模场景数据的协作与交换问题,如今已成为NVIDIA Omniverse和Isaac Sim的底层数据格式。可以这样理解:USD之于Isaac Sim,就像HTML之于网页——它定义了场景中“有什么”“在哪里”“如何组织”。本章将系统讲解USD的核心概念、场景层级结构、Prim操作流程以及环境搭建的基础方法,帮助你建立清晰的场景构建思维。

USD 场景 核心概念 Prim / Attribute / Relationship 场景层级 Stage → Prim → Property Prim 操作 创建 / 查询 / 修改 / 删除 Prim 类型 Xform / Mesh / Camera / Light Material / Physics / Joint 层级关系 /World/Table/Cube 父级变换影响子级 常用 API GetPrimAtPath / CreatePrim GetAttribute / SetAttribute 环境搭建基础 Stage 设置 / 地面 / 光照 / 碰撞

3.1 USD核心概念:从Prim到场景的基石

USD的核心设计思想是“分层合成”(Layering & Composition)。一个USD场景由多个层(Layer)叠加而成,每个层可以包含对场景的部分描述,最终通过合成规则生成一个统一的“舞台”(Stage)。这种设计使得多个开发者可以并行编辑同一场景的不同部分,而不会互相覆盖。在Isaac Sim中,我们通常直接操作Stage对象,但理解层的概念有助于排查场景加载异常的问题。

在USD中,Prim(Primitive的缩写)是场景图的基本节点。每个Prim代表场景中的一个实体,可以是一个物体、一个光源、一个相机,甚至是一个逻辑分组。Prim通过路径(Path)唯一标识,例如 /World/Table 表示World下的Table Prim。Prim可以包含以下内容:

  • 属性(Attribute):Prim的数据字段,如位置(translate)、旋转(rotate)、缩放(scale)、颜色(color)等。属性有明确的类型,如float、double、bool、、color3f等。
  • 关系(Relationship):Prim之间的引用或连接,例如一个Mesh Prim的材质关系指向某个Material Prim。
  • 元数据(Metadata):描述Prim自身的信息,如显示名称、文档说明、激活状态(active/inactive)等。

在Isaac Sim中,最常用的Prim类型包括:Xform(变换节点,用于分组和定位)、Mesh(网格几何体)、Camera(相机)、Light(光源)、Material(材质)、PhysicsScene(物理场景)、RigidBody(刚体)、Collider(碰撞体)等。理解这些Prim类型是后续搭建仿真环境的基础。

经验提醒:在Isaac Sim中,所有物体都必须挂载在某个Xform Prim下才能被正确变换。直接创建Mesh而不加Xform父级,会导致物体无法通过Transform工具移动。建议在创建任何物体时,先创建Xform作为父节点。

3.2 场景层级结构:Stage、Prim与Property的组织逻辑

USD场景采用树状层级结构,从Stage(舞台)根节点开始,向下逐级展开。Stage是场景的顶层容器,相当于一个完整的USD文件。在Isaac Sim中,每个仿真环境对应一个Stage。Stage之下是Prim层级,Prim可以嵌套,形成父子关系。父Prim的变换会传递给子Prim,这是场景组织中最核心的规则。

一个典型的场景层级如下:

/World
  ├── Ground (Xform)
  │     └── GroundPlane (Mesh + Collider)
  ├── Table (Xform)
  │     ├── TableTop (Mesh + Collider)
  │     └── TableLeg (Mesh + Collider)
  ├── Robot (Xform)
  │     ├── Base (Mesh + RigidBody)
  │     └── Arm (Xform)
  │           └── Link1 (Mesh + RigidBody)
  └── Lighting (Xform)
        ├── Sun (Light)
        └── Camera (Camera)

这种层级结构的设计有三大优势:变换继承(移动父节点,子节点跟随移动)、逻辑分组(将相关物体组织在一起,便于统一操作)、路径寻址(通过路径可以精确访问任意Prim)。在Isaac Sim的Stage面板中,你可以直观地看到这棵树,并拖拽调整层级关系。

层级 说明 示例 操作方式
Stage 场景根节点,包含所有内容 当前打开的USD文件 通过 omni.usd.get_context().get_stage() 获取
Prim 场景中的实体节点 /World/Table 通过 stage.GetPrimAtPath() 获取
Property Prim的属性或关系 translate、rotate、material:binding 通过 prim.GetAttribute() 获取

3.3 Prim操作实战:创建、查询、修改与删除

在Isaac Sim中,Prim操作主要通过Omniverse USD API完成。以下是最常用的操作流程,建议在Script Editor中逐步执行验证。

3.3.1 创建Prim

创建Prim的标准流程是:先获取Stage,然后定义路径和类型,最后设置必要的属性。以下代码创建一个位于 /World/Table 下的立方体:

import omni.usd
from pxr import UsdGeom, UsdPhysics, Gf

# 获取当前Stage
stage = omni.usd.get_context().get_stage()

# 创建Xform作为父节点
xform_path = "/World/Table"
xform_prim = UsdGeom.Xform.Define(stage, xform_path)

# 创建立方体Mesh
cube_path = "/World/Table/Cube"
cube_prim = UsdGeom.Cube.Define(stage, cube_path)

# 设置位置属性
cube_prim.AddTranslateOp().Set(Gf.Vec3d(0.0, 0.0, 0.5))

# 添加碰撞体
UsdPhysics.ColliderAPI.Apply(cube_prim.GetPrim())

3.3.2 查询Prim

查询Prim是调试场景的常用操作。你可以通过路径直接获取Prim,也可以遍历子Prim。以下代码演示如何遍历 /World 下的所有子Prim并打印其路径和类型:

world_prim = stage.GetPrimAtPath("/World")
for child in world_prim.GetChildren():
    print(f"路径: {child.GetPath()}, 类型: {child.GetTypeName()}")

3.3.3 修改Prim属性

修改属性是动态调整场景的关键。例如,在仿真过程中移动物体位置:

cube_prim = stage.GetPrimAtPath("/World/Table/Cube")
cube_xform = UsdGeom.Xformable(cube_prim)
# 清除已有的变换操作
cube_xform.ClearXformOpOrder()
# 添加新的平移操作
cube_xform.AddTranslateOp().Set(Gf.Vec3d(1.0, 2.0, 0.5))

3.3.4 删除Prim

删除Prim相对简单,但需要注意:删除后无法通过Undo恢复(在Script Editor中)。建议在删除前先确认路径正确:

stage.RemovePrim("/World/Table/Cube")
风险提示:在仿真运行过程中删除正在被物理引擎引用的Prim,可能导致仿真崩溃或产生未定义行为。建议在仿真暂停或初始化阶段进行Prim删除操作。

3.4 环境搭建基础:从空场景到可仿真环境

搭建一个可用的仿真环境,需要完成四个基础步骤:设置Stage参数、创建地面、添加光照、配置物理场景。以下是一个完整的环境搭建流程,适用于大多数AGV仿真项目。

3.4.1 Stage设置

Stage设置包括单位、重力方向和坐标系。Isaac Sim默认使用米(meters)作为单位,Z轴向上。在创建环境前,建议先确认Stage的元数据:

from pxr import UsdGeom
stage = omni.usd.get_context().get_stage()
# 设置Stage单位为米
UsdGeom.SetStageMetersPerUnit(stage, 1.0)
# 设置Stage轴为Z轴向上
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)

3.4.2 创建地面与光照

地面是AGV仿真中最基础的碰撞面。Isaac Sim提供了快速创建地面的方法,同时需要添加光照以保证视觉可见性:

# 创建地面(带碰撞)
from omni.isaac.core.utils.stage import add_reference_to_stage
from omni.isaac.core.utils.prims import create_prim

# 使用Isaac Sim内置的地面资产
create_prim("/World/Ground", "Xform")
create_prim("/World/Ground/Collider", "Plane", 
            attributes={"size": 100.0, "semantic:type": "ground"})

# 添加光照
create_prim("/World/Lighting/DomeLight", "DomeLight", 
            attributes={"intensity": 1000.0})

3.4.3 配置物理场景

物理场景是仿真能够运行的前提。Isaac Sim默认会创建一个PhysicsScene,但建议显式配置以确保参数正确:

from pxr import UsdPhysics, Gf

# 创建物理场景
physics_scene = UsdPhysics.Scene.Define(stage, "/World/PhysicsScene")
# 设置重力
physics_scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
physics_scene.CreateGravityMagnitudeAttr().Set(9.81)

3.5 案例拆解:搭建一个AGV测试环境

下面通过一个完整的案例,将本章知识串联起来。假设我们需要搭建一个AGV小车测试环境,包含地面、障碍物、目标点和AGV本体(简化为一个立方体)。

场景需求:地面尺寸20m×20m,障碍物位于(3, 2, 0)处,目标点位于(8, 5, 0)处,AGV初始位置在(0, 0, 0.5)。

操作步骤:

  1. 创建Stage并设置单位为米,Z轴向上。
  2. 创建地面:使用Plane Prim,尺寸20×20,添加Collider。
  3. 创建障碍物:使用Cube Prim,尺寸0.5×0.5×1.0,设置位置(3, 2, 0.5),添加Collider和RigidBody(固定质量)。
  4. 创建目标点:使用Sphere Prim,半径0.2,设置位置(8, 5, 0.5),添加视觉材质(绿色)。
  5. 创建AGV:使用Cube Prim,尺寸0.8×0.6×0.4,设置位置(0, 0, 0.5),添加Collider和RigidBody(质量10kg)。
  6. 添加光照和相机,调整视角。

关键代码片段:

# 创建障碍物
obstacle_path = "/World/Obstacle"
UsdGeom.Cube.Define(stage, obstacle_path)
obstacle = stage.GetPrimAtPath(obstacle_path)
obstacle.GetAttribute("size").Set(0.5)
UsdGeom.Xformable(obstacle).AddTranslateOp().Set(Gf.Vec3d(3.0, 2.0, 0.5))
UsdPhysics.ColliderAPI.Apply(obstacle)
UsdPhysics.RigidBodyAPI.Apply(obstacle)

# 创建AGV
agv_path = "/World/AGV"
UsdGeom.Cube.Define(stage, agv_path)
agv = stage.GetPrimAtPath(agv_path)
agv.GetAttribute("size").Set(0.8)
UsdGeom.Xformable(agv).AddTranslateOp().Set(Gf.Vec3d(0.0, 0.0, 0.5))
UsdPhysics.ColliderAPI.Apply(agv)
rb = UsdPhysics.RigidBodyAPI.Apply(agv)
rb.CreateMassAttr(10.0)

结果验证:运行仿真后,AGV在重力作用下落在地面上,障碍物保持静止,目标点可见。通过Stage面板可以查看完整的Prim层级结构。

复盘经验:在搭建环境时,建议先创建所有静态物体(地面、障碍物),再创建动态物体(AGV),最后添加光照和相机。这样可以避免在调整层级时影响物理属性。另外,所有物体的初始位置应确保不与地面重叠,否则物理引擎会强制修正位置,导致视觉跳动。

3.6 常见误区与总结

在USD场景构建中,初学者常犯以下错误:

  • 忽略Xform父节点:直接创建Mesh而不挂载在Xform下,导致无法通过Transform工具操作。
  • 路径错误:USD路径区分大小写,且必须从根节点 / 开始。使用 /World/table 和 /World/Table 是两个不同的Prim。
  • 属性类型不匹配:设置属性时使用了错误的数据类型,例如将float值赋给double属性,会导致赋值失败或精度损失。
  • 忘记添加Collider:物体没有碰撞体,物理仿真中会直接穿透地面。
  • 物理API应用顺序错误:先添加RigidBody再添加Collider,或反之,可能导致物理引擎无法正确识别。