原文页面 https://animallogic.com/technology/publications/ash-a-case-for-layered-shading/
PDF https://animallogic.com/wp-content/uploads/2023/06/ASH-A-Case-For-Layered-Shading.pdf
DOI https://doi.org/10.1145/3543664.3543675
ASH:分层着色的理由
Luke Emrose(Animal Logic,澳大利亚悉尼)
Curtis Black(Animal Logic,澳大利亚悉尼)
Emanuel Schrade(Animal Logic,澳大利亚悉尼)

图 1:Animal Logic 的 USD ALab 场景是一个典型制作镜头示例,使用 ASH 作为分层着色(layered shading)解决方案。我们的自定义 UI 让艺术家能够编辑场景对象的材质层(material layers)。
7 局限与未来工作(Limitations and Future Work)
ASH 已在多部电影制作中得到验证,成为 Animal Logic 工作流中成熟且稳定的一部分。如今我们把分层材质纳入我们创建的每一个资产中;“可独立调节的 layers 与 overrides”所带来的灵活性,让我们获得了更多创作工具,并在需要临时改动时取得更快的迭代速度。尽管如此,Glimpse 团队仍识别出许多可扩展/可改进的方向,我们也会持续推进工具以应对不断增长的复杂度与真实感需求。
7.1 分层深度(Layering Depth)
当前 Glimpse ASH 的实现对每个图元的 substance slots/layers 数量限制为 8。这个限制相对随意;从本文描述来看,也可以选择支持任意数量的层。
7.2 Substance Product 的可定制性(Substance Product Customization)
我们并不支持像 MaterialX、Arnold、PRMan 等系统那样,通过节点图连接来实现 surface products 的水平混合与插值(horizontal mixing / interpolation)。我们的经验是:ASH 的分层系统已提供一个足够的替代方案(并且在“编辑材质”的工作流维度上有明显优势)。但也可以设想把两种技术结合起来,形成一个表达能力更强的系统。我们计划在这一领域与其它厂商继续讨论。
7.3 垂直分层(Vertical Layering)
目前 ASH 的垂直分层通过硬编码的 surface products 支持:这些 products 把垂直层直接内建在其定义里。因此,这些垂直层无法在“产品暴露的参数”之外被内在地编辑。到目前为止这还不算严重问题,但一些用户希望获得更多控制;从代码维护角度看,把垂直层的处理自动化也很有吸引力,预计能降低代码量与复杂度。
我们希望把近期关于垂直分层的研究成果纳入系统,特别包括:[Belcour 2018]、[Weier and Belcour 2020]、[de Dinechin and Belcour 2022]、[Guo et al. 2018] 与 [Randrianandrasana et al. 2021]。
我们还设想通过扩展 slot 命名来支持 substances 的垂直分层:例如 "000" 目前是水平层的 slot 名称;如果要为该 slot 添加一个垂直层,可以指定 "000|010",表示把 slot "000|010" 指定的垂直 substance 层添加到 "000" 上。如果 override 的命名约定仍然适用,那么用户既可以像以前一样用 "000::010" 为 "000" 指定 override,也可以用 "000|010::010" 为某个垂直层指定 override。
这种方案显而易见的问题是:用户可能会对命名约定感到困惑;但我们可以通过合适的 UI 来隐藏这些复杂性。相较于像 MaterialX 那样基于图的垂直分层系统,该方案的一个优势是:材质可以以**非破坏性(non-destructive)**方式修改——垂直层可以完全在绑定层面添加或移除;此外垂直层也能在多个资产之间复用,并且如果按与水平层相同的方式排序,那么垂直堆栈中的层级位置也能被准确、可靠地控制。
7.4 更友好的 slot 命名(Friendly Slot Naming)
ASH 采用的字母数字命名与排序,旨在为“如何对可能很复杂的层名进行排序”这一问题提供一个合理折中。因此在 Animal Logic,我们建立了三位数字的命名约定:主 slots 命名为 "010"、"020"、"030"、…、"0X0"。这让 "000" 可以在任何时候被放到层堆栈最底部;同时以 10 为步长,也方便在 "010" 与 "020" 之间插入 "015" 等以调整顺序。出于简化与艺术家可用性考虑,我们刻意避免使用非数字字符。我们把该方案提交给 OSL 委员会时,有成员指出这让人联想到 BASIC 语言的 goto 标签;它确实与许多既有系统第一眼看上去很不相同。我们也意识到这一点,因此希望与 VFX 社区进一步讨论,探索改进/简化命名方案与策略的可能性。

图 20:Animal Logic 的 USD ALab 场景展示了在复杂环境中使用 ASH 进行分层着色的结果。
在我们的框架中,我们可以很容易地把“真实的金属高光响应(metallic specular)”与“光泽电介质响应(glossy dielectric)”组合起来,而无需使用诸如 "metalness" 这类非物理参数;同时仍保留 Photoshop 风格的 alpha 分层能力。下面是一位 Glimpse/ASH 生产使用者的评价:
Animal Logic 的 ASH 着色工具让艺术家能够在复杂层级中快速分层材质,而不必预先把最终 shader 组合固定下来。Glimpse 的分层机制也让我们能轻松为复杂装配体或大型布景叠加多种风化效果,而无需钻进单个资产组件内部逐一修改。该方法与我们的材质引用方案结合后,在处理复杂资产时显著缩短了 lookdev 部门的周转时间。
Jean-Pascal leBlanc(Global Asset Supervisor)
7.5 层级 vs 集合(Hierarchy vs Collections)
ASH 的层级化赋值依赖一个合理的场景层级,作为 slot 绑定发挥作用的脚手架(scaffolding)。但某些设施可能更偏好“更扁平”的对象层级。在这种情况下,把层级化赋值扩展为支持基于集合(collection-based)的赋值是很有意义的:可以由 collections 形成一个“虚拟层级”,并以更具表达力的方式给多个对象赋 slot。我们目前尚未在 Glimpse 中采用该想法,但计划将其作为与其它工作室讨论的议题,以进一步提升基于 slot 的赋值机制的灵活性。
我们相信:把本文工作呈现出来,并像我们目前已经在 OSL、MaterialX 与 Pixar USD Technical Steering Committees 所做的那样积极讨论,会促成对这些想法的改进与修正,使整个行业都能受益。这也激励我们持续改进与完善这项工作。我们的长期目标,是让 USD 等开源场景描述格式支持“每图元多材质绑定”,并具备层级传播与层排序能力,从而为在绑定层实现材质分层提供基础。我们也希望进一步扩展这些想法,使其包括真正的垂直分层支持,以及关于 slot 命名、slot 排序与生产用法的清晰标准。如果你有任何问题、建议或反馈,我们非常欢迎与你交流并讨论本文各方面内容。
8 结论(Conclusion)
材质分层与 slot 绑定让 Animal Logic 能以更高的真实感与效率表达表面外观。把基础材质(base materials)与 pattern 生成等管线环节解耦,并允许并行工作,相比我们在 ASH 之前的方式显著提升了生产力。将资产以编码其结构的层级构建,也帮助我们进一步发挥层级化 slot 绑定的威力,并简化材质赋值。摆脱一对一绑定的限制,使我们能够在过去需要重新发布成千上万材质资产的大规模表面修改场景中大幅简化工作。避免追随行业对 uber-shaders 的依赖,也让我们获得了更贴近真实物理的视觉风格与外观;如图 20 所示,它避免了单纯通过“混合材质参数”来表示分层时经常出现的不自然伪影。
参考文献(References)
说明:为便于检索,以下条目保留作者、题名、年份以及 DOI/URL 等关键信息(条目内容本身保持英文/可检索形式)。
- Okan Arikan. 2005. Pixie Documentation. http://www.okanarikan.com/project/2005/05/24/Pixie.html
- Autodesk. 2022. Arnold Documentation. https://docs.arnoldrenderer.com/display/A5AFMUG/Layer+Shader
- Laurent Belcour. 2018. Efficient Rendering of Layered Materials Using an Atomic Decomposition with Statistical Operators. ACM Trans. Graph. 37, 4, Article 73 (jul 2018), 15 pages. https://doi.org/10.1145/3197517.3201289
- Blender Documentation Team. 2022. Blender 3.2 Manual - Cycles. https://docs.blender.org/manual/en/latest/render/cycles/index.html
- Heloise de Dinechin and Laurent Belcour. 2022. Rendering Layered Materials with Diffuse Interfaces. Proc. ACM Comput. Graph. Interact. Tech. 5, 1, Article 13 (may 2022), 12 pages. https://doi.org/10.1145/3522620
- Larry Gritz, Clifford Stein, Chris Kulla, and Alejandro Conty. 2010. Open Shading Language. In ACM SIGGRAPH 2010 Talks (Los Angeles, California) (SIGGRAPH ’10). Article 33, 1 pages. https://doi.org/10.1145/1837026.1837070
- Yu Guo, Miloš Hašan, and Shuang Zhao. 2018. Position-Free Monte Carlo Simulation for Arbitrary Layered BSDFs. ACM Trans. Graph. 37, 6, Article 279 (dec 2018), 14 pages. https://doi.org/10.1145/3272127.3275053
- Niklas Harrysson, Doug Smythe, and Jonathan Stone. 2021. MaterialX Physically-Based Shading Nodes. https://www.materialx.org/assets/MaterialX.v1.38.PBRSpec.pdf
- Illumination Research Pte Ltd. 2021. 3Delight Documentation. https://documentation.3delightcloud.com/display/3DSP/Introduction
- Scott Iverson. 2014. AIR User Manual. http://www.sitexgraphics.com/air.pdf
- Wenzel Jakob. 2014. Mitsuba Documentation. https://www.mitsuba-renderer.org/releases/current/documentation.pdf
- Wenzel Jakob, Eugene d’Eon, Otto Jakob, and Steve Marschner. 2014. A Comprehensive Framework for Rendering Layered Materials. ACM Trans. Graph. 33, 4, Article 118 (jul 2014), 14 pages. https://doi.org/10.1145/2601097.2601139
- NVIDIA Corporation. 2019. MDL Documentation. https://developer.nvidia.com/designworks/dl/mdl_spec
- Matt Pharr, Wenzel Jakob, and Greg Humphreys. 2022. PBRT v4 Documentation. https://www.pbrt.org/users-guide-v4
- Pixar. 2022a. Renderman 13.5 Release Notes. https://renderman.pixar.com/resources/RenderMan_20/rnotes-13.5.html
- Pixar. 2022b. Renderman Documentation. https://renderman.pixar.com/resources/RenderMan_20/rfhLayering.html
- Pixar Animation Studios. 2017. Material Assignment in UsdShade with Collections and Purpose. https://graphics.pixar.com/usd/files/MaterialAssignmentinUsdShadewithCollectionsandPurpose.pdf
- Pixar Animation Studios. 2021. Renderman 24 MaterialX Lama Documentation. https://rmanwiki.pixar.com/display/REN24/MaterialX+Lama
- Pixar Animation Studios. 2022. USD Documentation - USDShade. https://graphics.pixar.com/usd/dev/api/usd_shade_page_front.html
- Joël Randrianandrasana, Patrick Callet, and Laurent Lucas. 2021. Transfer Matrix Based Layered Materials Rendering. ACM Trans. Graph. 40, 4, Article 177 (jul 2021), 16 pages. https://doi.org/10.1145/3450626.3459859
- SideFX. 2022. Mantra Documentation. https://www.sidefx.com/docs/houdini/shade/layering.html
- The Aqsis Team. 2015. Aqsis Documentation. https://www.aqsis.org/documentation/user_manual/index.html
- Andrea Weidlich and Alexander Wilkie. 2007. Arbitrarily Layered Micro-Facet Surfaces. GRAPHITE ’07, 171–178. https://doi.org/10.1145/1321261.1321292
- Philippe Weier and Laurent Belcour. 2020. Rendering Layered Materials with Anisotropic Interfaces. Journal of Computer Graphics Techniques (JCGT) 9, 2 (jun 2020), 37–57. http://jcgt.org/published/0009/02/03/
附录 A:使用 Co-shaders 的 PRMan RIB slot binding 示例
Display "test" "framebuffer" "rgb"
Projection "perspective" "fov" 40
Format 320 240 1
Translate 0 0 3
Rotate 0 1 0 0
Rotate 90 0 1 0
Scale 1 1 -1
WorldBegin
Shader "testslot" "010"
Shader "testslot" "020"
Surface "testbase"
AttributeBegin
TransformBegin
# resulting order : 010, 020, 000
# ASH ordering would be : 000, 010, 020
Shader "testslot" "000"
Translate 0 0 -0.5
Sphere 0.15 -0.15 0.15 360
TransformEnd
AttributeEnd
AttributeBegin
TransformBegin
# resulting order : 010, 020, 001 ordering
# ASH ordering would be : 001, 010, 020
Shader "testslot" "001"
Translate 0 0 0.5
Sphere 0.15 -0.15 0.15 360
TransformEnd
AttributeEnd
AttributeBegin
TransformBegin
# resulting order : 010, 020, 011 ordering
# ASH ordering would be : 010, 011, 020
Shader "testslot" "011"
Translate 0 -0.5 0
Sphere 0.15 -0.15 0.15 360
TransformEnd
AttributeEnd
AttributeBegin
TransformBegin
# resulting order : 010, 020, 021 ordering
# ASH ordering would be : 010, 020, 021
Shader "testslot" "021"
Translate 0 0.5 0
Sphere 0.15 -0.15 0.15 360
TransformEnd
AttributeEnd
WorldEnd附录 B:材质 Slot 绑定 API(Material Slot Binding API)
原生 USD 的材质绑定以 UsdRelationship 表达,并通过 Pixar 的 schema UsdShadeMaterialBindingAPI 进行编写。一个直接材质绑定如下(原文 Listing 3):
def Xform "Asset"
{
rel material:binding = </DustMaterial>
}Pixar 绑定机制的主要限制是:它不支持在同一 prim 位置(针对同一 material purpose)绑定多个材质。该设计也体现在 Hydra 对绑定材质的解析方式中:Hydra 使用 UsdShadeMaterialBindingAPI 的 ComputeBoundMaterial() 返回每个几何图元(给定 material purpose)解析后的单一材质。由于 Hydra 并不是我们管线中的硬性要求,我们决定用自定义编码方式,通过自研 API schema 来表达 Glimpse/ASH 的 bindings。
B.1 USD 中的 slot bindings
在自研 schema 中,我们尽量贴近原生 USD bindings:slot bindings 仍然用 UsdRelationship 表达,slot 名称被编码进 relationship 的名称中(见原文 Listing 4)。
def "brick_6141_91"
{
rel material:slotBinding:_000 = </LegoPlastic>
rel material:slotBinding:_050 = </DustMaterial>
}所有代表 slot binding 的 relationship 都以 material:slotBinding 命名空间开头,其余部分编码 slot 名称。关键细节在于:USD 的命名约定较严格。
- 任意
UsdProperty(包括UsdRelationship)的名字由一个标识符,或由多个以冒号分隔的标识符列表构成。 - 标识符若要有效,必须符合 USD identifier 规则:不能为空;必须以字母或下划线开头;且仅能包含字母、下划线或数字。
因此,我们把 Glimpse slot 名称编码为合法 USD identifier,采用以下步骤。以 slot override "050::000" 为例:
- 使用
:分割字符串:["050", "", "000"] - 把每个元素转换为合法 USD identifier:
["_050", "_", "_000"] - 用
:重新连接得到:"_050:_:_000"
这意味着:将 slot "050::000" 绑定到名为 "PlasticOverride" 的材质,会编码为(原文 Listing 5):
rel material:slotBinding:_050:_:_000 = </PlasticOverride>B.2 自定义 schema 的用法示例(Custom Schema Usage Example)
归根结底,用户想要的是在 USD 场景中以简单方式写入与读取 slot bindings。MaterialSlotBindingAPI 负责 slot 编码细节,让用户只需处理“规范化(canonical)”的 slot 名称即可:
prim = stage.DefinePrim('/Set', 'Xform')
material = UsdShade.Material.Define(stage, '/Set/Mat')
# Create an instance of the schema attached to your prim
bindingAPI = schemas.MaterialSlotBindingAPI(prim)
# Create a binding between 'prim' and 'material' on slot '000'
bindingAPI.Bind(material, '000') # creates relationship named 'material:slotBinding:_000'
# Get slot bindings expressed on 'prim'
bindingAPI.GetBindings()[0].getSlot() # return '000'
bindingAPI.GetBindings()[0].GetMaterialPath() # return '/Set/Mat'
bindingAPI.GetBindings()[0].GetMaterial() # return UsdShadeMaterial 'material'B.3 阻断关系(Blocking Relationship)
我们也需要能够“移除”某个 prim 上的 slot binding:这会影响该 prim(以及所有后代)上的绑定解析结果,强制忽略所有继承来的(该 slot 上的)bindings——除非在更低层级重新定义。MaterialSlotBindingAPI 使用 Attribute Block 来编码这种阻断行为。要实现这种结果,只需绑定一个无效的 UsdMaterial(原文 Listing 7):
bindingAPI = schemas.MaterialSlotBindingAPI(prim)
bindingAPI.Bind(UsdShade.Material(), '000')这将生成如下 relationship(原文 Listing 8):
rel material:slotBinding:_000 = None摘要
在过去 8 年里,Animal Logic 在其自研的路径追踪渲染器 Glimpse 中,一直使用自定义的材质定义与渲染技术——Animal Logic SHading System(ASH)。我们对比了 MaterialX、PRMan、USD/USDShade、MDL 等现有方案在材质绑定(material binding)与材质分层(material layering)上的能力与限制,并说明为什么我们的系统能提供其它着色解决方案与材质绑定/定义规范所缺失的特性与解法。我们主张现有开源项目应当支持真正的分层绑定、分层着色以及层级化赋值(hierarchical assignment);并进一步主张,这类方案应提供可控的排序规则,使分层机制能够充分覆盖典型制作场景与需求。文中给出并讨论了来自生产的例子,以及未来可研究的方向。
关键词
Survey,Shader Binding,Shading,Material Definitions,Layered Materials
1 引言
2013 年,Animal Logic 开发了自研路径追踪器 Glimpse:最初作为《与恐龙同行:电影版》(Walking with Dinosaurs: The Movie)的灯光预览工具,随后在《乐高大电影》(The LEGO Movie)中发展为完整的制作级渲染器。这一契机让我们得以重新思考渲染的诸多方面,其中包括:材质绑定与分层应当如何被定义与实现,并将过去数十年的经验教训纳入设计。
最终形成的系统——Animal Logic SHading System(ASH)——由一个规模不大的艺术家/技术美术与开发人员团队,在数月会议中共同设计,并首次用于影片《分歧者 3:忠诚世界》(Allegiant)。ASH 的目标是解决常见制作问题:例如材质分层、复杂度管理,以及帮助进行模块化材质构建(modular material construction);它至今仍在我们所有项目中持续使用。
本文讨论的两个核心概念是:
- 材质绑定(material binding):可渲染图元(renderable primitive)与其表面材质之间的连接关系。
- 材质分层(material layering):将多个 BSDF / lobe 组合成复杂材质的机制。
1.1 制作需求
ASH 的设计从一开始就围绕以下高层制作需求:
- 简单且灵活地对多个材质进行分层。
- 在不修改材质自身内容的前提下,在多个图元之间复用材质组件/功能。
- 对材质绑定中的单个层提供层级化赋值/传播(hierarchical assignment / propagation)。
- 分层复杂度不应显著拖慢渲染性能。
1.2 技术需求
为满足上述制作需求,我们的软件工程团队进一步明确了以下技术需求(这些需求组合起来即可完整覆盖制作侧需求):
- 对图元进行材质层的层级化赋值。
- 通过一个可赋值给图元的、有序且具名的材质数组实现自动分层;我们将该有序、具名的绑定机制称为 slot。
- 将 patterns 与 materials 清晰分离,使二者可独立并行开发,从而改善资产 lookdev 的迭代效率。
- 允许着色器之间进行连接,并通过按名称匹配自动绑定参数。着色器既可以在材质内部通过可复用子图连接,也可以在绑定系统内部通过我们称为 material overrides(材质覆写) 的机制连接。由此,一个 slot 可以作为另一个 slot 的功能扩展,并且这一切都直接由绑定机制支持。
- 允许用户在不修改着色器本身的前提下为其增加复杂度。例如:在整场景上增加一层灰尘。在 ASH 中,只需把一个 dust slot 赋值到场景根节点,并利用 slot 的排序确保它位于每个组合材质(composed material)的最上层即可。相比之下,在我们以往使用的一对一绑定材质系统中,这类需求往往需要对场景中所有着色器做繁琐的手工编辑:在每个材质中粘贴同一套 dust 节点网络,或依赖容易出错的自动化脚本批量注入。
- 艺术家对 Photoshop 分层(本质上是带显式顺序的 alpha 分层)的熟悉度,使得复现该工作流的“本质”非常有吸引力。
- 复杂的分层不应导致渲染时间高到不可接受。
- 避免参数混合式 uber shader 所带来的视觉伪影与物理不一致性;并允许在同一框架中正确处理金属表面、电介质表面与光泽表面。
- 提供丰富的材质响应,并将复杂的垂直分层(vertical layering)直接内建到材质响应中。
2 背景
2.1 材质绑定(Material Binding)
材质绑定(也常被称为材质赋值 / material assignment)在大多数情况下是一对一:一个材质绑定到一个图元。我们在设计绑定系统时,从 Pixar PRMan 的 co-shader 机制获得了大量启发;该机制允许把多个材质绑定到同一个可渲染图元。ASH 也支持类似的 many-to-one 绑定行为,并额外引入一种结构化的方法来解决“层/slot 的顺序(ordering)”。
在写作本文时,Pixar 的 Universal Scene Description(USD,已成为视效行业事实标准的场景描述系统)并不直接支持对同一图元赋多个材质(尽管我们通过自定义 schema 实现了支持,见附录 B)。我们希望本文能启发其它场景描述实现将“多材质赋值”作为一等概念来支持,或至少暴露出允许其以一等方式被支持的机制。
2.2 材质分层(Material Layering)
沿用 [Harrysson et al. 2021] 的术语,材质分层指把多个 lobe/BSDF 以**水平分层(horizontal,线性插值)与/或垂直分层(vertical,光学分层/optical layering,例如 [de Dinechin and Belcour 2022])**组合起来。传统实现通常把分层机制写进材质图本身(例如通过节点连接,见 [Pixar Animation Studios 2021]),因此“材质绑定”往往并不能定义任何分层或“额外功能(extended functionality)”。
ASH 设计的一个核心思想,是以更灵活、更贴近使用场景(usage-driven)的方式把这些概念结合起来。我们对既有术语做了少量扩展:
- 水平分层(见图 2a):可视为“Photoshop 分层”的着色器等价形式。现实世界里,表面经常由不同基本物质混合或覆盖构成:例如油漆覆盖水泥、磨损露出底层材质、贴花覆盖在其它材质之上等。实现上可以评估多个材质并用 alpha 分层进行混合,或用随机方式评估层;后者有非常直接的物理意义:现实中每条光子路径在某个交点只会“命中”某一种材质,混合是宏观统计效应。在 Open Shading Language(OSL)[Gritz et al. 2010] 中,常见做法是对 closures 做线性插值;而在 Glimpse 中我们执行随机评估(stochastic evaluation)。
- 垂直分层(见图 2b 与图 2c):有时也称“光学分层(optical layering)”。它将不同密度的电介质视作多层 coating。典型例子包括带清漆的涂料、精细的多层玻璃工艺与虹彩(iridescence)。垂直分层的额外复杂度在于:光会在每个层间界面发生反射与折射,需要系统跟踪并处理。高效渲染光学分层材质并不容易,近期研究界对此兴趣浓厚 [Belcour 2018; Jakob et al. 2014; Weidlich and Wilkie 2007; Weier and Belcour 2020]。这些工作主要关注垂直分层内部的光传输,而本文更关注分层材质的定义方式及其属性表达。我们进一步把垂直分层分为两类:
- 弱垂直分层(weak vertical layering,见图 2c):支持多个垂直堆叠的材质层,但穿过层的射线不一定支持折射、层间多次散射或物理准确的材质响应;例如某些系统可能仅以“染色(tint)”方式影响外观。
- 强垂直分层(strong vertical layering,见图 2b):支持折射、层间多次散射,并可能包含各向异性、层粗糙度等物理概念;可视为物理基础(physically-based)的垂直分层。

图 2:不同分层技术的可视化。射线与水平分层材质相交时,在给定表面点处只会受到某一层影响,具体取决于各层权重 (a)。垂直分层意味着射线在穿越层间界面时,可能被多个(甚至全部)层影响,即发生反射或透射。我们把在界面与层内部支持物理基础光传输的系统称为强垂直分层 (b);若射线穿过层时不发生散射等复杂传输,则称为弱垂直分层 (c)。
3 材质绑定:相关工作(Material Binding Previous Work)
与材质分层相比,材质绑定相关的既有工作相对有限;我们难以找到超出“标准一对一绑定”的大量创新例子。需要强调的是:当一个系统只支持一对一绑定时,分层机制往往被迫被封装到每个材质内部,或被隐藏在诸如 attributes / primitive variables 之类的场景描述数据中。这些“被隐藏的复杂度”对用户而言很难管理。我们过去使用过的所有系统,都在不同程度上存在同样问题。
3.1 Co-shaders
据作者所知,co-shaders 最早在 2007 年左右随 RenderMan 13.5 发布 [Pixar 2022a],是第一个把“单图元多材质绑定”引入并支持层级化赋值的公开机制。尽管在较新的 PRMan 版本中它已被弃用,co-shader 依然是一个很强的概念:它允许用户把材质功能划分为小而清晰的模块(co-shader modules),并由一个底层材质以某种方式编排这些模块形成聚合功能。若这些赋值还能层级传播,则可以把场景中的共通功能“推到”更高层级,从而避免重复的逐图元赋值。
ASH 的绑定机制在一定程度上可以用 co-shaders 模拟;我们在附录中的 RIB 示例(见附录 A 的 Listing 2)展示了这种可能。支持 RIB 与 RSL 的渲染器中,能够复现该示例的(非穷举)包括:3Delight [Illumination Research Pte Ltd. 2021]、AIR [Iverson 2014]、Aqsis [The Aqsis Team 2015]、Pixie [Arikan 2005]。渲染该示例将为每个图元产生与 Listing 2 注释中一致的 co-shader 顺序。
然而,如果我们希望得到某种严格的排序(例如为了讨论方便,假设按字母数字序排序),就要么需要在运行时排序(会给着色器评估带来不可接受的性能开销),要么依赖 Scene Filters。
3.2 Scene Filters
场景描述过滤器(scene description filters)是用户可编程模块:输入一段场景,输出经添加、修改或删除后的场景。因此,它们能用来实现各式各样的材质绑定机制。一个易于理解的例子是 PRMan 的 Ri Filter:允许以 C 或 Python 接口在渲染时修改场景。通过 scene filter 实现 slot 排序在技术上可行,但会对“跨工作室、跨渲染器共享场景”造成限制。
我们在不引入额外渲染时开销的前提下解决 slot 排序:在渲染器加载场景的 traversal 阶段对 slots 完成排序。若开放的场景描述格式将这种机制作为一等概念,会更有利于跨渲染器兼容性。
4 材质分层:相关工作(Material Layering Previous Work)
与材质绑定相比,材质分层在图形学研究界与工业界受到更多关注。这里我们回顾商业渲染器与开源渲染器常见的分层做法,并特别关注“水平分层”与“垂直分层”的支持方式。
**表 1(材质分层对比)**总结了若干系统的能力:
- 水平分层:由于复杂材质通常可通过材质图把若干硬编码基材质组合起来,因此各系统普遍提供“mix / blending”节点来描述水平分层。基于混合权重(blending weights),连接到 mixing node 输入端的材质会被组合为输出。公开文档可查的主流渲染器与材质标准几乎都提供 mixing nodes,例如 Arnold [Autodesk 2022]、Cycles [Blender Documentation Team 2022]、Katana [Pixar 2022b]、Mantra [SideFX 2022]、MaterialX [Harrysson et al. 2021]、MDL [NVIDIA Corporation 2019]、Mitsuba [Jakob 2014]、PBRT [Pharr et al. 2022]、PRMan [Pixar 2022b] 等。
关于 USD:UsdShade [Pixar Animation Studios 2017, 2022] 本身并不规定垂直或水平分层,而是把这些实现选择留给用户。usd-interest 论坛中有关于 layered materials 的讨论(例如:- https://groups.google.com/g/usd-interest/c/EGJMkTbTnDE/m/5JizUHQuAwAJ
- https://groups.google.com/g/usd-interest/c/_hEPO2Z3nzI/m/EzlaiWLFAwAJ
- https://groups.google.com/g/usd-interest/c/-pzQUZQP6p0/m/HKGnxQ5UBwAJ
),但截至本文写作时,UsdShade 并未对其作出具体的一等实现。
通过 mixing nodes 提供水平分层意味着:材质必须通过节点连接把“分层”编码进材质图;这使得分层结构的修改难以在场景层自由管理。多数情况下可通过谨慎的接口参数化缓解,但若要把某一层从一种节点类型换成另一种节点类型,仍难以避免手工重连图。
- 垂直分层:一些渲染器在预定义材质中提供垂直分层,例如 Katana 的 car paint 材质,或 Arnold 的 thin-film 材质节点(可施加到其它材质上)。Cycles、Mantra、Mitsuba、PBRT、PRMan 以及 ASH 也提供类似的“显式节点(explicit nodes)”。MDL 与 MaterialX 还允许通过 mixing nodes 组合材质来实现垂直分层,使得定义光学分层材质更灵活。MDL 目前支持弱垂直分层,而 MaterialX 看起来同时支持弱与强垂直分层。表 1 中以 (W) 标注弱垂直分层,其余默认认为是强垂直分层。
据我们所知,Glimpse/ASH 是唯一一个在绑定层(binding level)支持水平材质分层的系统。
| 渲染器 | 水平分层(Horizontal Layering) | 垂直分层(Vertical Layering) |
|---|---|---|
| Arnold | Mixing Shader Node | Explicit Nodes |
| Cycles | Mixing Shader Node | Explicit Nodes |
| Katana | Mixing Shader Node | Explicit Nodes |
| Mantra | Mixing Shader Node | Explicit Nodes |
| MaterialX | Mixing Shader Node | Mixing Shader Node |
| MDL | Mixing Shader Node | Mixing Shader Node(W) |
| Mitsuba | Mixing Shader Node | Explicit Nodes |
| Octane | Mixing Shader Node | Mixing Shader Node |
| PBRT v3 | Mixing Shader Node | Explicit Nodes |
| PBRT v4 | Mixing Shader Node | Explicit Nodes |
| RenderMan | Mixing Shader Node | Explicit Nodes |
| UsdShade | Implementation Defined | Implementation Defined |
| VRay | Mixing Shader Node | Mixing Shader Node |
| Glimpse/ASH | Binding Assignment | Explicit Nodes |
ASH 的关键特性之一,是对分层、mask、表面着色与置换(displacement)进行运行时随机评估(runtime stochastic evaluation)。这使我们能够用共享的材质定义高效地为多个资产做表面,而无需编辑既有材质图内部的节点连接或内容。传统上诸如“给整个场景加灰尘”或“针对资产中每个图元统一改变金属外观”之类的大规模改动,在 ASH 中由于其设计方式而变得相对轻松——这正是它为满足制作用例而生的原因。
Animal Logic 长期专注写实与物理基础渲染。因此,我们逐步远离基于 uber-shader 的方法:参数混合(parameter blending)会产生不自然的混合外观;相对地我们更偏好正确的金属高光响应与物理 Fresnel 项。由此我们需要一个支持真正物理基础“参数混合”的材质分层系统。我们用**随机混合(stochastic blending)**来实现:它把材质混合的物理性质建模为“多种混合材质以概率方式存在”。简言之,现实世界中每条射线/光子在某次交互时只会被一种材质反射或折射;水平分层因此是多个表面共同影响光传播后的统计聚合结果。我们直接建模这一点。
5 ASH
5.1 概览(Overview)
在 Animal Logic,我们把 ASH 作为着色系统集成进自研制作级路径追踪器 Glimpse。它在场景描述与渲染器之间提供一层抽象,用于材质赋值/绑定与材质图(material graphs)的表达。ASH 材质图会被处理并转换为 OSL 代码,用于运行时评估。
ASH 由三大组件构成:
- 面向“每图元多材质”的层级化组合式绑定(hierarchical multiple-material per-primitive scene-composition binding)。
- 用于定义着色图与连接的 C++ API,并基于 OSL 运行时 JIT 提供若干特性。 -(下文将在更细节处补充)在运行时对分层、mask、表面着色与置换执行随机评估,从而在保持工作流灵活性的同时控制性能成本。
在进一步解释之前,我们先给出贯穿 ASH 的关键术语与设计要点:
- 多重且层级化的材质赋值是 ASH 的基础绑定概念:一个图元所有具名材质赋值(下文称为 slots)会通过水平分层组合在一起。slots 在层级中传播,并在每个图元内部按字母数字序排序。
- **材质(在 ASH 中也称 substance)**以 OSL 节点图表示。每个 substance 最多可包含以下输出(我们称为 products)中的每种一个:surface(例如 BSDF)、displacement、normal(用于法线贴图)以及 mask(用于控制该 substance 的透明度)。
- slot 的 override 通过命名约定
X::Y实现:将Y的所有输出属性,按同名规则连接到X中对应的输入属性;从而依赖命名约定在不手工连线的情况下自动重接 substance 图,实现覆写功能。 - 在更高层级定义的 substance slot 可以在更低层级通过“重新定义同名 slot”被替换;同样地,把某个 slot 赋为空 substance(无关联图)会移除之前的赋值。
- displacement 与 bump mapping 从底层到顶层评估并组合;每个 slot 的 normal mapping 在 displacement 与 bump 之后立即评估。
5.2 层级化赋值(Hierarchical Assignment)
在材质赋值流程中,每个 substance 会被赋给一个具名 slot。赋值可以发生在场景中任意图元路径上,并会沿层级传播(即:在合适的场景结构中,对 group 与 transform 进行赋值也是被支持的)。当渲染器在场景中遇到一个可渲染图元时,该图元的最终“单一材质(singular material)”会被组合为:从所有祖先节点继承而来的、在该点可见的所有 substance 赋值的并集。slot 名称会被排序以定义层的先后顺序;在我们的实现中使用字母数字序(alphanumeric)。理论上可以用其它排序方法,但字母数字序对我们而言一直表现良好。
通过 slot 赋值把多个 substance 组合起来,是该系统效用最直观之处:在场景中视觉相近的一组可渲染元素,可以用一次赋值完成大范围表面;而局部细节则可在更深层级通过“新增层(layering)”或“替换(replacement)”的方式逐步细化。
- 替换(replacement):当某个层级对一个 slot 名称进行了赋值,而该 slot 名称在更高层级已经存在赋值时,较低层级的赋值会替换掉较高层级的赋值。
- 移除(deactivate / removal):替换还可用于“停用”更高层级的 slot;这通过把某个 slot 赋为空 substance(没有关联的 substance graph)实现。
- 新增层(new layer):当在当前可渲染图元之上的任意层级,出现一个此前未出现过的新 slot 名称赋值时,就会新增一层。
下面用一个简化的 pseudo-USD 片段来说明层级化赋值(原文 Listing 1)。
def "ObjectA"
{
rel slot:000 = </SubstanceA>
def "ObjectB"
{
// applies a new layer on top of '000'
rel slot:010 = </SubstanceB>
def "ObjectC"
{
// replaces the previous slot '000'
rel slot:000 = </SubstanceC>
}
def "ObjectD"
{
rel slot:000 = null // removes the previous slot '000'
}
}
}由该示例可得每个对象组合后的材质为:
- ObjectA:SubstanceA
- ObjectB:SubstanceB 覆盖在 SubstanceA 之上
- ObjectC:SubstanceB 覆盖在 SubstanceC 之上
- ObjectD:SubstanceB
5.3 随机评估(Stochastic Evaluation)
为了降低评估多层 substance 的代价,Glimpse 基于 mask product 的输出执行随机评估(stochastic evaluation)。对每个 integrator sample,我们会评估所有 slots 的 mask products,得到每层的 opacity。随后自顶向下地处理这些 opacity 来计算每层的权重(weights)。
如果这些权重之和小于 1,则剩余权重被视为该图元的“透明”权重。例如:考虑 Listing 1 中 ObjectB 的情况,SubstanceB 位于 SubstanceA 之上。令 SubstanceA 与 SubstanceB 的不透明度分别为 $m_A$ 与 $m_B$,则权重为:
- (1) $w_B = m_B$
- (2) $w_A = m_A \cdot (1 - m_B)$
- (3) $w_T = 1 - w_A - w_B$
其中 $w_T$ 是图元“完全透明”的隐式权重。在评估材质时,会按 $w_A$、$w_B$ 与 $w_T$ 的比例随机选择 SubstanceA、SubstanceB 或透明情况中的一种进行采样与着色。
5.4 覆写(Overrides)
override 是一种 substance graph:它提供输出属性,供其所覆写的 substance 消费。你可以把它看成:提供一组“pattern + 输出参数”,去覆写另一个图中既有的输入参数。
我们在图 3 中示意 override 的工作方式:假设某个 substance 被赋给层 slot "010",并且该 substance 含有输入参数 "x"、"y"、"z"。这些参数既可以直接设置为常量,也可以通过一个带 "x"、"y"、"z" 输出的 override 来驱动;该 override 被赋给 slot override "010::000"。当 ASH 组合最终的材质层 "010" 时,所有赋给 "010" 的 overrides(此例为 "010::000")都会尝试把自己的输出连接到 "010" substance 中任何同名输入上;只要参数名匹配,就会自动建立连接。若多个 overrides 为同名参数提供输出,则层级更“高”的 override 输出将获胜。
从最基本层面,这使得 substance 的输入值可以在一个流程中被定义,而在另一个流程中由包含纹理、程序纹理(procedurals)等复杂图的 overrides 来驱动。我们会建立命名约定,以确保覆写过程行为符合预期。

图 3:Overrides 示例。
5.5 自定义通道(Custom Channels / AOVs)
ASH 可以通过在 substance graph 中放置一个 customChannel 节点来定义自定义 AOV(Arbitrary Output Variables):你既可以为它提供常量输入值,也可以用计算结果驱动它。该节点会被识别为 custom channel,并自动暴露给 Glimpse。针对 Glimpse 支持的每种输出数据类型,都存在相应 custom channel 节点,例如 customChannelColor、customChannelInteger 等。
substance 可以计算任意数量的自定义输出通道,而 substance overrides 也可以在任何时候把它们添加到既有 substance 上。customChannel 节点还有一个特殊之处:即使它不提供输出连接,它仍会被 OSL shader 编译器保留。通常情况下,不提供连接输出的节点会在图裁剪(graph truncation)时被删除。
5.6 Houdini 用户界面(Houdini User Interface)
我们在 SideFX Houdini 的 LOPs UI 中完成 USD stage 的 surfacing 与 look development。标准的 Houdini material library 节点提供承载 USDShade materials 的容器;这些 material library 节点中的 subnet 则包含着色器本身。ASH substance graphs 在 VOP context 中编辑。ASH 会从 Glimpse 的内部节点定义自动生成 VOP 节点,并在 Glimpse ASH VOP UI 中过滤这些节点类型,以便只显示有效的 substance 节点。
一个典型的 ASH 图界面见图 4。注意:该图提供的所有 products 都会连接到 Houdini VOP 的 suboutput 节点以汇集输出。在这个例子中,surface product 是 glimpse_glossy1,displacement product 是 glimpse_displacement1,没有 normal product,而 mask product 是 glimpse_mask1。

图 4:ASH 图在 Houdini 中的集成示例。
层级化 slot 赋值由一个专用 UI 处理,它提供了一种新的视觉参照,帮助艺术家理解这些赋值在场景中的应用方式。该 UI 会展示 USD stage 的层级结构,并在每个图元处用图标表示该点 slot 的聚合情况。图 5 中的图标用于可视化层级化 slot 赋值:新 slot 赋值(图 5a)、继承的 slot 赋值(图 5b)、新 override 赋值(图 5c)、继承的 override 赋值(图 5d)。

图 5:Houdini 中 ASH slot 赋值图标:slot 已赋值 (a);slot 继承 (b);override 已赋值 (c);override 继承 (d)。
该 UI 的一个制作示例见图 6。它包含 3 列:Scene Graph Path、Slot name、Material Prim Path(即:在该 scene path 上被赋给该点的 substance 或 override 的路径)。在每个图元处,slots 会始终显示,并按顺序从**顶端的基础层(base layer)到底端的顶层(top layer)**排列。要定位某个 slot 或 override 具体在哪里被赋值,用户只需查看哪个图标是“亮”的:亮图标表示应用点(application point),暗图标表示从父层级继承。
在图元级别从左到右阅读一行时,你还可以从最低层到最高层看到 slot 与 override 的赋值情况。这意味着即使层级被折叠,用户也能一眼看出 substance 赋值是什么;随后再通过展开层级查看每个 slot 在哪里被赋给了哪一个 substance 来深入追踪细节。我们在所有 DCC 工具中使用过该 UI 的多种变体,它们都能很好地“一眼表达”ASH 的层级化赋值。

图 6:Houdini Slot Assignment UI(制作示例)。
6 制作示例(Production Example)
下面我们展示如何在真实制作资产表面流程中使用本文介绍的概念。我们选择 Animal Logic USD ALab 中的一个示波器(oscilloscope)资产作为例子。以图 7 作为起点,对比 Houdini 视窗预览与最终 Glimpse 渲染结果。

图 7:制作示例概览:视窗预览与最终渲染结果对比。
6.1 默认 substance(Default Substance)
在一个全新的场景中,如果没有任何赋值(见图 8),所有几何都会以默认的白色 diffuse product 渲染。

图 8:默认 substance。当在 DCC 中未进行任何 slot 赋值时,资产会以默认的白色 diffuse 渲染。
6.2 层级化分层(Hierarchical Layering)
我们使用层级化分层(图 9)来建立该资产的基础 substance 层:在资产根节点处,将 generic01(一个典型的 glossy shader)以基础层的形式赋给 slot "000"。由于我们的默认 glossy product 是中灰,这会产生偏暗的灰色外观。注意赋值 UI 会沿着场景层级显示继承关系。

图 9:层级化分层。当在资产根节点对某个 slot 进行赋值(此例为 glossy substance)时,该赋值会层级化应用到所有子节点。
6.3 Substance 覆写(Substance Overrides)
substance 的输入值可以通过为其赋值 override 来覆写。在图 10 中,generic01_override01 提供了颜色与粗糙度贴图(colour/roughness maps)。使用 ASH overrides 的一个显著优势是:基础库 substance generic01 可以在任何时候更新,而 override 保持不变。这自然形成了一种分工:一部分团队负责创建基础 substances,另一部分团队负责驱动输入值;二者可以并行工作。若把这些工作都塞进同一个 substance 图中,就很难获得如此清晰的边界与并行能力。

图 10:Substance Overrides。在资产根节点赋值一个 substance override,提供基础颜色与粗糙度贴图 pattern,用于驱动此前赋值的 glossy substance。
在图 11 中,我们在更深的层级加入第二个金属 substance metal_stainless01 及其 override metal_stainless01_override,它们被赋给一个“更高”的 slot,但由于它们是在层级更深处赋值,因此只影响对应的子图元。请注意与图 10 相比,旋钮后方的金属元件外观发生了变化。

图 11:Substance Overrides。在层级更深处给更高的 slot 赋值新的金属 substance(及其 override),只作用于旋钮后方的金属元件。
6.4 层级化替换(Hierarchical Replacement)
对于需要与 generic01 不同 substance 的图元,我们可以在更深层级对同一个 slot "000" 进行赋值,从而替换原本的 generic01。图 12 展示了示波器外壳与图 11 的差异:它不再使用 glossy product,而改为金属 product metal_stainless01。

图 12:层级化替换。通过在更深层级对与 glossy 相同的 slot 名称赋值金属 substance,从而在外壳处用金属替换既有 glossy substance。
6.5 覆写复用(Override Sharing)
由于 overrides 本身也是 substance graphs,它们可以被复用并赋给多个图元上的多个 slots。这使得我们能非常快速地同时改变许多图元的颜色等属性。图 13 中旋钮变为红色:override plastic_opaque01_red 只覆写“红色”这一项,但底层 substance 类型保持不变(仍为 glossy)。
同时,dirt 与 dust 等 substances 可以作为额外层叠加在基础层之上,并且自带 masking,从而自动只出现在遮挡区域或表面顶部。图 14 中,通过 dirt01 substance 与 dirt01_override01 override,在示波器表面叠加了灰尘、磨损等效果(与图 13 对比可见)。

图 13:覆写复用。一个 override 可以被赋给许多不同部件,从而轻松提供 patterns(例如红色旋钮)而无需改变 substance 类型(仍为 glossy)。

图 14:覆写复用。在资产所有元素之上叠加一层 dirt(及其 override)。
6.6 覆写分层(Override Layering)
由于 overrides 也通过排序后的 slots 赋值,因此它们可以像 substances 一样分层。例如在图 15 中,我们在层级的另一处改变了继承的 dirt 颜色,但保留了原始的 dirt mask。该 slot 排列为:
"050"-dirt01(继承)"050::010"-dirt01_override(继承)"050::020"-dirt01_casing_override(对象特定)
结果是在图 15 中,示波器外壳的 dirt 颜色比图 14 更浅。

图 15:覆写分层。通过在更深层级应用一个 substance override,降低外壳金属上 dirt 层的 pattern 强度。
6.7 Slot 移除(Slot Removal)
slot 赋值可以通过赋值“空 slot”来移除。例如要移除继承的 dirt01 substance,我们可以在 frontPanel_M_geo 图元上对 slot "050" 赋一个空 substance。图 16 中前面板的 dirt 消失,但资产其它部位仍保留 dirt。

图 16:Slot 移除。通过在更深层级对此前的 dirt slot 名称赋空 substance,从而从前面板移除 dirt。
6.8 在 overrides 中添加自定义 AOV(Adding Custom AOVs in Overrides)
AOV 也可以通过 slot overrides 添加。正如 1.2 节所述,substance overrides 本质上就是标准 substance graphs:其中的节点会被添加到它们所目标的 substance 上,因此任何有效节点(例如 AOV 节点)都可以被加入到主 substance 中,就好像它们一直就在那个 substance 图里一样。这样我们就能在不重连材质图、也不改任何代码的前提下,为既有 shader 添加新功能。
图 17 中,一个自定义 AOV(glimpse_customChannelColor1)被添加到 override 中,用于输出“dirt substance 是否存在”的 AOV。由于 substances 会自动分层,该 AOV 只需输出值 1;Glimpse 会根据该 slot 层被采样的概率对其进行随机采样。最终得到的 AOV 会与包含该 AOV 的 substance 的分层与 masking 结果一致。我们发现这一机制对快速生成合成所需额外数据非常有用,并且对制作流程影响很小,因为无需修改或重新发布任何库资产。

图 17:在 overrides 中添加自定义 AOV。substances 与 overrides 都可以提供自定义 AOV;此例中 dirt substance 提供一个 matte 以便后续使用。
6.9 贴花与投影(Decals and Projections)
贴花(decal)可以通过“带 mask 的额外 substance”作为额外 slot 层来实现。在图 18 中,侧面的贴纸被加入为一个额外的 substance slot 层。该贴纸是一个 coated paper 材质,并通过一个投影(projected)mask override 来控制覆盖区域。它被分层在基础金属层之上,但在 dirt、paint 与 dust 层之下。

图 18:贴花与投影。在基础金属层与顶部 dirt 层之间,为一个 slot 赋值新的 decal substance 及其 override。
6.10 分层置换/凹凸/法线(Layered Displacement/Bump/Normal)
substance 的 displacement / bump / normal products 可设置为 add、mix 或 none 模式。add 与 mix 模式的作用范围会受 substance mask 限制;none 模式则忽略所有 masking 与分层,直接对所有地方生效。
add 与 mix 的区别在于:在 mix 模式下,displacement 还会以与 surface product 的 substance 分层相同的方式自顶向下混合(也即:更高 slot 会遮罩更低 slot);而在 add 模式下,贴花 displacement 会“叠加”在底层金属 displacement 之上。图 19 展示了 add 与 mix 两种选项:贴纸 displacement 分别表现为被底层材质遮罩(masked by)或位于底层材质之上(on top of)。

图 19:分层置换——Add 与 Mix 模式。置换、凹凸与法线 products 可选择性地在更低层之上进行混合;此例中 decal 的置换可以选择累加在底层金属置换之上,或被底层材质遮罩。