BACK TO ARCHIVE
USD Pipeline

理解 USD Composition Arcs

05.21.2024 ADUCG RESEARCH

Universal Scene Description

USD 的核心在于其强大的组合能力(Composition Arcs)。

在 USD 中,Composition Arcs(组合弧) 是实现这一能力的关键机制。它们是一组操作符,定义了如何将多个图层(layers)和场景描述组合成单一的”组合场景图(composed scenegraph)“。理解 Composition Arcs 对于有效使用 USD 至关重要,因为它们支持非破坏性覆盖、多用户工作流程,以及从小型资产构建大型复杂场景的能力。

什么是 Composition Arcs

Composition Arcs 是 USD 中的一组操作符,用于驱动场景组合过程。在运行时,组合引擎会评估这些写入场景描述中的操作符,并将最终组合的场景图呈现给用户。

USD 提供了 六种主要的 Composition Arcs

  1. Sublayers(子层)
  2. Inherits(继承)
  3. Variants(变体)
  4. References(引用)
  5. Payloads(有效载荷)
  6. Specializes(特化)

每种组合弧都有其特定的用途和行为特征,理解它们的差异是掌握 USD 组合系统的关键。

六种 Composition Arcs 详解

1. Sublayers(子层)

Sublayers 是 USD 中最基本的组合弧。它允许你将一个图层中的场景描述合并到其他图层之上。

工作原理:

  • 根图层及其子层形成一个有序的图层树
  • 组合引擎对这棵树进行深度优先遍历,生成一个有序的”图层栈(layer stack)”
  • 较高层(stronger layers)中的意见(opinions)会覆盖较低层(weaker layers)中的意见

语法示例:

#usda 1.0
(
    subLayers = [
        @shot_layout.usd@,
        @shot_sets.usd@
    ]
)

典型用途:

  • 支持多用户协作工作流(例如:布局艺术家和场景布置艺术家同时工作)
  • 实现非破坏性覆盖
  • 构建图层栈结构
  • 可以通过 Sdf.LayerOffset 支持时间偏移和缩放

实际案例: 在电影制作中,你可能有一个镜头文件 shot.usd,它子层化了 shot_layout.usd(布局)和 shot_sets.usd(场景布置)。这样,布局艺术家和场景布置艺术家可以同时在各自的图层上工作,而不会相互冲突。

2. References(引用)

References 是 USD 中最常用的组合弧之一,用于将另一个图层栈中某个 prim 的组合场景图层次结构引入当前场景。

工作原理:

  • 引用的场景图被封装在”引用 prim”下
  • 可以引用外部文件或内部层次结构
  • 支持路径映射和重命名
  • 可以应用时间偏移(通过 Sdf.LayerOffset

语法示例:

def "World" {
    def "chars" {
        def "DukeCaboom" (
            references = @DukeCaboom.usd@
        ) {
        }
    }
}

典型用途:

  • 从其他文件聚合场景描述数据
  • 构建资产层次结构(从小型资产组装大型资产)
  • 资产重用(同一资产可以被引用多次)
  • 轻量级数据的文件加载

实际案例: 在《玩具总动员 4》的古董商场场景中,单个道具资产可能被引用多次,分布在整个场景中。每次引用都可以有自己的位置和覆盖属性。

3. Payloads(有效载荷)

Payloads 本质上是可选加载的引用,允许客户端在运行时决定是否加载昂贵的场景描述。

工作原理:

  • 行为类似于 References,但可以在运行时加载或卸载
  • 通过 UsdStage::Load()UsdStage::Unload() API 控制
  • 卸载时不会产生内存和运行时成本
  • 加载/卸载是运行时标志,不会修改图层

语法示例:

def "World" {
    def "chars" {
        def "DukeCaboom" (
            payloads = @DukeCaboom.usd@
        ) {
        }
    }
}

典型用途:

  • 加载重型几何数据
  • 按需加载场景元素
  • 优化内存使用
  • 支持时间偏移(通过 Sdf.LayerOffset

最佳实践 - “Lofting”: Pixar 的管线中常用一种技术叫”lofting”(提升),即在资产结构中引入一个中间层,包含即使 payload 未加载也需要的属性或元数据,然后这个中间层使用 payload 弧来使所有昂贵的数据可选加载。

实际案例: 场景布置艺术家在处理古董商场场景时,可能不需要看到所有角色的详细几何体。他们可以选择不加载 DukeCaboom 的 payload,从而节省内存和加载时间。

4. Variants(变体)

Variants 允许你在单个资产中打包一组替代方案,用户可以通过”变体选择”来选择要使用的变体。

工作原理:

  • 创建包含多个”变体”的”变体集(variant sets)”
  • 通过”变体选择”指定要组合的变体
  • 变体选择可以在任何更强的图层中覆盖
  • 不支持时间偏移

语法示例:

def "DukeCaboom" (
    variantSets = ["Cape"]
    variants = {
        string Cape = "WhiteCape"
    }
) {
    variantSet "Cape" = {
        "RedCape" {
            def "RedCape" { }
        }
        "WhiteCape" {
            def "WhiteCape" { }
        }
        "NoCape" {
        }
    }
}

典型用途:

  • 资产的多个变体(如不同颜色、配置)
  • LOD(细节层次)切换
  • 提供用户可选的替代方案
  • 嵌套变体支持复杂的变体组合

实际案例: DukeCaboom 角色可能有一个 “Cape” 变体集,包含 “RedCape”、“WhiteCape” 和 “NoCape” 三个选项。资产默认选择 “WhiteCape”,但在特定镜头中,艺术家可以覆盖这个选择为 “RedCape”。

5. Inherits(继承)

Inherits 允许一个源 prim 上创作的意见影响场景图中所有创作了继承弧指向该源的 prims。

工作原理:

  • 类似于面向对象编程中的类继承概念
  • 通常与 class 说明符一起使用
  • 可以继承外部文件或内部层次结构中的 prims
  • 不支持时间偏移
  • 在强度排序中位于 Sublayers 之后

语法示例:

class "_class_Sphere" {
    double radius = 10
    color3f[] primvars:displayColor = [(1, 0, 0)]
}
 
def Sphere "Sphere1" (
    inherits = </_class_Sphere>
) {
}
 
def Sphere "Sphere2" (
    inherits = </_class_Sphere>
) {
    # 可以覆盖继承的属性
    color3f[] primvars:displayColor = [(0, 1, 0)]
}

典型用途:

  • 对多个(可实例化的)prims 应用编辑而不失去实例化能力
  • 定义可重用的”类”或”模板”
  • 在不增加原型数量的情况下修改实例

实际案例: 如果你有 100 个引用的球体实例,想要改变它们的材质,可以使用 inherit 弧从一个 class prim 继承材质属性,这样所有球体都会更新,同时保持实例化的内存优势。

6. Specializes(特化)

Specializes 类似于 Inherits,但在强度排序中是最弱的,用于提供”模板”或”基线”值。

工作原理:

  • 行为类似 Inherits,但强度排序规则不同
  • Specializes 的意见总是比其他所有弧的意见弱
  • 可以作为默认值或后备值
  • 不支持时间偏移

语法示例:

class "_template_Asset" {
    double size = 1.0
    string material = "default"
}
 
def Xform "Asset1" (
    specializes = </_template_Asset>
) {
    # 任何其他弧的意见都会覆盖 specializes
}

典型用途:

  • 提供模板值或基线值
  • 定义可被任何其他弧覆盖的默认属性
  • 广播 specs(prim specs 或 property specs)到图层栈

与 Inherits 的区别:

  • Inherits:强度高,用于应用必须保留的覆盖
  • Specializes:强度最弱,用于提供可被任何东西覆盖的默认值

LIVRPS 强度排序

USD 使用一个称为 LIVRPS(发音为 “liver-peas”)的强度排序规则来确定当多个图层和组合弧提供冲突的意见时,哪个意见应该胜出。

LIVRPS 代表什么

LIVRPS 是一个助记符,表示组合操作从强到弱的顺序:

  1. L - Local (Sublayers):在活动根图层栈中搜索直接意见
    • 包括 Value Clips(值剪辑),它们比图层上的直接意见弱
  2. I - Inherits:搜索影响路径的继承,递归应用 LIVRP(无 Specializes)评估
  3. V - Variants:搜索影响路径的变体,递归应用 LIVRP 评估
  4. R - References:搜索影响路径的引用,递归应用 LIVRP 评估
  5. P - Payloads:搜索影响路径的有效载荷,递归应用 LIVRP 评估
  6. S - Specializes:搜索影响路径的特化,递归应用完整的 LIVRPS 评估,导致 Specializes 意见总是最后

注意:最近 OpenUSD 引入了一个新的组合弧叫 “relocates”(重定位),现在强度排序助记符变成了 LIVERPS(E 代表 rElocates),但本文仍使用传统的 LIVRPS 术语。

强度排序可视化

最强 ↑
    │
    ├─ Local Opinions (Sublayers)
    │   └─ Value Clips
    ├─ Inherits
    ├─ Variants
    ├─ References
    ├─ Payloads
    └─ Specializes
    │
最弱 ↓
    └─ Schema Fallback Values

嵌套组合弧的解析顺序

重要的是要理解,当解析嵌套的组合弧时:

  • 在最近的祖先父 prim 或 prim 本身上创作的弧/值剪辑元数据获胜
  • “祖先弧”比”直接弧”弱

实际示例

假设我们有以下场景结构:

# shot.usd (Root Layer)
#usda 1.0
(
    subLayers = [@shot_layout.usd@]
)
 
def "World" {
    def "DukeCaboom" (
        references = @DukeCaboom.usd@
    ) {
        # Local opinion - 最强
        double3 xformOp:translate = (10, 0, 0)
    }
}
 
# shot_layout.usd (Sublayer)
over "World" {
    over "DukeCaboom" {
        # Sublayer opinion - 比 reference 强
        double3 xformOp:translate = (5, 0, 0)
    }
}
 
# DukeCaboom.usd (Referenced file)
def "DukeCaboom" {
    # Reference opinion - 最弱
    double3 xformOp:translate = (0, 0, 0)
}

在这个例子中,xformOp:translate 的最终值是 (10, 0, 0),因为:

  1. Root layer 的直接意见最强
  2. Sublayer 的意见次之
  3. Reference 的意见最弱

组合弧的选择指南

根据不同的使用场景,选择合适的组合弧:

按用途选择

需求推荐的组合弧
加载整个文件内容(轻量级,链接到其他文件)Sublayers
加载文件中特定层次结构(轻量级数据)References
加载文件中特定层次结构(重型几何数据)Payloads
在活动图层栈中加载现有子层次结构(带时间偏移)References(内部)
为多个(实例化的)prims 添加覆盖Inherits
提供可被覆盖的基线值Specializes
作为层次结构的变体Variants

按时间偏移能力选择

支持时间偏移/缩放(通过 Sdf.LayerOffset):

  • ✅ Sublayers
  • ✅ References
  • ✅ Payloads

不支持时间偏移:

  • ❌ Inherits
  • ❌ Variants
  • ❌ Specializes

按目标类型选择

文件引用(可以指向外部文件):

  • Sublayers
  • References
  • Payloads

内部引用(只能指向当前图层栈内的层次结构):

  • Inherits
  • Variants
  • Specializes
  • References(也支持内部引用)

实际应用场景和最佳实践

1. 多用户协作工作流

场景: 多个艺术家同时处理同一个镜头

解决方案: 使用 Sublayers

#usda 1.0
(
    subLayers = [
        @shot_animation.usd@,
        @shot_lighting.usd@,
        @shot_fx.usd@,
        @shot_layout.usd@
    ]
)

每个部门可以在各自的图层上工作,通过强度排序自然地处理覆盖关系。

2. 资产聚合和重用

场景: 构建由多个子资产组成的复杂资产

解决方案: 使用 ReferencesPayloads

def "AntiquesMall" {
    def "Props" {
        def "Chair_01" (
            payload = @chair.usd@
        ) {
            double3 xformOp:translate = (0, 0, 0)
        }
        def "Chair_02" (
            payload = @chair.usd@
        ) {
            double3 xformOp:translate = (5, 0, 0)
        }
        # ... 更多引用
    }
}

3. LOD 和资产变体

场景: 一个资产需要多个细节层次或配置

解决方案: 使用 Variants

def "Tree" (
    variantSets = ["LOD", "Season"]
    variants = {
        string LOD = "high"
        string Season = "summer"
    }
) {
    variantSet "LOD" = {
        "high" {
            def "HighPolyGeo" { }
        }
        "medium" {
            def "MediumPolyGeo" { }
        }
        "low" {
            def "LowPolyGeo" { }
        }
    }
    variantSet "Season" = {
        "summer" {
            color3f[] primvars:displayColor = [(0, 1, 0)]
        }
        "autumn" {
            color3f[] primvars:displayColor = [(1, 0.5, 0)]
        }
        "winter" {
            color3f[] primvars:displayColor = [(1, 1, 1)]
        }
    }
}

4. 实例化覆盖

场景: 需要修改大量实例化资产的共同属性

解决方案: 使用 Inherits

class "_class_TreeMaterial" {
    rel material:binding = </Materials/TreeMaterial_Autumn>
}
 
def "Forest" {
    def "Tree_001" (
        instanceable = true
        references = @tree.usd@
        inherits = </_class_TreeMaterial>
    ) { }
    
    def "Tree_002" (
        instanceable = true
        references = @tree.usd@
        inherits = </_class_TreeMaterial>
    ) { }
    
    # ... 数百个实例
}

通过修改 _class_TreeMaterial,可以同时更新所有树的材质,同时保持实例化的内存优势。

5. 默认值和模板

场景: 为资产提供默认配置,但允许完全覆盖

解决方案: 使用 Specializes

class "_template_StandardAsset" {
    string assetInfo:version = "1.0"
    string assetInfo:department = "modeling"
    double size = 1.0
}
 
def "MyAsset" (
    specializes = </_template_StandardAsset>
) {
    # 任何在这里或更强图层中的意见都会覆盖模板
}

组合弧的关键概念

1. 封装(Encapsulation)

当使用 ReferencesPayloadsInheritsVariantsSpecializes 弧时,结果会被”封装”在使用弧的 prim 下。这意味着:

  • 弧目标的内部结构对外部是隐藏的
  • 可以在不影响弧目标的情况下重命名引用 prim
  • 路径映射自动处理

2. 列表编辑(List Editing)

除了 Sublayers 外,所有弧都使用列表编辑操作:

  • Prepend(前置):添加到列表开头
  • Append(追加):添加到列表末尾
  • Delete(删除):从列表中删除
  • Explicit(显式):完全替换列表

3. 层栈 vs 单层

重要:组合弧目标是层栈(layer stacks),而不是单个层。这意味着它们会递归加载层中的所有内容。

4. 祖先数据流动

当弧目标非根 prim 时:

  • 不会接收通常”流动”到层次结构下的父数据
  • 例如:primvars、材质绑定或来自祖先 prims 的变换不会”继承”
  • 看到组合结果

调试和检查组合结构

USD 提供了多种工具来检查和调试组合结构:

1. usdview

使用 usdview 的 Composition 面板可以可视化 prim 的组合弧结构。

2. Python API

from pxr import Usd
 
stage = Usd.Stage.Open("myfile.usd")
prim = stage.GetPrimAtPath("/World/DukeCaboom")
 
# 查询组合弧
query = Usd.PrimCompositionQuery(prim)
for arc in query.GetCompositionArcs():
    print(f"Arc Type: {arc.GetArcType()}")
    print(f"Target Path: {arc.GetTargetPath()}")
    print(f"Introducing Layer: {arc.GetIntroducingLayer()}")

3. usd-inspect 命令

usd-inspect composition /World/DukeCaboom myfile.usd

4. 检查属性来源

prim = stage.GetPrimAtPath("/World/DukeCaboom")
attr = prim.GetAttribute("xformOp:translate")
 
# 获取属性值的来源
stack = attr.GetPropertyStack()
for prop in stack:
    print(f"Layer: {prop.GetLayer()}")
    print(f"Value: {prop.Get()}")

常见陷阱和注意事项

1. 循环依赖

避免创建循环引用:

A references B
B references A  # ❌ 错误!

2. 过度使用 Sublayers

Sublayers 会增加图层栈的深度,过多的 sublayers 会影响性能。考虑使用 References 或 Payloads。

3. Inherits vs Specializes 混淆

  • 需要强覆盖?使用 Inherits
  • 需要弱默认值?使用 Specializes

4. Payload 管理

确保重型数据放在 Payloads 后面,轻量级元数据”lofted”到 payload 外部。

5. 变体性能

过多的变体或深度嵌套的变体可能会影响性能。保持变体结构简单和扁平。

总结

USD 的 Composition Arcs 是构建可扩展、协作友好的 3D 场景描述的强大工具。通过理解六种组合弧及其 LIVRPS 强度排序规则,你可以:

  1. 构建复杂的场景层次结构:从小型资产组装大型环境
  2. 支持多用户协作:不同部门同时工作而不冲突
  3. 实现非破坏性工作流:覆盖而不修改原始数据
  4. 优化性能:通过 Payloads 和实例化管理内存使用
  5. 提供灵活性:通过 Variants 支持多种资产配置

快速参考

组合弧强度时间偏移主要用途
Sublayers最强(L)图层合并、多用户协作
Inherits强(I)类继承、实例覆盖
Variants中(V)资产变体、LOD
References中弱(R)资产聚合、轻量级引用
Payloads弱(P)重型数据、可选加载
Specializes最弱(S)模板值、默认属性

掌握这些概念需要时间和实践,但一旦理解,你将能够充分利用 USD 的强大功能来构建复杂的生产管线。建议通过实际项目和 usdview 工具不断实验,加深对组合系统的理解。


参考资料:

本文采用 Creative Commons BY-NC-ND 4.0 协议进行授权。

BY-NC-ND: 署名-非商业性使用-禁止演绎

End of Article