In UsdShade, all shaders are UsdPrims or just "prims". However, in deference to the larger body of technical discourse on shading, we will refer to them as "nodes" in this discussion.
Shading nodes should be encapsulated in a containing object, and are not generally used in isolation.
Shading networks can be organized into coherent packaged units (UsdShadeNodeGraph), with their own public parameters exposed and connected to the internal nodes. In this scenario, the UsdShadeNodeGraph is a parent or ancestor prim (UsdShadeNodeGraph can be nested) to all of the UsdShadeShader prims in the network, and serves as the point of encapsulation - the UsdShadeNodeGraph prim can then be referenced into other, larger networks as a building block, with its entire network intact. When referenced into larger networks, NodeGraphs can also be instanced so that they appear as a single prim in the network, and can be processed more efficiently when referenced from multiple locations.
If the network of shading nodes is directly consumable as a "shader" of a type known to some client renderer (e.g. a surface shader), then the encapsulating parent/ancestor should be declared as a UsdShadeMaterial, which is a container that can also be bound to geometries or collections. Materials can also be reused and instanced, retaining the same network but allowing top-level "Material Interface" parameters to be authored uniquely.
Containers vs Primitive Shading Nodes
Containers are prims that are designed to have child prims, like UsdShadeMaterial and UsdShadeNodeGraph, and that encapsulate parts or a whole network. These containers are differentiated against non-containers, which are are shader nodes like UsdShadeShader prims.
Containers usually define an interface that establishes input and output attributes, which allows exposing functionality of the encapsulated part of the network. With such an interface definition they can be treated in many ways like a primitive shading node, but have an implementation that can be inspected in Usd.
Exposing parameters on containers
To expose a parameter to the container, we use the same mechanism that connects nodes.
# Expose a parameter to the public interface
internalPort = upstreamShader.CreateInput(
'internalPort', Sdf.ValueTypeNames.Float)
exposedPort = material.CreateInput(
'ExposedPort', Sdf.ValueTypeNames.Float)
exposedPort.Set(1.0)
internalPort.ConnectToSource(exposedPort)
Which will yield a public interface parameter called 'ExposedPort' on the UsdShadeMaterial called 'MyMaterial', and set its default value to 1.0
To expose an output of a node network as an output of a NodeGraph, or as a "terminal output" of a Material, we again use the same connection API, except that now we are connecting an Output to another Output (in effect, forwarding the Output from a node to its encapsulating container):
# The output represents the result of the shader's computation. For
# complex types like "surface illumination" we use the type Token as
# a standin for the type specific to the renderer
outPort = surfaceShader.CreateOutput(
'out', Sdf.ValueTypeNames.Token)
surfaceTerminal = material.CreateOutput(
'surface', Sdf.ValueTypeNames.Token)
# For outputs, it is the container's Output that connect's to the Node's
# output
surfaceTerminal.ConnectToSource(outPort)
Which will yield a public interface parameter called 'ExposedPort' on the UsdShadeMaterial called 'MyMaterial', and set its default value to 1.0
#usda 1.0
def "Model"
{
def "Materials"
{
def Material "MyMaterial"
{
token outputs:surface.connect =
</Model/Materials/MyMaterial/Surface.outputs:out>
def Shader "Surface"
{
token outputs:out
}
}
}
}
Connectability Rules for UsdShade Types
As noted above, encapsulation is critical to UsdShade connectability rules, with different UsdShade nodes providing appropriate connectivity rules described below:
UsdShadeShader: Inputs can be connected to any Input or Output of any other shader or NodeGraph encapsulated by the same nearest-in-namespace encapsulating NodeGraph or Nodegraph-derived container. Outputs cannot be connected.
UsdShadeNodeGraph: Inputs follow the same rule as Shaders. Outputs can be connected to any Output on a prim (Shader or NodeGraph) encapsulated by the NodeGraph itself, or to an Input of the same NodeGraph itself, creating a "pass through" connection.
Default behavior for NodeGraph-derived Types (e.g. UsdShadeMaterial) Inputs and Outputs follow the same rule, which is that they can be connected to any Output on a prim (Shader or NodeGraph) encapsulated by the Material itself. Note that "pass through" connections are not allowed for Nodegraph-derived container nodes.
Any new or derived Typed or single-apply API schema can register its own UsdShadeConnectableAPIBehavior to customize connectivity rules. It can also specify, in its extraPlugInfo customData, the isUsdShadeContainer and requiresUsdShadeEncapsulation booleans to customize those aspects of behavior without needing to provide a UsdShadeConnectableAPIBehavior implementation.
Behavior defined on an authored API schemas, wins over
Behavior defined for a prim type, wins over
Behavior defined for the prim's ancestor types, wins over
Behavior defined for any built-in API schemas.
If no Behavior is found but an api schema adds providesUsdShadeConnectableAPIBehavior plug metadata then a default behavior is registered for the primTypeId, with its isContainer and requiresEncapsulation driven by extraPlugInfo metadata
Note that interface-only connections can only happen between inputs and source which have "interfaceOnly" connectivity.
Connections and Dataflow in UsdShade
UsdShade uses UsdAttribute connections both to indicate dataflow from shading node outputs to inputs, and to indicate pre-rendering propagation of values authored on UsdShadeNodeGraph and UsdShadeMaterial inputs to shader node inputs. In USD, connections (and relationships) are authored on the consumer, and target the source or producer. Therefore, data in a UsdShade network flows from a connection's target to its anchor. To reliably translate UsdShade networks for consumption by renderers, we need to establish a few rules about how values propagate in the face of connections.
Valid Shader Connections Win Over Input Values
When an input on a shading node has both an authored value (default or timeSamples), and a connection to an output on another shading node, then the connection alone is transmitted to the renderer - the authored value is irrelevant. Connections that target an output that does not exist in the containing Material are ignored; if the connected input has an authored value, then in this case, and this case alone, we pass the value to the renderer and ignore the connection.
In the following example, we will provide values to the renderer for inputs valueOnly (2) and brokenConnection (4), while informing the renderer of a connection between validOutput and connected, ignoring the value authored of 42 on connected.
When we create inputs on NodeGraphs or Materials to serve as "public interface" for shading properties, it is common to create an appropriately-typed attribute, but not provide a default value for it. When USD is the document for a material shading network, this "uninitialized interface attribute" allows the Material to continue to receive updates to published shaders made available through the SdrRegistry long after the Material has been created. Why? Because of the first rule of interface value propagation:
If a Material or NodeGraph input provides no value, and one or more of its shader's inputs connects to the interface attribute, then the value supplied to the renderer for that shading input should be whatever value is authored on the shader input, or if none is authored, then we emit no value to the renderer, indicating it should simply follow the shader implementation's own default value.
NodeGraphs can be embedded inside Materials, and also as nested components inside other NodeGraphs. Because of this nestability, it is posible that a deeply embedded shader node input may need to travel several connection hops to find an interface attribute that provides a value for it to use. This leads to the second and final rule of interface value propagation:
If a shader node input is connected to a containing NodeGraph input that is in turn connected to an outer-containing NodeGraph or Material, it is the outermost authored input default in the connection chain that provides the shader input's value. This allows the "user" of a NodeGraph to always be able to drive its inputs from its own public interface.
Putting these two rules together, in the example below, we expect the following values to be passed to the renderer for each shader input:
spOne = 4, because neither of the interface attributes in its connection chain supply a value.
spTwo = 14, because matInterfaceTwo provides the strongest opinion, as the outermost value-provider in the connection chain.
spThree = 64, because only its directly-embedding NodeGraph's interface attribute provides a value stronger than its own default.
NodeGraphs also define outputs to declare the signals that are provided for the rest of the network. From the outside, which is where the NodeGraph is connected to other shading nodes or NodeGraphs, the outputs behave conceptually like those on shading nodes. On the inside of the NodeGraph the outputs are connected to outputs of nested shading nodes or nested NodeGraphs or they can be connected to input attributes on the same NodeGraph as a pass through mechanism.
In the example below we have a NodGraph with two inputs (ngPassThruIn and ngToModifyIn) and two outputs (ngPassThruOut and ngModifiedOut). ngPassThruIn is sent straight to ngPassThruOut without modification, which means it is essentially forwarding connections and effectively result1 is connected to input1 directly. The input of ngToModifyIn is fed to the Modifier shading node, which sends a modified result to ngModifiedOut, which effectively models result2 -> toModify (on Modifier) and modified -> input2.
This handles both the rules that connections win over default values and the forwarding logic to the interfaces of containers, like Materials or NodeGraphs. This utility function can be seen as a way to turn physical connections into logical ones. This can be useful for renderers ingesting a Material network because generally the logical connections are their only concern. This conversion "flattens" the network into a simple network that only contains Shaders with their respective input values and connections.
There are situations where only valid output attributes of shading nodes are desired and hence we have a mode, where it will not report input or output attributes on containers that carry default values. This mode can be activated by the optional shaderOutputsOnly flag.
Attributes in Usd can have not just a single connection target, but potentially multiple attributes they target. UsdShade supports having multiple connections for Inputs and Outputs (of containers, only), which means that UsdShadeUtils::GetValueProducingAttributes also handles these scenarios. The function is essentially performing a depth first connection tracing, which means that when a multi-connection is encountered along the path the tracing splits and potentially multiple source attributes are found. Note that invalid connections targets are skipped. The function will report all valid upstream attributes, which can be a mix of Inputs with default values and Outputs of Shader nodes. It is up to the client to resolve and report any inconsistencies based on what is supported in the target rendering system.
A USD file containing UsdShade-based shader definitions must adhere to the following rules, in order to produce valid SdrShaderNode s in the shader registry:
Every concrete shader prim at the root of the composed UsdStage should represent a new and complete shader definition. Inherits, references and other composition arcs may be used to avoid redundant scene description.
The shader prim's name becomes the unique identifier of the corresponding shader node in the registry. A shader's identifier is a concatenation of the
family name of the shader,
any type variations pertaining to the shader and
the shader version, which can contain one or two ints representing the major number and an optional minor number.
The type variations and shader version are optional parts of a shader identifier (i.e. not all shader identifiers may include them). If present, the different parts of the identifier are delimited by an underscore. Using UsdShadeShaderDefUtils::SplitShaderIdentifier, a shader's identifier can be split into the family name, implementation-name of the shader node (which includes the family name and the type information), and the shader version. For example:
Shader Prim Name
Family Name
Implementation Name
Version (major.minor)
MultiTexture
MultiTexture
MultiTexture
<empty>
MultiTexture_3
MultiTexture
MultiTexture
3
MultiTexture_float2
MultiTexture
MultiTexture_float2
<empty>
MultiTexture_float2_3_1
MultiTexture
MultiTexture_float2
3.1
The info:id attribute value of the shader, if authored, must match the name of the shader prim (i.e. the identifier of the SdrShaderNode).
The info:implementationSource of the shader must be UsdShadeTokens-> sourceAsset. There must be one or more "info:SOURCE_TYPE:sourceAsset" attributes that point to resolvable shader implementations for different source types (eg, glslfx, OSL etc.).
Shader prims, their inputs and outputs can contain sdrMetadata values meant to be recorded in the shader registry. The keys in the sdrMetadata dictionary correspond to the keys in SdrNodeMetadata and SdrPropertyMetadata. The only exceptions are as follows:
defaultInput metadatum on shader inputs gets translated to a more obscure key value of __SDR__defaultInput (which is the value of SdrPropertyMetadata->DefaultInput) in the metadata dictionary recorded by SdrRegistry.
Setting sdrMetadata["primvarProperty"]="1" on a shader input implies that the input names a primvar to be consumed by the shader. This causes '$' + inputName to be included in the SdrShaderNode->Primvars metadata on the SdrShaderNode. Note that it's not translated to metadata on the property itself.
connectability metadata authored on UsdShadeInputs gets translated to SdrPropertyMetadata->Connectable. Connectability value of "interfaceOnly" is converted to connectable="0". Connectability value of "full" is converted to connectable="1".
SdfAssetPath (or asset) valued shader inputs are automatically tagged with sdr metadata SdrPropertyMetadata->IsAssetIdentifier="1".
sdrMetadata["swizzle"] is metadata that can be specified for properties in SdrShaderPropertyoutput definitions that describes the component(s) of the full color/vector output value produced by the shader property, and is necessary for shading systems that rely on dynamic code generation rather than self-contained shader-objects/closures. swizzle metadata is not meant to ever appear in user documents, and does not provide the ability to swizzle data on input connections.
doc = """Fallback value to be returned when fetch failed."""
sdrMetadata = {
# This gets translated to SdrPropertyMetadata->DefaultInput="1"
# on the "fallback" SdrShaderProperty.
token defaultInput = "1"
}
)
float outputs:result
}
Using Shader Networks with Geometry
Imageable prims bind to a UsdShadeMaterial to subscribe to the "look" described by the material's shading networks. Prims that need to bind to materials must apply the UsdShadeMaterialBindingAPI schema, otherwise the bindings will not be used by renderers. Material bindings inherit down the prim namespace and come in two forms:
Direct bindings, in which a prim directly names (via relationship) the material it wants to bind.
Collection-based bindings, in which a collection identifies a set of prims, and the binding (again, a relationship) names both the collection and the material to which the collected prims should be bound.
Additional characteristics of the binding, such as the "material purpose" of the binding or the binding strength for collection-based bindings, can be specified. See UsdShadeMaterialBindingAPI for more details on material bindings and how bindings get resolved.