BACK TO ARCHIVE
lookdev light transport usd interoperability pipeline framework dcc mechanics nuances katana foundry usdsuperlayer

在 USD 世界中结合节点与层的优势

08.09.2025 ADUCG RESEARCH

摘要

Katana 的优势在于其程序化节点图架构 ,这种架构能够提供动态且可扩展的工作流——各个工作室对包含数以万计节点的场景早已不陌生。Universal Scene Description(USD) 的基于层的特性,对基于节点的程序化方法提出了挑战。在大规模场景下,当前“每个节点创建一个 USD layer”的程序化方式并不可取,因为修改前面的某个节点,可能会触发整个 USD Stage 的重新组合,以及重新执行生成 USD layer 的函数。在这篇扩展摘要中,我们介绍了通往解决方案的历程:UsdSuperLayer 节点类型。它提供了一种“procedural on-demand”的方法,从而能够高效管理以节点形式表示的数千个 primitives 和庞大的着色图。

Katana 节点图中的一个 USDSuperLayer Node。

1 引言

在讨论解决方案之前,我们需要先从较高层次描述一下 Katana 当前采用的 USD 原生程序化方法。一个 Katana 节点会为程序化场景图生成工具制定一份可供“cook”的配方。每个 Katana 节点都会为零个或多个 Engine 描述这份配方。Engine 是一种 plug-in,它会根据提供的配方生成场景图。在 USD 原生处理引擎系统中,每个 Engine 会产出并可编辑一个单独的 USD layer,这个 layer 被称为 EditLayer。每个 Engine 都可以根据其输入 Engine 生成一个 USD Stage,从而使输入能够基于输入场景图数据修改其 EditLayer。在每个 Engine 处理结束时,我们会构建一个由所有结果 EditLayer 组合而成的 USD Stage。

该图展示了我们的节点如何创建一个或多个 engine,以及它们如何在我们的 USD Processing Engine 中连接起来。

2 需求

首先,和所有优秀项目的开端一样,我们需要先列出最优先的目标与需求。我会在这里列出其中几个最高优先级,大多数需求其实是建立在核心基础之上的工作流需求。

2.1 可定制性

这个工具必须具备极高的可定制性。通过与最终用户讨论他们目前对现有 Lighting 和 Lookdev 工作流所做的修改,我们明显发现每个人的需求都不一样,而最核心的结论是:它必须足够可定制,以适应最终用户的个体需求。这包括能够改变节点布局、改变可见内容,以及能够配合他们自己的 USD Schemas 一起工作。此外,有些行为并不与 schema 上的属性直接对应。例如,变换并不会通过 USD Schemas 被完全揭示。这些行为需要额外选项,例如 xformOp 的名称以及旋转顺序。

除此之外,我们还希望能够提供多种不同的数据查看方式:

  • Tree 视图,可显示场景图层级,并允许在场景中添加、移除和放置新的 USD Prims。
  • 一个 node graph,用于编辑 shading networks。
  • 用于批量编辑的 spreadsheet 视图。
  • 由最终用户设计的自定义视图。

2.2 支持编辑与创建

为了简化用户与节点交互时的体验,明确变更的来源至关重要。比如,必须清楚地表明某个 USD Prim 或属性修改是发生在当前节点,还是来源于节点图的上游。此外,工具还必须基于这些信息明确指示用户当前可执行的操作。例如,虽然无法在创建位置的下游删除 USD Prim,但仍然可以将其 deactivation。

2.3 支持 Katana node graph 的基础能力

我们希望确保当前节点能够支持常见的 Katana 工作流,例如 parameter expressions ,以及基于序列的工作流。最终用户的使用体验应当是熟悉的,并且要与现有节点图保持兼容。

2.4 性能

Katana 擅长以高性能处理庞大的生产场景。这个节点基础架构的一个关键需求,是能够在单一上下文中以交互方式创建和编辑数以万计的 USD Prims,尤其是在材质场景中,我们已经观察到这种规模的 shading graphs。因此,能够以交互方式创建和编辑这些内容,并在“live” render 运行时完成操作,是一项关键要求。

3 我们如何走到这里

Katana 在程序化工具方面拥有坚实基础,提供了大量现成的示例、工具与架构设计。本节将回顾这些现有方法,并解释它们为何不适合我们的特定需求。

3.1 SuperTools

最初的提案是使用我们的 SuperTool 设计。它是一个单独的节点,根据用户交互来管理许多内部节点的创建。但这个方案很早就被否决了,因为正如前文所述,如果创建数以万计的节点,而每个节点又都会引入自己的 USD layer,那么性能将远远不够。

3.2 Custom Traversal

另一种方案与我们当前 MaterialNetwork 节点的工作方式类似。这类节点包含自定义 traversal 行为,用来将成千上万个节点合并为少量更小的 recipe 步骤。这解决了 USD layers 从数千个缩减到少数几个的问题。然而,traversal 行为本身可能会成为瓶颈。编辑输入场景图也是一个挑战:当节点图上游发生变更时,为每个 shader 重新生成自包含节点会非常耗时。更麻烦的是,即便只是修改一个参数值,也需要重新 traversal,并使 recipes 失效。

4 将 USD Layer 用作 Recipe

我们开发了一个新工具——UsdSuperLayer 节点。它受到 SuperTool 的启发,但具备一种全新的按需程序化节点及其行为。与标准节点为 Engines 提供 recipe 以生成场景图不同,UsdSuperLayer 节点把部分“cooking”过程带到了用户界面中。随后,这份 recipe 被存储在一个专用的 USD layer 中,我们将其称为 ContentLayer。

这使我们的数据模型从“在带参数的节点上存储变更与数值”,转变为“把所有这些信息存储在一个 USD layer 中”,也就是 ContentLayer。这样我们就能够直接序列化该 layer 并发送给 Engine,Engine 随后只需执行非常少量的程序化改动,再将该 ContentLayer 的内容传输到 Engine 的 EditLayer 中。存储的大部分数据并不需要程序化操作;创建 lights、shaders 以及设置参数,通常都是一次性过程。不过,我们仍然需要满足用户希望与输入节点图交互的那些场景。

以 ContentLayer 作为 ground truth,使我们能够为用户构建一幅图景,用来指示节点图该位置处场景图的状态。在该节点的输入点,我们会将 ContentLayer 作为 USD session layer 添加到构建好的 USD Stage 上。通过这种方式,我们可以很容易区分某个 USD Prim 上的 opinion 是否是在本地创建的。

一个用于展示传入、本地以及已禁用 USD Prims 的设计示意图。

复制操作同样是现有架构在易用性方面表现不足的一个领域。在 SuperTool 工作流中,无法在节点之间或应用会话之间复制和粘贴条目。使用 Custom Traversal 方法时,我们虽然可以复制和粘贴节点,但如果只想提取某一组节点对应的变更,或提取组合后的数据,就并不容易。有了 USD layer 和 USD Stage 之后,我们既可以复制粘贴“由输入节点图结果与节点自身共同组合出的 USD Prim 结果”,也可以只复制粘贴变更本身。二者都可以通过传输 USD Stage 或 ContentLayer 中的内容来实现。此外,由于我们处理的是序列化后的 USD 数据,这也让我们能够在应用内外进行复制粘贴。

以节点形式保留这种行为,意味着我们保留了那种隐式的非破坏性方法,同时具备了修改输入并让该节点监听传入场景图、再据此更新的能力。用户不仅可以像仅使用 USD Stage 那样,在 edit target 层级上拆分自己的 opinions 和 actions;除此之外,他们还能够组合这些编辑,并且更重要的是,可以重新连线,让这些 layers 以完全程序化的方式作用于任何输入数据。节点消除了大量管理性负担,不再需要为保持非破坏性的、基于 recipe 的方法而去管理众多 layers 的创建与发布。借助我们现有的导出工具集,这些内容可以保存为 USD 数据,并传递到 pipeline 的其他环节中。

UsdSuperLayer 节点的工作机制可以拆分为四个部分:tree view、parameters and properties、USD layer editing actions,以及 coding interface。

5 Tree View

tree view 展示了以 BasePath 上下文为基础的场景图层级。虽然 tree 项目反映的是整个 USD Stage 上下文,但该视图的根是以 BasePath 为索引定位的。这个视图还提供了额外的过滤选项,可排除那些并未在当前 ContentLayer 上被编辑的 USD Prims。此外,我们也可以基于 API 和 USD IsA Schemas 进行过滤。这使得节点能够继承这种基础设计,以聚焦于 USD Stage 中较窄的一部分。例如,我们将提供聚焦于 Lighting(UsdGaffer)和 Look Development(UsdMaterialNetwork)的节点,它们会过滤出与这些工作流相关的 USD Schemas。这个能力还可以进一步定制,甚至能精细到每个节点单独配置,并与项目一起保存。tree view 会显示以 BasePath 之下为根的场景图层级。过滤选项既可用于排除未在当前 ContentLayer 上编辑的 USD Prims,也可基于 API 和 USD IsA Schemas 进行过滤。这一基础设计允许为特定工作流构建专门节点,例如 Lighting(UsdGaffer)和 Look Development(UsdMaterialNetwork),以过滤相关的 USD Schemas。这种定制能力可扩展到单节点级别,并会随项目一起保存。

5.1 参数与属性

一个 USD Prim 可以包含一个或多个 USD Schemas,它们描述该 USD Prim 可用的属性。USD Schemas 也可以被扩展,这意味着我们的工具必须以通用方式处理属性,以支持未知的 USD Schemas。为了增强大家对它能够处理 USD Prim 一切可能用例的信心,我们设计并使用了一套 plug-in 系统。我们从 USD Stage 中读取传入的 USD Prim,因此也会读取附着在其上的所有 USD Schemas。这意味着,无论附带了多少 USD API Schemas,或者未来是否会新增 schema,核心实现都能够为它们创建可编辑参数。

接下来的挑战是如何处理 shaders。一个 shader 的 schema 通常只描述它具有一个 shader identifier,以及一些支撑它的 source。幸运的是,我们可以利用这些信息,并在 SdrRegistry 中查找该 shader identifier。这样我们就能获得它的所有 inputs/outputs,并据此以类似方式生成一个自定义的参数 UI。

一个参数生成示例:这些参数直接根据 USDLuxRectLight 的 USD Schemas 生成,其中一部分用于处理 transforms,另一部分则直接对应 USD Prim 上的属性。

将 USD layer 作为 ground truth 的另一个好处,是我们的参数数量可以大幅减少。这在编辑现有场景图时尤其重要。当节点本身就是 recipe 时,就必须为网络中的每个节点创建参数。修改 shading networks 需要重建其前面整个节点网络,包括每个节点的全部参数和端口。这在计算上代价高昂,并且会在这些节点被填充时造成延迟。将 USD layer 作为 ground truth 后,我们可以按需创建参数。根据需要,node graph 也可以仅作为 interaction layer 来绘制,从而避免保留 recipe,获得一种更轻量的表示方式。

5.2 编辑 layer

如前所述,在我们最初的 USD 原生设计中,计划是向一个 Engine 提供 recipe,再由它生成一个 USD layer。通过 UsdSuperLayer 节点,我们将这种行为改为一种面向 action 的方法,在节点级编辑中保留“USD layer 本身就是 recipe”的特性。我们通过 edit targets 来做到这一点,并保留原生的 USD 方法。这里有一些需要权衡的地方。最主要的是,当 ContentLayer 发生变化时,我们必须使 recipe 失效,因为我们并不知道这次变化的具体性质。然而,直接与 USD layer 通信、而不必经过中间 recipe 的收益是巨大的。这为工作室直接使用 USD 实现自己的自定义工具打开了大门,使这些工具可以迁移到其他 DCCs 或他们已有的工具中。我们也可以通过把许多改动批处理到一个 USD layer 中来保留一定的性能,从而降低重新组合的开销。

6 挑战

尽管我们已经决定开始使用 SdfLayer 作为数据容器,但随着这种节点逐渐脱离 Katana 一些长期存在的基础模式,仍有一些问题需要解决。场景图响应节点图的许多方面之间,其实存在不兼容之处。

6.1 参数表达式与动画参数

在 Katana 中,一种常见工作流是创建 expressions 来定义参数的值。它可以表现为 Python Parameter Expressions,或者 Reference Expressions。一个 expression 可能会将一个参数链接到另一个参数,也可能链接到当前 view flag 所对应的 graph-state。这意味着,这些值可能与最初设置 expression 时相比已经不同。通常,我们会在 traversal 节点图并构建 recipe 时解析这些 expressions。然而,由于我们使用的是“USD layer 作为 recipe”的方式(而且这个 USD layer 只有在节点自身某部分发生变化时才会更新),如果被连接的值在 expression 设置之后发生了变化,那么其中的值就可能已经过时。此外,我们还必须确保:如果复制一个带有 expressions 的 USD Prim,也必须把 expression 一并复制,以保证这符合 Katana 当前工作流的惯例。

我们最初针对这个问题的解决方案最终相对直接,并为解决其他类似的节点图相关问题指明了路径。expression 或 animation curve 会使用自定义的 USD Codeless Schemas 序列化到 USD layer 中。随后,在我们 traversal 场景图时,就可以查询负责该参数的 plug-in,并请求它对一个复制出来的 USD layer 进行更新。接着,这个更新后的 layer 会被发送给 Engine,从而基于当前 graph state 获得最新的值。这样一来,既能同步 Engine 产出的结果场景图,更重要的是,又不会因为修改节点自身上的 layer 而引发级联事件。

我们的一张示意图示例,用于说明参数表达式可能链接到其他节点的几种不同方式。

6.2 按需程序化

有些行为必须在 traversal 时,以程序化方式加入场景图。核心问题在于:某些对 USD Prims 或 USD layer 内容的变更,需要知道节点图上游已经创建的现有数据。例如,要支持对变换进行追加或前置,就必须知道传入的 xformOpOrder

我们通过引入“procedural on-demand”系统解决了这一问题。该系统将所需的程序化 recipe 存储在一个新的自定义 schema 中,用于描述如何运行一个 Sub-Engine。然后,这些 Sub-Engines 就可以被处理,并作为 USD Sublayers 添加到 Engine 的 EditLayer 中。

7 结论

我们相信,“procedural on-demand”工作流在缓解两类方法各自缺点的同时,为最终用户提供了两全其美的方案。关于最终的用户体验和用户界面,仍然还有工作要做,因此它很可能会随着时间继续变化。我们相信,应当利用 USD 及其内部 registries 和 plug-ins,将其作为对场景诸多方面以及其 inputs、outputs、properties 和 behaviours 的通用描述。尽可能让我们的工作具备可扩展性,并以无尽的可定制性为目标,是确保新工具能够集成进现有 pipelines 的关键。我们相信,我们已经创造出一种新颖方式,以直观的方法来处理 USD 与程序化节点图的复杂性,从而能够在统一的 USD 基础之上,以极大规模实现非破坏性的、基于序列的工作流。

Acknowledgments

我们还要感谢参与这一新行为原型开发与生产实现的 Katana engineering team 成员:Adele Peleschka、Chaitanya Kukde、Gabriela Almeida、Jordan Jenkins、Luke Walters、Owen Frankland、Ryoichi Kato、Subin Jeong 和 Tamuka Tagwireyi。

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

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

End of Article