4. USD 基础与场景构建
好,咱们进入第四章。说实话,很多刚接触 Isaac Sim 的朋友,上来就被 .usd 文件搞懵了。它到底是什么?为什么 NVIDIA 放着好好的 .fbx、.obj 不用,非要搞个 USD?
我当年第一次接触 USD 时,也觉得这东西太抽象了。但后来在搭建一个多机械臂协作产线时,才真正体会到它的威力——没有 USD,那个项目根本做不下去。今天我就把 USD 的核心概念掰开揉碎讲给你听。
4.1 USD 到底是什么?
USD(Universal Scene Description)是皮克斯开源的一套场景描述框架。说白了,它不只是一个文件格式,而是一整套场景数据组织、组合、传递的规范。
你想想看,一个复杂的仿真场景里,可能有机械臂、传送带、工件、传感器、灯光、物理属性……这些东西如果都塞进一个文件里,谁来维护?谁改了什么?版本怎么控制?
USD 的答案很聪明:分层 + 非破坏性覆盖。每个环节只管自己的事,最后通过「合成」拼成完整场景。
4.2 三大核心概念:Prim、Layer、Composition
这三个词你会在 Isaac Sim 里天天见到。我一个个说。
4.2.1 Prim——场景里的最小单元
Prim 就是 USD 世界里的「东西」。一个机械臂是 Prim,一个关节是 Prim,一个材质球也是 Prim。你可以把它理解成面向对象里的「对象实例」。
每个 Prim 都有:
- 路径:比如
/World/robot_arm/shoulder_joint,类似文件系统的路径 - 类型:Xform(变换节点)、Mesh(网格)、Joint(关节)、Camera(相机)等
- 属性:位置、旋转、缩放、颜色、质量……
- 元数据:比如 kind(种类)、purpose(用途,如渲染/代理/碰撞)
# 一个简单的 Prim 定义示例(.usda 格式)
def Xform "World"
{
def Mesh "ground_plane"
{
float3[] extent = [(-10, -0.01, -10), (10, 0, 10)]
int[] faceVertexCounts = [4]
int[] faceVertexIndices = [0, 1, 2, 3]
point3f[] points = [(-10, 0, -10), (10, 0, -10), (10, 0, 10), (-10, 0, 10)]
uniform subdivisionScheme = "none"
}
}
/World,所有静态物体放 /World/static,动态物体放 /World/dynamic。这样后期加物理、加碰撞时,一眼就能找到目标。
4.2.2 Layer——场景的「透明胶片」
Layer 是 USD 最巧妙的设计之一。你可以把它想象成 Photoshop 里的图层:
- 每个 Layer 只记录自己关心的数据
- 多个 Layer 叠在一起,形成最终场景
- 上层 Layer 可以覆盖下层 Layer 的数据
- 每个 Layer 可以独立保存、独立版本控制
举个例子。我在做抓取仿真时,会这样分层:
| 层级 | 内容 | 谁维护 |
|---|---|---|
| Layer 0(底层) | 机械臂基础模型、关节定义 | 供应商/建模师 |
| Layer 1 | 物理属性、碰撞体、质量 | 仿真工程师(我) |
| Layer 2 | 抓取目标、工件位置 | 工艺工程师 |
| Layer 3(顶层) | 灯光、相机、调试辅助线 | 我自己调试用 |
这样做的好处是:供应商更新了机械臂模型,我只需要替换 Layer 0,其他层完全不受影响。我曾经因为没分层,手动改了一个 .usd 文件里的 200 多个关节位置……嗯,那次之后我再也不敢偷懒了。
4.2.3 Composition——怎么把这些层「合」起来
Composition 是 USD 的合成引擎。它定义了 Layer 之间如何叠加、覆盖、引用。常用的合成方式有:
- SubLayers:最简单的叠加,后加载的 Layer 覆盖先加载的
- References:引用另一个 .usd 文件作为子场景,类似编程里的 import
- Payloads:延迟加载,场景打开时不加载细节,需要时才加载——对大场景性能至关重要
- Inherits:类继承机制,子 Prim 继承父 Prim 的属性
- Specializes:更精细的覆盖,优先级高于 Inherits
4.3 在 Isaac Sim 里实操:创建、编辑、保存
理论说完了,咱们上手。打开 Isaac Sim,跟着我做。
4.3.1 创建新场景
- 点击菜单 File → New Stage,创建一个空场景
- 在 Stage 面板(默认在左侧)右键根节点,选择 Add Prim → Xform,命名为
World - 在
World下继续添加:Mesh(地面)、Sphere(工件)、Capsule(障碍物)等 - 用 Transform 工具(快捷键 W/E/R)调整位置、旋转、缩放
# 你也可以直接在 USD Composer 里写代码创建
from pxr import UsdGeom, Sdf
stage = omni.usd.get_context().get_stage()
world_prim = stage.DefinePrim("/World", "Xform")
sphere = stage.DefinePrim("/World/sphere", "Sphere")
sphere.GetAttribute("radius").Set(0.1)
4.3.2 编辑场景属性
选中任意 Prim,在 Property 面板(右侧)可以修改:
- Transform:位置、旋转、缩放、枢轴点
- Visibility:可见性(Invisible 用于隐藏辅助物体)
- Purpose:用途(default 渲染用、proxy 代理用、guide 辅助用)
- Physics:物理属性(需要先添加 Physics 组件)
Ctrl 拖动物体,可以复制出一个新物体。我经常用这个功能快速摆满一排工件。
4.3.3 保存场景——分层保存是关键
保存时,Isaac Sim 默认会把所有内容存到一个 .usd 文件里。但我不建议这么做。
我的标准流程是:
- 先保存基础模型为
robot_base.usd(只包含几何和关节) - 再保存物理配置为
robot_physics.usd(引用 robot_base.usd,添加碰撞和物理属性) - 最后保存完整场景为
grasp_scene.usd(引用前两个,加上工件、灯光、相机)
# 在 Isaac Sim 里通过 Python 分层保存
from pxr import Usd, Sdf
# 保存基础层
stage_base = Usd.Stage.CreateNew("robot_base.usd")
# ... 添加模型数据 ...
stage_base.Save()
# 保存物理层(引用基础层)
stage_physics = Usd.Stage.CreateNew("robot_physics.usd")
stage_physics.GetRootLayer().subLayerPaths.append("./robot_base.usd")
# ... 添加物理属性 ...
stage_physics.Save()
4.4 知识体系总览
下面这张图,是我自己总结的 USD 在 Isaac Sim 里的知识结构。你保存下来,后面每学一章都可以回来对照。
4.5 本章小结
USD 的三个核心概念,你记住了吗?
- Prim 是场景里的每个物体——路径、类型、属性,缺一不可
- Layer 是场景的透明胶片——分层保存,互不干扰
- Composition 是胶水——把各层按规则合成最终场景
我个人觉得,USD 最难的不是概念本身,而是「分层思维」的转变。很多工程师习惯把所有东西塞一个文件里,改起来心惊胆战。但一旦你习惯了分层,你会发现:场景不再是静态的「文件」,而是动态的「数据流」。