概述本指南的目标是为艺术家和管道提供构建合理且有特色的USD资产的良好起点。它不一定试图提供严格的规范，而是定义一些围绕“最小可行”USD资产构建工具和工作流程的术语和期望。本文件还希望通过提供开始使用USD工作的良好基础来加速对USD资产的一些直觉，而无需预先进行广泛的USD研究或知识。没有什么可以取代体验，但我们希望加速您的体验。许多工作室、艺术家和作品都希望并需要灵活地构建其资产。提供一些艺术家、技术总监、经理、协调员和开发人员在谈论USD资产时可以使用的核心概念非常重要。对于任何USD管道中的任何资产结构，以下概念和描述应该足够常见。Models模型是USD资产。已发布的模型使用零部件或装配体模型类型或基于这些类型的类型。Component组件模型是可发布的资产，将其几何形状保留在有效负载后面；它们通常是独立的（除了从全球图书馆引用的material之外）。Assembly装配模型是引用零部件或其他装配资产的可发布资产。生成自己的组件的程序集应该负载这些组件并确保正确的模型层次结构。组类型仅用于组织程序集和维护模型层次结构。所有已发布的组件和装配模型都应继承自类基元。其中一些术语可能在管道和工作流程中具有现有含义。这可能会让艺术家和开发者的讨论和决策变得困难。然而，在不同的工具和工作室中一致地使用这些术语将加快每个参与者的学习曲线。什么是USD资产？资产，正如我们在行业中经常使用的术语，是一种以USD为单位的模型。资产一词在不同的工作室/环境/工具中通常意味着不同的事物；例如，几何文件、相关文件的压缩包、动画缓存、纹理贴图和shaders通常都称为资产；甚至USD也使用“资产”作为指向外部非USD文件的属性类型。资产的这些非USD部分可以与USD数据一起存储和版本化，尽管情况可能并非总是如此，但在定义或引用USD模型时，它有助于考虑资产的所有部分。模型不仅仅指几何图形，这不仅仅是 USD/Pixar 的惯例。Softimage XSI 有模型，具有类似的概念，即模型包含对资产做出贡献的所有内容。USD的资产结构通常包含两个考虑因素：原始层次结构和组合/文件组织。原始层次结构关于模型层次结构和组合有一些规则，但资产原始层次结构的具体细节是相当开放的。基于组件的类型的祖先必须全部具有基于组或基于组件的类型才能成为正确的模型。诸如material:binding必须以引用基元范围内的基元为目标的关系，否则目标将被清除。关于组织原语的一些其他建议：使用范围来组织基元，以及应避免转换的地方。Xforms 的开销更大，使用 Scopes 可以减少对子 prim 的意外转换。较短的名称可以帮助减少检查原始路径的痛苦，但长度、大小写选择等......取决于个人需求。在 Scope 原语上设置目的更加清晰，因为您不必跟踪各个 gprim 目的。当 Hydra 以USD进行现场工作时，这也更容易消化。Composition和文件组织磁盘上USD资产的具体实施细节通常由工作室现有的基础设施驱动。无论是工作室还是个人，在构建资产时都要考虑以下几点：通常按部门/工作/贡献/内容划分层级有效负载需要外部文件，但除此之外，层分离主要是根据需要组织工作将图层存储为 .usd 的子目录/图层（使用相对路径引用）有助于提高可移植性组件的各个元素层通常被引用或子层在一起。引用有助于保持意见强度的可预测性，因为每个贡献都是 LIVRPS 中的“R”合理的USD资产的最低限度是 asset.usd 和 Payload.usd强烈建议对“asset.usd”使用“.usd”扩展名，以避免在资产层在ascii 和二进制之间切换时必须重新创作参考路径。将其传递到各个层也可能很有用。使用子文件夹组织每个资源，尤其是当存在各个图层引用的其他文件或子图层在一起时。即“model”、“lgt”等...在图层文件夹中，使用文件夹组织USD文件引用的非USD数据会很有帮助即“maps”、“vol”等……结构的版本控制USD资产的结构应该灵活，以适应不同的需求或优先事项。利用基元或文件中的元数据，可以编写系统来了解如何灵活地编辑或贡献资产。引用模型可能不需要了解太多，这更多的是帮助艺术家或工具编辑资产。USD 唯一专门用于帮助版本控制的部分是资产解析器插件。有关编写 AR 插件的其他资源，请参阅更多详细信息。Component组件是USD管道中发布的最基本的资产。一个合理的、基本的组件具有以下特征：模型根 prim 上的组件类型有效负载使几何图形可延迟独立/便携式，不引用外部几何或模型从全球图书馆引用的material是一个常见的例外。从至少一个类 prim 继承，允许将上下文特定的覆盖广播到继承（或“订阅”）同一类的每个资产。根处的变换（“Xformable”）prim，被设置为资源根文件的defaultPrim。组件模型可能有也可能没有 DCC 特定的装备、几何变体等。非USD数据（即装备）通常与资产的核心USD层一起发布，并且通常具有指向这些 sidecar 文件的元数据。组件内的原语是通过范围来组织的。这有助于避免意外转换不是组件根或子组件的基元。“geo”和“mtl”等较短的名称有助于使调查原始路径变得更容易。然而，总体组织比具体名称更重要。简单组件/apple (xform) ← Component Kind /geo (scope) /mtl (scope) /\_\_class\_\_ (class) /apple (class) ← Inherited by /<component>Scopes一个组件至少应该有两个范围：几何形状和material。可能会出于特定目的添加其他范围，但可以合理地假设所有范围都将从这些范围开始。Geometry地理范围和图层表示场景中的可见几何图形。任何下游贡献者都将在资产的现有几何结构上叠加他们的工作。Materialsmtl范围包含组件使用的material和shaders。这些可以在资产中定义，从全局material库引用，或两者的某种组合。请记住，材质层几乎总是对模型的几何形状有意见；至少材质绑定，但诸如新的 UVsets或shaders信号的primvars之类的意见也是可能的。Purpose显示目的可用于为 GL 查看器或其他用途策划轻量级表示。如果存在，建议在使用显示目的时在“geo”下命名范围。无需标记每个原语的用途；标记父范围就足够了。不仅每次启动的目的管理都很困难，而且似乎还会导致九头蛇的不稳定。简单组件 - 有目的范围/apple (xform) ← Component Kind /geo (scope) /proxy (scope) ← Purpose: proxy★ /render (scope) ← Purpose: render★ /mtl (scope) /\_\_class\_\_ (class) /apple (class) ← Inherited by /<component> 关于组件场景图结构的其他一些注意事项：在类基元下组织类基元不是必需的，但它可以使基元层次结构中的工作变得更容易材质可以从外部材质库引用，但使用组件定义的材质的任何纹理贴图都应与组件一起存在。material可能会以自己的方式变重（原始计数），因此有助于将它们保持在有效载荷后面。如果需要在卸载有效负载的情况下调整material，在这些情况下，资产可以将material移至有效负载之外。变体可用于同一资源的 LOD 或几何体变体。虽然变体集非常灵活，但它有助于定义至少一两个常见/标准变体集（即几何形状和material）。根 prim 上的资产信息元数据可以存储有关资产的更多有用信息希望基本的组件结构能够为将来的专业组件结构提供良好的基础。子组件Subcomponents子组件是存在于组件模型中的 Xformable prims，并且有意以USD形式提供用于转换。建议将子组件放置在枢轴所在的位置，而不是依赖枢轴。这可以避免需要单一类型的 xformOpOrder 约定。子组件可以嵌套，并且在适用的情况下，每个子组件都可以镜像简单组件的整体结构。为了提高效率，每个子组件内的几何形状和material范围可以被引用到位，并使其可实例化。这对于仅使用刚性变换并严重依赖场景图实例的环境道具可能最有用。关于嵌套子组件的一个考虑因素：当放大许多实例的较大场景时，如果您的子组件很浅并且没有嵌套，则使用点实例器表示它们的运动可能会更容易，因为每个子组件都可以是原型。几何子集GeomSubsetsGeomSubsets 非常有用，可以组合网格原点并将其定义为 GeomSubsets。建议使用它们，但是 GeomSubsets 不能像单个网格 prim 那样被激活/隐藏，也不能被转换。如果您有需要调整可见性/激活或调整变换的变体，则需要使用网格prims。Assembly装配体是一种已发布的资产，它将其他已发布的零部件或装配体模型聚合在一起。用于对模型进行分组和组织的中间 xform 使用组类型。程序集要么直接实例化对其他模型的引用，要么将这些引用作为原型提供给 PointInstancers。当有效负载弧已经存在于组件模型中时，它们不会提供任何好处；事实上，它们会使装载/卸载阶段变得复杂，并在以USD工作时增加额外的开销。组装简单/appleBowl (xform) ← Assembly Kind /apples (xform) ← Group Kind /apple1 (reference to apple component)★ /apple2 (reference to apple component)★ /apple3 (reference to apple component)★ /apple4 (reference to apple component)★ /apple5 (reference to apple component)★ /bowl (reference to bowl component)★ /\_\_class\_\_ (class) /appleBowl (class) ← Inherited by /<assembly> 组件可以覆盖各个组件上的材质，以更好地将各种资产集成在一起。这些案例可以将这些装配级material存储在“mtl”范围中，如下所示：组装简单 - 使用material/appleBowl (xform) ← Assembly Kind /apples (xform) ← Group Kind /apple1 (reference to apple component) /apple2 (reference to apple component) /apple3 (reference to apple component) /apple4 (reference to apple component) /apple5 (reference to apple component) /bowl (reference to bowl component) /mtl (scope)★ /\_\_class\_\_ (class) /appleBowl (class) ← Inherited by /<assembly> 虽然装配体通常仅包含对其他模型的引用，但生产通常会提出独特的要求。对于装配体需要生成新几何体的情况，将其发布到其自己的零部件模型并引用回装配体是很尴尬或麻烦的。在这些情况下，新的几何图形应存储在有效负载后面并具有组件类型。组装简单 - 采用material和新几何形状/appleBowl (xform) ← Assembly Kind /apples (xform) ← Group Kind /apple1 (reference to apple component) /apple2 (reference to apple component) /apple3 (reference to apple component) /apple4 (reference to apple component) /apple5 (reference to apple component) /bowl (reference to bowl component) /geo (scope) ← Component Kind, Payload★ /mtl (scope) /\_\_class\_\_ (class) /appleBowl (class) ← Inherited by /<assembly> 组Group建议不要直接在已发布资产的根上使用种类组。组仅用于帮助装配模型遵守模型层次结构规则。细节DetailsInstancing实例化资产的最简单方法是在引用到场景时使其可实例化，或者使用 PointInstancer 多次分散模型。在这两种情况下，模型的根基元可用于转换或编辑资源，但当模型仍为实例化时，所有子基元均不可直接编辑。引用的组件通常可在程序集中实例化。PointInstancers 在这两个组件中都很有用，而不仅仅是大型装配体。以下是树的组件示例：程序集通常将其引用的组件标记为可实例化，尽管这并不总是必需的。这是一个建模注释，但组件模型制作者应该将点实例化器和可实例化基元视为建模者技巧包中的工具。在某些情况下，简单地组合成一个大网格最终可能会更容易/更便宜，但实例化不应仅被视为场景布局。您可能会发现拥有螺栓、叶子或小块食物使用实例很有用。变体VariantsUSD变体集提供了很大的灵活性，这可能会让一开始难以决定要做什么。建议USD资产一开始有两种变体：一种是几何变化，另一种是material。组件可以利用变体集来描述不同的几何形状和/或material组合。组件也可以使用变体来存储不同的布局、材质覆盖等……但为了简单起见，仅通过几何体和/或材质变体集即可获得大量效果。重要的是模型的变体集可以在资产的根 prim 上访问。即使组件被标记为可实例化，或者负载已卸载，这也允许用户进行变体选择。变体集可能存在于较低的基元中，但通常它们应该可以在模型的根基元上访问以供用户设置。每个变体单独的文件Separate Files per Variant变体可以存储在单独的文件中，也可以全部存在于单个层中。将变体保留在一个文件中可以简化文件管理并减少 USD 必须跟踪的层数。另一方面，对于某些工具或管道来说，每个变体的层可能更容易管理。将变体作为层进行管理的能力取决于明确定义的变体集，因为强制执行变体集可以任意更改磁盘上文件的管道将具有挑战性。除非有理由强制执行其中之一，否则具有打包和解包变体的灵活性将是资产结构可以采用的一种选择。后备变体选择Fallback Variant Selections管道功能，允许在没有编写变体选择的情况下自动选择变体。这使得艺术家可以自动根据自己的需求定制不同的作品。有关更多信息和示例，请参阅Colin Kennedy 的 USD Cookbook 。除非有理由强制执行其中之一，否则具有打包和解包变体的灵活性将是资产结构可以采用的一种选择。仅变化模型Variation-Only Models对于某些管道或作品来说，变体可能是一个不寻常的概念。如果几何/模型变体难以投标/管理等……发布“uber”组件可能会很有用，其变体仅引用单独发布的组件模型。Payloads重要的是，将重型几何结构和大量原件保留在有效载荷后面。组件应该有自己的Payloads，而程序集不需要Payloads，因为引用的组件已经有自己的Payloads。一些以镜头为中心的贡献（例如 FX）可能会引入许多新的几何体。这些艺术家和部门需要确保这些新贡献是可卸载的，并且保留模型层次结构，即使它们是在一个镜头中创作的。嵌套的有效负载会增加性能问题，并增加工具的复杂性。将组件定义为带有有效负载的可发布资产有助于避免嵌套有效负载，并提供一些关于打开卸载场景时可能发生的情况的期望。Alembic采用USD的管道往往拥有大量围绕 Alembic 的现有资产和工具。在早期采用期间，您可以直接加载或引用 Alembic 文件，而无需立即将数据转换为 .usdc。与 Alembic 相比，使用 USDC 有优势，但即使某些数据存储为 Alembic，也应该可以构建功能性资产结构。请参阅Alembic USD 插件了解更多详细信息。继承和类Inherits and Classes继承类 prims 是建立USD资产以实现未来灵活性的一种方法。有很多继承方法可用于构建强大的USD资产，但建议模型从一种继承开始。通过继承弧进行编辑的示例可以是变体选择、材质覆盖，甚至新基元。通过继承进行广播覆盖是更改场景图实例的一种方法。从一个类继承很有用，但对于模型从其他类 prim 继承也可能很有价值。特别是，用于组织资产的组或子组可能是其他类 prim 继承的良好候选者。专业化的行为与继承类似，但在定义资产时，继承的意见强度通常更适合传播压倒性意见。绘图模式Draw ModeUSD模型可以使用绘制模式提高视口性能；您甚至可以将绘制模式与卸载的有效负载结合使用。有 5 个可能的选项：origin - 绘制替换的 prim 的模型空间基向量。bounds - 绘制替换的 prim 的模型空间边界框。cards - 绘制纹理四边形作为替换 prim 的占位符。default - 正常绘制USD子树的明确意见。inherited - 服从父级。有关更多详细信息，请参阅UsdGeomModelAPI - 绘制模式。附加层/贡献者合并其他部门或学科工作的模型可以遵循material模式并将其层引用到资产的根文件层中。定义对资产的任何独特/新贡献的范围也可能会有所帮助。例如，对于篝火来说，它具有基于资产的照明和体积效果：/campfire (xform) ← Component Kind /fx (scope) /geo (scope) /lgt (scope) /mtl (scope) /__class__ (class) /campfire (class) ← Inherited by /<component> 文件和组成由资产命名并存储在根资产层中的目录也按照资产命名。一些工作室发现他们喜欢根据层是 ascii 还是二进制来使用扩展名，因为它消除了歧义并提供了用户/艺术家的期望文件的“重量”。使用 .usd 的优点，特别是对于根文件来说，是可以在 ascii 和二进制之间进行更改，而不会破坏引用。基本文件夹/campfire campfire.usd payload.usd geo.usd mtl.usd 营火.usd#usda 1.0 ( defaultPrim = "campfire" upAxis = "Y" ) class "__class__" { class "campfire" { } } def Xform "campfire" ( assetInfo = { asset identifier = @./campfire.usd@ string name = "campfire" } prepend inherits = </__class__/campfire> prepend payload = @./payload.usd@ ) 对资产的所有其他层贡献应该是板条箱文件（二进制 .usd 或 .usdc），并以其角色/贡献命名，这些角色/贡献被引用到“有效负载”中。这些额外层的顺序取决于它们的功能以及它们在工作流程时间轴中的位置。一般来说，这些层是相互构建的，以便后来的学科/贡献者可以使用和/或覆盖现有的工作。这就是为什么几何位于底部，然后是material。这就是为什么“照明”通常是为USD资产/镜头做出贡献的最后一层之一。TODO：各个层何时应在资产结构中进行子分层和引用？将相关的非USD文件放置在资产的子目录中可能会有所帮助；每个目录都会描述内容：/campfire campfire.usd geo.usd--> /hda <-- --> /maps <-- --> /maya <-- mtl.usd payload.usd 任何不进入该“类型”文件的目录的相关文件都可以根据使用这些文件的层按文件夹进行组织。/campfire campfire.usd --> fx.usdc <-- --> /fx <-- geo.usd --> lgt.usdc <-- --> /lgt <-- /maps mtl.usd /vdb 可以将缩略图等其他有用的内容添加到资产中，如下所示：/campfire campfire.usd fx.usd /fx geo.usd lgt.usd /lgt /maps mtl.usd --> thumbnail.jpg <-- --> README.md <-- /vdb materialUSD 资产必须封装所有几何图形和材质，以便在管道或工作流程中有效引用。材质本身可以从某个全局库中引用，但它们的引用点必须位于资源的根基元下方。外观开发可能发生在组件或装配体中。为了具体化实例，特别是在较大的装配中，继承的类 prims 可以将材质绑定广播到相关模型。TODO：shaders网络有哪些注意事项？人们如何针对多个渲染委托？仅限 MaterialX？人群和 UsdSkelTODO：围绕人群和USD资产结构有哪些考虑？UsdSkel 如何影响结构化资产？BG角色的结构与“英雄”模型不同吗？是否有关于 Value Clips 的考虑因素？物理或其他模式/目的对于某些用例，可能需要在模型中描述物理现象。如果刚体模拟是唯一的要求，则可以为实例提供物理驱动的交互。如果物理需求不需要散布模型的 gprim，则为物理基元设置专用范围可能会很有用。游戏本文档的大部分内容旨在构建动画和视觉特效的USD资产。本节将概述实时需求需要不同方法的地方。TODO：双向性同步编辑LOD细节层次游戏 LOD 通常具有基于摄像机的组件；在没有某种原始适配器的情况下，以USD令人满意地实现这一点可能具有挑战性，因为变体无法动画化。是否有任何游戏引擎按原样使用USD并与之交互，或者它们都转换为其内部格式？相关工作流程和注意事项专栏Two cases: archival and kitbashingUSD and Digital Backlots - Eliot SmyrlASWF - WGUSD PresentationSiggraph 2021 TalkDigital Backlots and Libraries are Hard - Davide Pesare评论和变体是否会创建特定于评论的变体？自定义模式什么时候需要它们？具体 vs ApiSchema？命名Prims 通常按照 ProperCase 约定命名，并且可能带有数字后缀。示例：World或RedMaterial_1属性和关系通常按照驼峰命名法命名。例子：curveWidth围绕不同惯例的讨论/建议（由工作室和艺术家）公司/工作室/域特定属性的命名空间盲数据处理未知模式或未定义模式存储在本地格式和USD格式之间转换的 DCC 之间的交换过程中不会丢失它们也许 DCC 特定模式需要考虑一些事情例如，Houdini 有一些 ApiSchema 在 Solaris 之外没有任何功能。因此，除非它们实际上从 USD 文件中删除，否则数据应该持续存在，但保持休眠状态，直到带回 Houdini。即使未检测到插件，架构也应以USD形式保留，因此这一问题很大程度上与USD和 DCC 原生格式之间的转换有关如果在模式不存在时管道可能希望具有替代行为，则还可以定义后备 Prim 类型。结构示例资产的表示，可能在实际制作中使用。不一定会遵循本文档的所有建议，但通常会实施最重要的建议，并且研究起来很有用且有趣。AnimalLogic 的 ALabhttps://animallogic.com/usd-alab/https://usd-alab.s3.amazonaws.com/documentation.html蓝天工作室Apples (usd-interest)OpenUSDOpenUSD 下载Kitchen SetCity SetUsdSkel Examples本文来自于：https://github.com/usd-wg/assets/blob/main/docs/asset-structure-guidelines.md
Houdini终于从19.0版开始支持MaterialX（1.38版）：在：https://www.sidefx.com/docs/houdini/solaris/materialx所以我想写一些关于MaterialX的东西。目前的Houdini虽然支持导入MaterialX，不支持导出。也许它将来会支持出口。否则无法使用。看H19.0.440的更新MaterialX vops 有一个 RMB > Save > MaterialX... 菜单项，用于将着色器网络保存到 .mtlx 文件。您现在可以导出到 MaterialX。此外，大约有 148 个由 MaterialX 准备的标准节点（在官方 MaterialX 中内联或 OSL 中编写）作为 VOP 实现。当我尝试放置它时...... 看起来 MaterialX 中作为标准提供的大多数节点都是用 VOP 实现的，除了 Lama 节点。 可用的节点不多。有吸引力的节点也很少。 但是你要注意什么？ 此 MaterialX 标准曲面的规格在https://autodesk.github.io/standard-surface/中有详细说明。 Karma 的 MaterialX 标准曲面： Arnold 的 aiStandardSurace 参数： 参数完全相同。 换句话说，Karma 和 Arnold 现在可以使用通用的 Uber 着色器。standard_surface此外，AMD 的 GPUOpen 站点 ( https://matlib.gpuopen.com/main/materials/all ) 发布了大量的 MaterialX 文件。什么是 MaterialX？什么是 MaterialX？我认为有些人会成为这样的人。这里有一些我想介绍的网站。MaterialX官网https://www.materialx.org/MaterialX 的概述、规格和API文档都写在这里。我已经在我的网站上发布了规范的日文翻译，所以如果您有兴趣，请访问。除了MaterialX github https://github.com/materialx/MaterialX源代码，您还可以下载使用下面介绍的 MaterialXViewer。Autodesk 的 github https://github.com/autodesk-forks/MaterialX如果您在 Arnold 中寻找 MaterialX，也请查看此处。MaterialX 很难解释，因为您必须多次阅读规范才能理解它，但这里是我发布的翻译介绍的一部分。1.1. 简介许多 CG 制作工作室为每个制作流程使用涉及多个软件工具的工作流程。此外，由于需要在多个设施之间共享和外包大量工作，因此其他部门和工作室需要使用不同的软件包和渲染系统的完全外观开发模型。必须通过。此外，工作室渲染管道过去使用由经验丰富的程序员开发并由技术总监修改的整体着色器。纹理和着色器之间的预硬化连接和硬编码纹理颜色校正选项现在可用于通过图像处理树和混合运算符将输入纹理图像和程序纹理生成器转换为各种着色器。灵活的基于节点图的着色器网络，可连接和构建任意输入。至少需要四种不同的相关数据关系来指定 CG 对象的完整“外观”：根据图像处理网络是如何使用源、运算符、连接和参数构建的，输出空间可变数据流的数量会有所不同。 不同的贴图类型取决于与纹理文件名或 ID 关联的几何特定信息。 根据空间可变数据流、统一值和 BxDF 着色器输入之间的关系，您可以定义不同数量的材质。 根据材质与特定几何图形的关联方式，您创建的外观数量会有所不同。 据我们所知，没有通用的开放标准来传输上述所有数据关系。每个应用程序都有自己的文件格式来存储这些信息，但这些格式是私有的、专有的，并且没有充分指定和实施以完全打开和重现应用程序。不是... 因此，要指定由着色器网络构造的 CG 对象的“外观”，可以将这种外观（或外观的子组件）从一个软件包传递到另一个软件包或在安装之间传递。我们需要一个定义明确的标准，独立于平台。1.2. 提案我们提出了一个新的 Material Content Schema、MaterialX 和一个基于 XML 的文件，用于读取和写入该 MaterialX 内容。MaterialX 模式定义了一些主要元素类型和一些互补的子元素类型。主要元素类型有：定义数据处理图的标准节点集 使用 BxDF 着色器和自定义处理运算符扩展标准节点集。 定义绑定到特定统一参数值和空间变化输入数据流的着色器实例。 定义可以从节点图中引用的几何属性。 定义附加到几何体的材料和属性的特定组合。MTLX 文件是表示 MaterialX 文档的标准 XML 文件。此处使用的 XML 元素和属性对应于 MaterialX 元素和属性。一个 MTLX 文件可以是一个完全独立的文件，也可以拆分为多个文件以共享和重用组件。粗略地说，它是一种使用 XML 格式文件在不同 DCC 之间交换数据的机制，用于着色器图形定义和材质分配。例如，假设您在 Maya 中为角色建模，在 Arnold 中构建着色图，然后将该材质分配给模型。Maya 场景可以使用 Maya 进行渲染，但是如果您想像使用另一个 DCC（Houdini 或 Katana）进行外观开发的情况，您可以理解为什么需要 MaterialX。当您通过 FBX 或 Alembic 将 Maya 场景传递到另一个 DCC 时会发生什么？几何会通过，但着色器图不会通过，对吧？好吧，在另一个 DCC 中重建着色器图并重新分配材质是一项非常低效的任务。此外，代理（Arnold 术语中的 Stand-In，Maya 术语中的 GPU 缓存）无法直接访问几何体。如果渲染器和Arnold统一，可以不使用MaterialX，使用Arnold标准格式ass或者USD来解决这个问题，但是如果渲染器不同呢？不同的渲染器提供不同的着色器。官方的 MaterialX 提供了基于 OSL 的标准节点，因此如果您创建一个仅包含这些标准节点的着色器图，您可以使用支持 MaterialX 的渲染器按原样渲染。Houdini 的 Karma 在 VOP 中使用其标准节点，而不是在其 OSL 中。使用MaterialXViewer有一个查看器可以查看 MaterialX 着色器图并将着色器从 MaterialX 转换为 OSL/MDL/GLSL，所以让我们使用这个查看器来玩一下 MaterialX。1.首先，根据自己机器的环境，从https://github.com/materialx/MaterialX/releases下载MaterialXViewer 。 2.zip解压到任意位置时，内容如下。执行文件夹中的MaterialXView.exe 启动MaterialXViewer。 3. MaterialXViewer的使用方法请参考以下文档。https://github.com/materialx/MaterialX/blob/main/documents/DeveloperGuide/Viewer.md 4.点击Load Material按钮，选择下面的一个MaterialX示例文件，我们来看看。在查看器中按一个键会将OSL 文件转换为相同的 MaterialX 文件位置，将键转换为 GLSL 文件，将键转换为 MDL 文件。MaterialXViewer安装目录/resources/Materials/Examples/StandardSurfaceMaterialXViewer 可以读取 MaterialX 文件并将其转换为 OSL/GLSL/MDL 文件（最初它是一个名为 ShaderX 的项目，但在我知道之前它已与 MaterialX 项目合并）。此 MaterialXViewer 无法读取任何 MaterialX 文件，并且由于它在内部通过 MaterialXGenGLSL 显示 GLSL，因此无法显示具有光线跟踪处理节点的 MaterialX 文件，例如 Ambient Occlusion。此外，无法显示 MaterialX 标准节点以外的节点，例如带有 Arnold 节点的 MaterialX。作为一个例外，可以显示 aiStandardSurface。这是因为它对应于 MaterialX 标准曲面。在Solaris上导入MaterialX文件要加载 MaterialX 文件，只需在参考 LOP 中指定该文件。现在材质已添加到场景图树中，您可以在“指定材质 LOP”中将其指定给目标 prim 并进行渲染。导出MaterialX文件我想自己导出 MaterialX，而不是标准的 Houdini 导出器。如果在Python shell 中键入，import MaterialXMaterialX 模块可以作为标准使用。 这允许您创建自己的导出器。 由于 MaterialX 文件是 XML 格式的，所以我认为那些处理过 XML 解析器的人可以直观地控制它们。 这次我想编写一个 Python 脚本，将在 Houdini 的 mat 上下文中构建的 MaterialX 节点图导出到 MaterialX 文件中。将Houdini内置的MaterialX节点图导出到MaterialX文件我已经构建了一个 MaterialX 节点图，如下所示。构建 MaterialX 节点图时要记住的一件事是输入和输出类型必须匹配。一种常见的做法是将vector3的输出连接到color3的输入。请注意，这往往与其他节点图一起使用。我想用我自己的 Python 脚本将它导出到 MaterialX 文件中。 以 MaterialX 格式查看，这是一个必需的表面材质元素，其节点是通过该方法添加的。通过该方法添加 单个节点的 Node 元素。 MaterialX 具有与 Houdini 中的子网相对应的 NodeGraph 元素。 所以通过方法添加子网节点。MtlX Surface MaterialaddMaterialaddNodeaddNodeGraph要将Houdini中创建的MaterialX节点图导出到MaterialX，首先需要从MaterialX VOP节点检查MaterialX节点类型和输出类型。我这次做的脚本是根据MaterialX VOP的节点类型名称和签名参数判断创建的。这是我根据以上内容整理的 Python 脚本。/stage/一个 mtlx 文件会自动生成在与直接在 context 下materialx命名的 Material Network 中的MtlX Surface Material每个节点的 hip 文件相同的位置。import hou import MaterialX as mx import re signatureLabeltoValueMap ={ "Boolean (Float)":"boolean_float", "Boolean":"boolean", "BSDF (C)":"bsdfC", "BSDF (F)":"bsdfF", "BSDF":"bsdf", "Color (B)":"color3B", "Color (Color 4)":"color3_color4", "Color (Color Array)":"color3_color3array", "Color (Color)":"color3_color3", "Color (FA)":"color3FA", "Color (Float)":"color3_float", "Color (I)":"color3I", "Color (Vector 2)":"color3_vector2", "Color (Vector 3)":"color3_vector3", "Color (Vector 4)":"color3_vector4", "Color 4 (B)":"color4B", "Color 4 (CF)":"color4CF", "Color 4 (Color 4 Array)":"color4_color4array", "Color 4 (Color 4)":"color4_color4", "Color 4 (Color)":"color4_color3", "Color 4 (FA)":"color4FA", "Color 4 (Float)":"color4_float", "Color 4 (I)":"color4I", "Color 4 (Vector 2)":"color4_vector2", "Color 4 (Vector 3)":"color4_vector3", "Color 4 (Vector 4)":"color4_vector4", "Color 4 Array (Color 4 Array)":"color4array_color4array", "Color 4":"color4", "Color Array (Color Array)":"color3array_color3array", "Color":"color3", "Displacement Shader (F)":"displacementshaderF", "Displacement Shader (V)":"displacementshaderV", "Displacement Shader":"displacementshader", "EDF (C)":"edfC", "EDF (F)":"edfF", "EDF":"edf", "filename":"filename", "Float (B)":"floatB", "Float (Color 4)":"float_color4", "Float (Color)":"float_color3", "Float (Float Array)":"float_floatarray", "Float (I)":"floatI", "Float (Vector 2)":"float_vector2", "Float (Vector 3)":"float_vector3", "Float (Vector 4)":"float_vector4", "Float Array (Float Array)":"floatarray_floatarray", "Float":"float", "Integer (Float)":"integer_float", "Integer (Integer Array)":"integer_integerarray", "Integer Array (Integer Array)":"integerarray_integerarray", "Integer":"integer", "Light Shader":"lightshader", "Matrix 3x3 (FA)":"matrix33FA", "Matrix 3x3":"matrix33", "Matrix 4x4 (FA)":"matrix44FA", "Matrix 4x4":"matrix44", "String (String Array)":"string_stringarray", "String Array (String Array)":"stringarray_stringarray", "String":"string", "Surface Shader (C)":"surfaceshaderC", "Surface Shader (F)":"surfaceshaderF", "Surface Shader":"surfaceshader", "VDF (C)":"vdfC", "VDF (F)":"vdfF", "VDF":"vdf", "Vector 2 (B)":"vector2B", "Vector 2 (Color 4)":"vector2_color4", "Vector 2 (Color)":"vector2_color3", "Vector 2 (FA)":"vector2FA", "Vector 2 (Float)":"vector2_float", "Vector 2 (I)":"vector2I", "Vector 2 (M3)":"vector2M3", "Vector 2 (Vector 2 Array)":"vector2_vector2array", "Vector 2 (Vector 2)":"vector2_vector2", "Vector 2 (Vector 3)":"vector2_vector3", "Vector 2 (Vector 4)":"vector2_vector4", "Vector 2 Array (Vector 2 Array)":"vector2array_vector2array", "Vector 2":"vector2", "Vector 3 (B)":"vector3B", "Vector 3 (Color 4)":"vector3_color4", "Vector 3 (Color)":"vector3_color3", "Vector 3 (FA)":"vector3FA", "Vector 3 (Float)":"vector3_float", "Vector 3 (I)":"vector3I", "Vector 3 (M4)":"vector3M4", "Vector 3 (Vector 2)":"vector3_vector2", "Vector 3 (Vector 3 Array)":"vector3_vector3array", "Vector 3 (Vector 3)":"vector3_vector3", "Vector 3 (Vector 4)":"vector3_vector4", "Vector 3 Array (Vector 3 Array)":"vector3array_vector3array", "Vector 3":"vector3", "Vector 4 (B)":"vector4B", "Vector 4 (Color 4)":"vector4_color4", "Vector 4 (Color)":"vector4_color3", "Vector 4 (FA)":"vector4FA", "Vector 4 (Float)":"vector4_float", "Vector 4 (I)":"vector4I", "Vector 4 (Vector 2)":"vector4_vector2", "Vector 4 (Vector 3)":"vector4_vector3", "Vector 4 (Vector 4 Array)":"vector4_vector4array", "Vector 4 (Vector 4)":"vector4_vector4", "Vector 4 (VF)":"vector4VF", "Vector 4 (VV)":"vector4VV", "Vector 4 Array (Vector 4 Array)":"vector4array_vector4array", "Vector 4":"vector4", "Volume Shader (C)":"volumeshaderC", "Volume Shader (F)":"volumeshaderF", "Volume Shader":"volumeshader", } nosignatureOutputMap={ "surface":"surfaceshader", "dispacement":"displacementshader", "vector2":"vector2", "float":"float", "color":"color3", "vector4":"vector4", "vdf":"vdf", "vector":"vector3", "bsdf":"bsdf", "edf":"edf", } def defineNodeAndNodeGraph(outputNode,doc,ref): for eachVop in outputNode.inputAncestors(): nodeName = eachVop.name() nodeTypeName = eachVop.type().name() #ノードグラフを追加する if nodeTypeName == "subnet": subnetOutput = None for eachChild in eachVop.children(): if eachChild.type().name()=="suboutput": subnetOutput = eachChild break nodeGraph = doc.addNodeGraph(nodeName) defineNodeAndNodeGraph(subnetOutput,nodeGraph,ref) #ノードを追加する else: if nodeTypeName.startswith("mtlx"): nodeTypeName = nodeTypeName.split("mtlx")[-1] else: continue #Separate系のノードにsignatureパラメータがないのでVOPノードタイプ名からMaterialXノードタイプを判断する if nodeTypeName.endswith(("3c","4c","3v","4v")): nodeTypeName = nodeTypeName[:-1] signatureValue = "" signature = eachVop.parm("signature") if signature != None: signatureLabel = signature.menuLabels()[signature.menuItems().index(signature.eval())] if signatureLabel in signatureLabeltoValueMap: signatureValue = signatureLabeltoValueMap[signatureLabel] refNodeDef = None if signatureValue != "": refNodeDef = ref.getNodeDef("ND_"+nodeTypeName+"_"+signatureValue) else: refNodeDef = ref.getNodeDef("ND_"+nodeTypeName) if refNodeDef == None: for outputType in eachVop.outputDataTypes(): if outputType in nosignatureOutputMap: refNodeDef = ref.getNodeDef("ND_"+nodeTypeName+"_"+nosignatureOutputMap[outputType]) if refNodeDef != None: break if refNodeDef == None: continue nodeElement = doc.addNode(refNodeDef.getNodeString(),nodeName) nodeElement.setType(refNodeDef.getType()) for eachInput in refNodeDef.getInputs(): parmType = eachInput.getType() value = eachInput.getValueString() eachParmName = eachInput.getName() if parmType in ["color2","color3","color4","matrix33","matrix44","vector2","vector3","vector4"]: tupleValues = 0 if value != "": tupleValues = tuple([float(i.strip()) for i in value.split(",")]) vopParmName = eachParmName+"_"+signatureValue parmCheck = eachVop.parmTuple(vopParmName) if parmCheck == None: vopParmName = eachParmName evaluatedValues = eachVop.parmTuple(vopParmName).eval() if len(evaluatedValues)==1: evaluatedValues = evaluatedValues*int(re.sub(r"\D","",parmType)) if evaluatedValues != tupleValues: nodeElement.setInputValue(eachParmName, ",".join([str(i) for i in evaluatedValues]), parmType) elif parmType == "integer": vopParmName = eachParmName+"_"+signatureValue parmCheck = eachVop.parm(vopParmName) if parmCheck == None: vopParmName = eachParmName evaluatedValue = eachVop.parm(vopParmName).eval() if evaluatedValue != int(value): nodeElement.setInputValue(eachParmName, str(evaluatedValue), parmType) elif parmType == "float": vopParmName = eachParmName+"_"+signatureValue parmCheck = eachVop.parm(vopParmName) if parmCheck == None: vopParmName = eachParmName evaluatedValue = eachVop.parm(vopParmName).eval() if evaluatedValue != float(value): nodeElement.setInputValue(eachParmName, str(evaluatedValue), parmType) elif parmType in ["string","filename"]: vopParmName = eachParmName+"_"+signatureValue parmCheck = eachVop.parm(vopParmName) if parmCheck == None: vopParmName = eachParmName evaluatedValue = eachVop.parm(vopParmName).eval() if evaluatedValue != value: nodeElement.setInputValue(eachParmName, evaluatedValue, parmType) elif parmType == "boolean": if value == "true": value = True else: value = False vopParmName = eachParmName+"_"+signatureValue parmCheck = eachVop.parm(vopParmName) if parmCheck == None: vopParmName = eachParmName evaluatedValue = eachVop.parm(vopParmName).eval() if evaluatedValue != value: nodeElement.setInputValue(eachParmName, str(evaluatedValue).lower(), parmType) return def connectNodeAndNodeGraph(outputNode,doc): for eachVop in outputNode.inputAncestors(): if eachVop.type().name() == "subnet": eachNodeElement = doc.getNodeGraph(eachVop.name()) suboutputNode = None for i in eachVop.children(): if i.type().name()=="suboutput": suboutputNode = i break connectNodeAndNodeGraph(suboutputNode,eachNodeElement) else: eachNodeElement = doc.getNode(eachVop.name()) for eachConnect in eachVop.inputConnections(): inputNode = eachConnect.inputNode() inputNodeTypeName = inputNode.type().name() connectedParm = eachConnect.outputName() targetParm = eachConnect.inputName() if inputNodeTypeName == "subnet": targetNodeElement = doc.getNodeGraph(eachConnect.inputNode().name()) targetOutput = targetNodeElement.getOutput(targetParm) if not targetOutput: targetOutput = targetNodeElement.addOutput(targetParm) subnetOutputType = eachConnect.outputDataType() if subnetOutputType == "color": targetOutput.setType("color3") elif subnetOutputType == "color2": targetOutput.setType("color2") elif subnetOutputType == "color4": targetOutput.setType("color4") elif subnetOutputType == "float": targetOutput.setType("float") elif subnetOutputType == "vector": targetOutput.setType("vector3") elif subnetOutputType == "vector2": targetOutput.setType("vector2") elif subnetOutputType == "vector4": targetOutput.setType("vector4") elif subnetOutputType == "int": targetOutput.setType("integer") elif subnetOutputType == "bool": targetOutput.setType("boolean") elif subnetOutputType == "surface": targetOutput.setType("surfaceshader") elif subnetOutputType == "displacement": targetOutput.setType("displacementshader") subnetOutputNode = inputNode.subnetTerminalChild(targetParm)[0] for i in subnetOutputNode.inputConnections(): if i.outputName() == targetParm: outputNodeName = i.inputNode().name() break targetOutput.setConnectedNode(targetNodeElement.getNode(outputNodeName)) eachNodeElement.setConnectedOutput(connectedParm,targetOutput) else: targetNodeElement = doc.getNode(eachConnect.inputNode().name()) eachNodeElement.setConnectedNode(connectedParm,targetNodeElement) return searchPath = hou.text.expandString("$HH/materialx") ref = mx.createDocument() mx.loadLibraries(["libraries"],searchPath,ref) materialxNetwork = hou.node("/stage/materialx") if materialxNetwork != None and materialxNetwork.type().name() == "matnet": for eachVop in materialxNetwork.recursiveGlob("*",filter=hou.nodeTypeFilter.Vop): if eachVop.type().name()!="mtlxsurfacematerial": continue outputNodeName = eachVop.name() surfaceshaderName = "" displacementshaderName = "" for eachConnect in eachVop.inputConnections(): if eachConnect.outputName() == "surfaceshader": surfaceshaderName = eachConnect.inputNode().name() elif eachConnect.outputName() == "displacementshader": displacementshaderName = eachConnect.inputNode().name() if surfaceshaderName == "": continue doc = mx.createDocument() defineNodeAndNodeGraph(eachVop,doc,ref) connectNodeAndNodeGraph(eachVop,doc) material = doc.addMaterial(outputNodeName) material.setConnectedNode("surfaceshader", doc.getNode(surfaceshaderName)) if displacementshaderName != "": material.setConnectedNode("displacementshader", doc.getNode(displacementshaderName)) filePath = hou.text.expandString("$HIP") + "/" + hou.text.expandString("$HIPNAME") + "_" +outputNodeName + ".mtlx" mx.writeToXmlFile(doc, filePath) 使用导出的 MaterialX 文件如果在 Maya 的 Arnold 环境中使用 aiMaterialX 读取 MaterialX 文件（Arnold7.0 对应 MaterialX1.38）或使用 MaterialXViewer 读取，则可以像这样轻松传递着色器图。hip文件和python脚本都在这里：https://drive.google.com/file/d/1frXCG4R2Zv2MiFZl0cn2k2RvUgkoEmfc/view?usp=sharing
usd-noodleusd-noodle是个开源的以节点方式显示USD文件依赖关系的小工具。项目地址：https://github.com/chris-gardner/usd-noodleusd-noodle可以通过命令行独立运行，也可以在其它DCC中运行，还可以集成在DCC中使用（作者提供了Houdini的完整插件！）安装方法Git到本地在houdini内部运行可直接在Houdini中执行以下命令：import sys sys.path.append('/path/to/somewhere/usd-noodle') import usd_noodle usd_noodle.main()为了方便使用可以制作为Shelf上的快捷方式：作为Houdini插件使用将hoodle.json移动到houdini的packages中，并且编辑hoodle.json"NOODLE": "/path/to/usd-noodle"启动方法New Pane Tab Type -> USD -> USD-Noodle启动面板在Lop物体节点上右键LOP Actions -> Noodle Stage将此段加载到Noodle中显示。Noodle以图形化节点方式显示USD文档结构，更易于快速了解文档结构及引用关系。下期将尝试使用nxt_graphs_usd来查看和管理USD文档。NXT相比要强大得多，而且借助nxt_graphs_usd可以完成对USD文档的编辑，另外NXT目前已经有了blender及maya的插件版本，并且还支持nuke及houdni等DCC。
USD/SolarisUSD是一个用于处理由Pixar创建的场景图的库，Pixar是一种文件格式，供多个人同时使用。与以前的文件格式的主要区别在于 ，可以通过将多个USD文件组合到一个“过程”（称为“过程场景图”）中来构造一个场景图。Solaris可以在程序上将此USD文件与USD文件组合并处理SOP以构建单个场景图。另一个功能是此Solaris（Stage网络）上的节点位于内存或文件中，并且这些节点中的每个都是USD文件，被视为一个层。节点处理= USD处理=程序场景图构建这就是Solaris的世界，Solaris的世界就是USD的世界。这是Solaris和USD的粗略概述。从Pixar官方下载USD文件Pixar官方提供了些USD资产及场景供学习和参考Pixar官方USD资产下载链接http://graphics.pixar.com/usd/downloads.html打开Solaris下载完成后，启动Houdini18并将预设布局切换至Solaris。面板介绍改变窗口布局后，将会多出两个面板，这是专门供Solaris也就是USD所使用的。第一个是“Scene Graph Tree场景图树”，它显示当前所选节点的USD场景图（所谓的阶段）。该Solaris表示“ USD 过程场景图”，其中一个节点被视为USD文件=图层。连接节点后，将按程序合成该节点。当前选中的节点或显示标志处于打开状态的阶段是“到目前为止组成节点的结果”。另一个是“Scene Graph Details场景图详细信息”。该面板使您可以专注于所选Prim的属性和其他信息。在Houdini / SOLARIS中打开下载的USD文件使用LoadLayer首先，使用“ LoadLayer”节点打开USD文件。这里为何不是 LoadUSD？因为在USD中，USD文件又被称为图层，在之前我的博客上也有过介绍。LoadLayer的作用是“将指定的USD作为图层加载”。用合成操作符节点打开除了LoadLayer之外，Houdini的Solaris还有多种节用于读取USD图层。现在我使用各种合成操作符节点来加载它。如果您在此节点中读取，则可以使用成分操作符功能加载USD文件。如果以这种方式读取，则正在加载的usda文件中的ClassPrim将被隐藏并且无法被覆盖。但是，如果使用LoadLayer加载该图层，然后将该节点连接到Reference节点，则使用参考对子图层进行图层加载，然后在这样的场景中使用\</sdfPath ...>Reference Primer将被加载为。在这种情况下，将不封装该层，并且还将加载指定层中的另一个Prim，以便可以像继承过程中那样覆盖参考源的Prim。用Sublayer打开首先，使用Sublayer节点打开它。使用Sublayer打开时与使用LoadLayer打开时的参数是基本相同的。场景图路径与执行LoadLayer时相同。与loadlayer的区别在于，USD文件可以与具有此单个节点的子层组合。USD 与此子层组合的优点是，可以使用“静音层”和“启用”来打开/关闭USD的效果。这样，你可以在切换USD的图层效果时轻松检查Sublayer节点的效果，以便打开/关闭2D软件的图层。使用此节点进行读取的好处是可以在读取时像PhotoShop图层一样打开和关闭该节点。用Reference打开接下来，尝试使用“Reference”节点进行加载。Reference节点还具有将MultiInput的输入结果转换为Reference的功能。通过将引用类型设置为引用文件，您可以使用此节点来“按引用读取USD文件”。查看SceneGraphPath，可以看到原始名称为绿色。与LoadLayer的最大不同在于，顶级节点名称不同。使用LoadLayer加载时，将按原样加载USD文件中写入的基本结构。但不同之处在于Reference以Destination Primitive指定的原始名称。$OS是节点名称，因此Prim在节点名称reference1下读取。可以尝试右键单击Reference节点，然后打开“ LOP Actions”>“Inspect Active Layer”。会打开当前节点的编辑状态。与Reference节点的读取差异使用reference1节点加载和使用LoadLayer加载并引用它之后连接节点之间存在很大差异。区别在于是否封装。在使用参考节点加载时，如果您查看USDA，则在参考前面添加此类文件路径规范，由指定路径的USDA的DefaultPrim或SdfPath指定Prim将通过引用加载。使用StageManager打开最后来说StageManager节点。在教程视频中，有许多使用此StageManager的示例，但是上述三个与此StageManager有什么区别？由于与“Stage”管理器有很大的不同，因此该节点可以自己构建一个Stage。在这种情况下，该Stage是多个USD文件组成的结果。打开后，StageManager屏幕看起来就像上面图像一样的窗口。单击其中的文件夹图标。仅使用此StageManager节点，就为ScenerGraphPath 构建了包含引用的完整Stage（USD组成的场景图）。编写并打开ASCII文件很少有人这样做，但是使用inlineusd，你可以手动编写USD文件。通过这样用ASCII编写USD Source，甚至你可以直接构建出一个完整的Stage。实际应用中如何打开USD？如上所讲，在Solaris中有多种方法去加载USD文件，那么在实际使用时如何选择呢？如果要放置许多对象，请使用StageMnaager如果要在Stage上布置数百个资产。在这种情况下，将它们一一读取或分组在一起是非常麻烦的。在这种情况下，使用StageManager可以轻松地通过引用来加载和加载多个资产，并可以通过拖放轻松地执行诸如分组和更改层次结构之类的操作。换句话说，通过此单个节点完成了阶段构建因此，建议将此节点用于场景布局中。当然，将它们组合到一个StageMnager中可能会造成混淆！！！在这种情况下，您可以连接多个StageMnaager。如果要封装图层，则为使用Reference如果要“封装”并加载要通过参考或指定Prim加载的层的DefaultPrim以外的其他东西，请使用Reference节点进行加载。当“Reference”节点读取数据时，指定的“Prim”将被隐藏并且不能被覆盖。因此，当你想要读取这些角色的时候，使用Reference节点进行加载似乎比较好。其它情况下可使用LoadLayer除此之外，建议使用LoadLayer +合成操作符节点或其他节点（Graft等）。对于LoadLayer读取的模式，利用Solaris的节点库构建一些结构（构建一个场景，同时合并要为其指定着色器的灯光节点，等等）。可以使用SubLayer或Reference直接读取文件，但是在那种情况下很难更改输入顺序或暂时将其关闭，因此很难使用，因为该节点的可列表性不是很好。如果始终使用LoadLayer进行加载和编写，则切换起来会更容易，并且会澄清“我正在读取文件”，因此个人最好使用LoadLayer一次性加载USD文件。一起去使用它们当然，StageMnaager和LoadLayer +组合操作符节点可以一起使用。在使用StageMnager一次构建场景时，对节点进行细微的变化并最终将它们组合在一起，等等。通过选择不同的打开USD的方式，尽可能的简化复杂的场景来构建节点。结语介绍完了如何在SOLARIS / LOP中打开USD文件。我们会发现仅打开一个USD文件，有这么多种不同的方法。除此之外，你还可以使用LOP等打开+ SOP场景，因此我认为Solaris是用于处理USD的非常灵活的工具，并且可以根据想法启用各种USD创作。后面将会讲解“合成操作符”节点。
Primitive Type Primitive类型Primitive类型种类较多，如几何体一般包括 mesh，以及一个父级 Xform，灯光一般包括 Light以及一个父级 Xform，材质包括 Shader以及父级的 Material，用于方便用组去管理，还会创建 Scope类型出来。相机一般是 Camera。另外还包括 BasisCurves等类型。parent primitives 父级类型在houdini LOP中，parent primitives分为 Scope，Xform和 None。Scope:范围，在USD中是primitives的一种类型，它类似于组的概念，但是它本身不支持变换，主要用来组织层级关系，比如 /models/..., /materials/..., /collections,Xform:Xform在USD中是中是 primitives的一种类型，它类似于组的概念，可支持变换属性。Kind种类在USD中是指“模型”的种类。这个概念在USD中较为难理解。具有几何体形状的每个基本体应具有“种类”。此“模型”含义在USD中并不是图形学意义上的模型，而是一个抽象概念，用于将场景图划分为更容易管理的部分，是对场景图的注释。Kind包括以下几种： Kind种类 Draw mode USD中定义 Icon Assembly程序集 有 重要的组模型，通常是已经发布的资产或者引用的资产 Component组件 有 模型层级结构的“叶”项，这种层次不能再包含其它模型（Group或Assembly或其它Component） Group组 有 一组其它模型（Assembly或Component或Group），模型层级结构的分支项 Subcomponent子组件 无 模型的子模型 None无 无 无定义 场景图树中的模型层次结构可能如下图所示：要维护模型层次结构，场景中应该具有以下内容：处理其下（从其降序）的几何体都应该具有一个起始，包括根 prim，例如 /Models。仅 group/assembly应包含其他模型类型。Component类型不能包含 groups, assemblies或 models。
基本概念Primitives (prims)prims是USD的基本“单位”，在谈论USD时，"Primitive"通常缩写为"prim"。类似于在HTML中的 "element" 。prim有一个名称，0个或多个命名属性，以及0点或多个子级，例如，多边形网络是个prim，灯光是一个prim，材质是一个prim。"xform"prim存储适用于其子prim的transform。Prims是定义阶段的对象树中的“节点”。Properties特性, attributes属性, relationships关系"Property" 是可以附加到prim类型的两种命名数据的通用名称：Attributes属性:属性是描述prim的类型化值（例如整数，颜色，变换矩阵，数组等）（例如，float size = 1.0定义Cube Prim的统一大小）。这是最为常见的property。Relationships关系：关系是在一个prim和另一个prim之间建立的链接（例如，指定对mesh prim的shader要使用的材质，或指定collection中的primitives）。Metadata 元素据属性本身可以附加有命名值，这可以修改它们的工作方式，例如，如果为mesh指定颜色primvar ，则可添加interpolation元数据，该元数据指定渲染器应如何在这些点的颜色之间混合。元素据还用于附加“附加”数据，这些数据可能特定于DCC，或者对于调试或构建用户界面很有用。例如，不同属性的文档存储在元素据中。Layers层一个.usd文件是一个层（.usdz文件，一个文件可以在一个存档文件中包含多个层）。一个层可以表示场景的一个片段，例如，道具，角色，装备的一部分，灯光设置等。它还可以表示用来创建基准的默认值，例如包含镜头序列设置的图层，您可以在代表该序列中每个镜头的文件中reference这些设置。在Houdini中，各种节点创建新的内存层以覆盖现在数据。您可以使用Layer Break LOP手动创建一个新层，一个典型的LOP网络将涉及许多层。其中一些层是从磁盘导入的，某些层是由节点在内存中创建的，最后由这些层组成。（network editor中的可选可视化工具向您显示了网络中哪些节点在不同的层上工作。）Composition arcs 合成操作符从一层到另一层的引用。例如，场景图层可能会reference进包含道具，灯光等图层。这些图层可能reference自己的子层，这些子层可能会reference子子层，依此类推。将各层复合在一起，合并每层中的prims。较高层中的属性将覆盖下面的属性。例如，场景文件可以reference灯光层，但是灯光部门可能已经从 "base defaults", "finished lighting", 和"in-progress lighting"的三个子层组成了该层本身。其他部门不需要知道灯光层是如何组成的，他们只需要reference顶级的lighting层既可。在Houdini中，可以使用Reference LOP ，使用LOP网络中的节点将参考添加到要在内存中构建的层。你可以使用 Sublayer LOP将现有子层（.usd文件）加载到当前阶段的层堆栈中。Stage舞台当加载USD文件和系统组成的所有层结合在一起来计算prims的最后场景图，该结果被称为Stage。请注意，stage只是结果的名称。“stage文件”和“layer文件”之间没有区别，如果将.usd文件加载为"top-level"文件，它将创建一个新的stage。在另一种情况下，你可以将同一文件作为不同stage的图层引用。在Houdini中，位于网络项部的LOP节点创建一个stage，随后的每一个LOP会按程序修改该阶段的内容，从而创建一个新阶段。因此，对于任何LOP节点，您都可以使用hou.LopNode.stage()获取该节点输出的stage对象。您可以usda通过右键单击LOP节点选择LOP Actions ▸ Inspect Flattened Stage来查看节点以语法形式显示的stage输出。组成每个USD文件都包含完整的“场景”。USD文件可以将其它USD文件的内容导入为图层。USD库包括用于将多层合成、叠加到顶层文件创建的整个场景(stage)中的软件。组成包括合并各层之间的以下差异：New prims.（新prims）。Prim ordering.（Prim排序）。Whether a prim is active or inactive.（prim是活动还是不活动）。Variants.（变化）。New properties (attributes and relationships).（新特性（属性和关系））。Attribute values. (Higher layers can even "block" (unset) the value of an attribute so it appears to be unassigned).（属性值）。Metadata values.（元数据值）。Houdini会自动组合您在编辑USD时创建的各种磁盘和内存层。你还可以使用工具来了解每个图层上的内容以及图层的组成方式。Sublayers, referencesUSD具有两种“导入”另一个USD的内容的方法：sublayering 是将导入文件的树覆盖在当前树上，而reference是将导入树的内容作为分支附加到现有树上。你可以将sublayering视为组成整个场景的不同版本（例如，将照明部门的场景版本与最终照明重叠在布局部门的场景版本中并使用照明）并将其视为场景添加一部分（例如，导入道具以开始布局）。Sub-layering 子层sublayering 将导入文件的内容覆盖在现有内容之上，每棵树关于具有相同路径的prims和具有相同名称的属性、元数据的opinions将基于opinion strength合并。当合并每个文件都应构成整个场景一部分时，此功能很有用。例如，组成代表场景布局，道具，角色，特效，灯光的单独图层。sublayering 的固有特征是“一切都仪在同一位置”。prims在新文件中的路径与其在源文件中的路径相同。因为这些部门中的每个部门都在单独的子层中工作，所以他们不必担心共享单个文件和进行冲突的编辑。但是，因为它们都在同一场景图层次结构中工作，所以每个部门可以将更改应用于其他部门创建的任何内容。Houdini中Sublayer LOP节点加载新的子层到场景中。LOP网络通常总是在空根层的最强子层。一些LOP节点可能会启动一个新的更强子层，然后再由所有后续LOP节点对其进行修改。这些网络输出的是此子层堆栈的组成。Referencing 引用Referencing将被Referencing文件并“移植”到当前树中的分支上。Referencing有且于将较小的单个资产移植到较大的场景中。特别地，引用是在不同位置多次加载同一图层文件的唯一方法。（因为子分层适用于整个树，所以多次对同一文件进行子分层将无效。）简单的垃圾桶资产#usda 1.0 ( defaultPrim = "TrashCan" ) def Xform "TrashCan" ( kind = "component" ) { def Cylinder "Can" { token axis = "Y" bool doubleSided = 0 double height = 2 double radius = 1 } }一组中3个不同房间的引用垃圾桶的文件有3次资产#usda 1.0 () def Xform "Scene" { def Xform "Set" { def "BathroomTrashCan" ( append references = @./trashcan.usda@ ) { double3 xformOp:translate = (2, 0, 1.4) uniform token[] xformOpOrder = ["xformOp:translate"] } def "KitchenTrashCan" ( append references = @./trashcan.usda@ ) { double3 xformOp:translate = (16.01, 5, -43.072) uniform token[] xformOpOrder = ["xformOp:translate"] } def "OfficeTrashCan" ( append references = @./trashcan.usda@ ) { double3 xformOp:translate = (-7.12, 0, 11.9) uniform token[] xformOpOrder = ["xformOp:translate"] } } }Houidni的Reference LOP和Stage Manager LOP创建references。 Graft LOP是相似的，但不是从一个文件引用，它插入一个分支到场景图树从LOP网络的其他地方。Houdini跟踪多个层次的子层和引用如何协同工作以在单个场景图位置生成关于图元的最终意见可能会变得很复杂。你可以使用Scene Graph Details pane面板的Layer Stack和Composition选项卡来检查哪些层构成了primitive，以及如何将这些不同的层组合在一起。Payloads 有效载荷有效负载本质上是一个references，但是有可能让USD不加载有效负载，除非特别要求它们。这使您可以控制场景的哪些部分被加载到内存中，从而通过集中精力关注场景的各个部分来减少内存使用和处理时间。在Houdini中，您可以在references文件的节点的参数中指定references的文件是纯引用还是有效负载。默认情况下，Houdini加载所有有效负载（因此它们的行为与引用完全相同）。但是，场景图树中的Configure Stage LOP和Load Masks控件允许您禁用默认的有效负载加载，然后选择要加载的特定有效负载。Activation and visibility 激活和可见性由于USD是专为非破坏性编辑而设计的，因此不允许deleting( 删除) primitives。但是，您可以使用新值override(覆盖 ) primitives，或者deactivate(停用 ) primitives，以使它们不起作用。您还可以编辑场景中对象的visibility可见性。默认情况下，USD prims是可见的。可见性选项只有两个设置：“inherit继承”和“invisible不可见”。因此，将prim 标记标记为“不invisible”也会使其所有后代也不可见。在Houdini中，您可以在Scene Graph Tree面板中以交互方式在激活和不激活以及可见/不可见之间切换图元，以允许您查看图元的效果。Houdini在内存中未保存的图层中执行这些编辑，因此它们不会影响最终生成的USD。Opinion strength Opinion 强度属性根据“strongest wins”规则进行解析，因此任何给定属性的所有值都将从最强的PrimSpec中获取。例如，具有默认值的“弱层”将被具有动画的更强层掩盖。例如，layout部门在场景中放置了一个道具，然后动画部门添加了一个动画该道具的图层。决定意见强度的最常见方法是图层堆栈的顺序。例如，最薄弱的一层可能是layout层，在这里资产被合并和定位。下一层可以将动画部门的角色添加到场景中（如果有必要，可以通过对某些现有图元应用更强的opinions，动画部门可以移动或调整layout）。然后，FX部门可能要向场景图添加新的几何图形，或将RBD模拟应用于layout或动画部门添加的某些现有primitives。Default primitive 默认primitive作为其metadata的一部分，每一层都可以指定“默认primitive”。如果您在文件中进行referenc，但未明确指定要附加的文件中的根级别的prim ，则将引用该prim 。您可以使用Configure Layer LOP为图层指定默认primitive（以及其他图层级metadata）。当您写出USD文件时，USD render node具有用于设置“顶层”图层文件的默认prim的参数。该USD render node有一个选项（Error Saving Layer With No Default Primitive）如果您还没有指定一个默认的primitive的所有层的被写到导致错误。