2. USD基础与场景构建

好,我们正式开始接触Isaac Sim的核心——USD。说实话,我第一次接触USD时也觉得有点绕,毕竟它跟传统的3D文件格式差别挺大的。但一旦你理解了它的设计哲学,你会发现它其实非常优雅,特别适合做大型工业级仿真。

USD的全称是Universal Scene Description,皮克斯搞出来的东西。它不是为了替代FBX或OBJ,而是为了解决「如何组织和管理一个包含成千上万资产的复杂3D场景」这个问题。在Isaac Sim里,你看到的每一个物体、每一束光、每一段材质,本质上都是USD节点。

USD文件结构:层与引用

USD最核心的概念是「层」(Layer)。你可以把层想象成Photoshop里的图层——每一层只记录自己修改的部分,最终合成一个完整的场景。

关键理解:USD文件不是「一个文件描述一个完整场景」,而是「多个文件通过层叠和引用组合成一个场景」。这种设计让团队协作变得非常方便——你改你的机器人,我改我的环境,互不干扰。

常见的USD文件格式有三种:

格式特点我常用的场景
.usd纯文本,可读性强调试阶段,方便看结构
.usda也是文本,但语法更严格写教学示例时用
.usdc二进制,体积小加载快最终交付的资产

我个人习惯在项目初期全部用.usd文本格式,方便排查问题。等场景稳定了,再转成.usdc做性能优化。

举个例子,一个典型的机器人场景文件结构可能是这样的:

# 根场景文件 robot_scene.usd
# 它引用了两个子层
(
    subLayers = [
        @./environment.usd@,
        @./robot.usd@
    ]
)

# 环境层 environment.usd
def Xform "GroundPlane"
{
    ...
}

# 机器人层 robot.usd  
def Xform "RobotArm"
{
    ...
}

你看,根文件只负责「组装」,具体的几何体、材质、关节定义都分散在子层里。这种结构在团队协作时特别有用——我见过不少团队因为用传统格式,每次合并场景都像在打仗。

Prim与Attribute:USD的基本单元

USD里的每个对象都叫Prim(Primitive的缩写)。一个Prim可以是一个立方体、一个关节、一个光源,甚至是一个空的分组节点。每个Prim身上挂着一堆Attribute,用来描述它的属性——位置、旋转、颜色、质量等等。

一个小技巧:在Isaac Sim的Stage窗口中,你看到的每一行都是一个Prim。右键点击任意Prim,选择"Inspect",就能看到它所有的Attribute。我调试时经常用这个功能,比翻文档快多了。

Prim的类型有很多,但最常用的就这几个:

  • Xform:变换节点,用来做分组或定位
  • Mesh:网格几何体,比如机器人的外壳
  • Material:材质定义
  • PhysicsBody:物理属性,比如质量、碰撞形状
  • Joint:关节,连接两个刚体

Attribute则分为「内置」和「自定义」两种。内置的比如translate、rotate、scale,每个Prim都有。自定义的你可以随便加,比如给一个机器人手臂加一个maxPayload属性,用来记录它的最大负载。

我曾经在一个项目中,需要给几百个零件加上「供应商编号」和「维护周期」信息。用自定义Attribute,直接在USD里写:

def Xform "GripperFinger"
{
    custom double custom:maintenanceInterval = 1000.0
    custom string custom:supplier = "SMC"
    
    def Mesh "FingerMesh"
    {
        ...
    }
}

这样信息就跟模型绑在一起了,不会丢失。比用Excel表格管理靠谱得多。

创建与编辑场景:从零搭一个仿真环境

在Isaac Sim里创建场景,我推荐两种方式:

  1. 可视化操作:直接用Create菜单添加Prim,然后在属性面板里调参数。适合快速原型。
  2. Python脚本:用USD API批量创建和修改。适合需要精确控制或重复操作的场景。

举个例子,我想创建一个带物理属性的桌子:

# Python脚本方式
from pxr import Usd, UsdGeom, Gf

stage = Usd.Stage.CreateNew("table.usd")

# 创建桌子腿
leg = UsdGeom.Cylinder.Define(stage, "/Table/Leg1")
leg.AddTranslateOp().Set(Gf.Vec3d(0.5, 0, 0.5))
leg.AddScaleOp().Set(Gf.Vec3d(0.05, 0.5, 0.05))

# 创建桌面
top = UsdGeom.Cube.Define(stage, "/Table/Top")
top.AddTranslateOp().Set(Gf.Vec3d(0, 0.5, 0))
top.AddScaleOp().Set(Gf.Vec3d(1.2, 0.02, 0.8))

stage.Save()

注意:USD的坐标轴是Y轴向上的,跟ROS的Z轴向上不同。如果你从ROS导入模型,记得做一次坐标变换。我刚开始做机器人仿真时就踩过这个坑,机器人直接「躺」在地上了。

编辑场景时,我最常用的操作是「Override」。比如你引用了一个现成的仓库模型,但想把它换个颜色。你不需要修改原始文件,只需要在引用层上添加一个Override:

# 在场景文件中
over "Warehouse/Wall"
{
    over "Material"
    {
        color3f inputs:diffuseColor = (0.8, 0.2, 0.2)  # 改成红色
    }
}

原始文件不变,你的修改独立保存。这就是USD「非破坏性编辑」的精髓。

导入与导出资产:格式转换与最佳实践

Isaac Sim支持导入的格式不少,但说实话,真正用得顺手的就那几个:

格式导入质量我的建议
URDF高(保留关节和物理属性)机器人模型首选
STEP/IGES中(只保留几何)需要手动添加物理属性
FBX/OBJ中低(材质可能丢失)紧急情况才用
glTF高(材质保留好)静态环境模型推荐

导入时,有几个坑我反复踩过:

  • 单位问题:USD默认单位是厘米,但很多CAD软件用毫米。导入后模型可能变得巨大或微小。记得在导入设置里统一单位。
  • 材质丢失:FBX和OBJ的材质路径经常断掉。我习惯在导入后手动检查一下Material面板。
  • 物理属性:URDF导入时物理属性保留得最好,但碰撞形状有时需要手动调整。特别是复杂的曲面,建议用简单的盒体或球体替代,能大幅提升仿真性能。

导出时,我一般只导出.usd或.usdc格式。如果你需要给其他软件用,可以转成glTF或FBX。但要注意,USD的一些高级特性(比如层、引用、自定义属性)在导出时会丢失。所以我的建议是:在Isaac Sim内部始终用USD,只在最终交付时做格式转换。

一个经验:我曾经接手一个项目,对方给了我一堆FBX文件,每个文件几十兆,场景里几百个零件。导入Isaac Sim后,Stage窗口里密密麻麻全是Prim,根本没法管理。后来我花了两天时间,把所有零件按功能分组,重新组织成USD的层结构,才让场景变得可维护。从那以后,我坚持在项目一开始就用USD组织好资产结构。

本章知识结构

下面这张图概括了USD场景构建的核心逻辑,从文件结构到最终的可编辑场景:

USD场景构建核心逻辑 USD文件结构 层(Layer)与引用 格式:usd/usda/usdc Prim:场景基本单元 Attribute:属性描述 内置 vs 自定义 可视化创建 vs Python脚本 非破坏性编辑:Override与层叠 导入(URDF/STEP/FBX/glTF)→ 导出(USD/glTF/FBX) 核心原则:用USD组织资产,用层实现协作,用Override保持非破坏性 导入时注意单位与物理属性,导出时保留USD原生格式

嗯,这一章的内容就到这里。USD的基础打牢了,后面搭建复杂场景时你会觉得非常顺手。记住:好的资产结构,是高效仿真的第一步。