/*! \page usdShade_page_front UsdShade : USD Shading Schema \if ( PIXAR_MFB_BUILD ) \publicLib \mainpage UsdShade : USD Shading Schema \endif UsdShade provides schemas and behaviors for creating and binding materials, which encapsulate shading networks. \tableofcontents # UsdShade Networks {#UsdShadeNetworks} UsdShade provides schemas and behaviors for creating shading networks (UsdShadeNodeGraph) and materials (UsdShadeMaterial). The networks are composed of UsdShadeShader objects, as well as other UsdShadeNodeGraph. Objects in a network are connected together and to their encapsulating Material using the UsdShadeConnectableAPI schema, which allows one to create UsdShadeInput and UsdShadeOutput (which are UsdAttribute schemas), and *connect* them using [UsdAttribute connections](http://openusd.org/docs/api/class_usd_attribute.html#af8eaf3216d67a143923b65465eac881a). Here's a python example. ~~~~~~~~~~~~~{.py} # create material materialPath = Sdf.Path('/Model/Materials/MyMaterial') material = UsdShade.Material.Define(stage, materialPath) # create shaders downstreamShader = UsdShade.Shader.Define( stage, materialPath.AppendChild('Downstream')) upstreamShader = UsdShade.Shader.Define( stage, materialPath.AppendChild('Upstream')) # Connect inputPort = downstreamShader.CreateInput( 'DownstreamInput', Sdf.ValueTypeNames.Float) inputPort.ConnectToSource(upstreamShader, 'UpstreamOutput') ~~~~~~~~~~~~~ This will yield a material with two connected nodes. ~~~~~~~~~~~~~{.usd} #usda 1.0 def "Model" { def "Materials" { def Material "MyMaterial" { def Shader "Downstream" { float inputs:DownstreamInput.connect = } def Shader "Upstream" { float outputs:UpstreamOutput } } } } ~~~~~~~~~~~~~ # Encapsulation and Sharing {#UsdShadeEncapsulation} \note 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 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](http://openusd.org/docs/USD-Glossary.html#USDGlossary-Instancing) 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. To expose a parameter to the container, we use the same mechanism that connects nodes. ~~~~~~~~~~~~~{.py} # 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 ~~~~~~~~~~~~~{.usd} #usda 1.0 def "Model" { def "Materials" { def Material "MyMaterial" { float inputs:ExposedPort = 1 def Shader "Downstream" { float inputs:DownstreamInput.connect = } def Shader "Upstream" { float inputs:internalPort.connect = float outputs:UpstreamOutput } } } } ~~~~~~~~~~~~~ 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): ~~~~~~~~~~~~~{.py} # 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 ~~~~~~~~~~~~~{.usd} #usda 1.0 def "Model" { def "Materials" { def Material "MyMaterial" { token outputs:surface.connect = def Shader "Surface" { token outputs:out } } } } ~~~~~~~~~~~~~ # Connections and Dataflow in UsdShade {#UsdShadeConnections} 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 ## {#UsdShadeConnectOverInput} 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_. ~~~~~~~~~~~~~{.usd} #usda 1.0 def "Model" { def "Materials" { def Material "MyMaterial" { def Shader "Downstream" { float inputs:brokenConnection = 4 float inputs:brokenConnection.connect = float inputs:connected = 42 float inputs:connected.connect = float inputs:valueOnly = 2 } def Shader "Upstream" { float outputs:UpstreamOutput } } } } ~~~~~~~~~~~~~ ## Resolving Interface Connections ## {#UsdShadeResolvingInterface} When we create inputs on NodeGraphs or Materials to serve as "public interface" for shading properties, it is common to _create_ an approrpiately-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 SdrShaderRegistry 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. ~~~~~~~~~~~~~{.usd} #usda 1.0 def "Model" { def "Materials" { def Material "MyMaterial" { float inputs:matInterfaceOne float inputs:matInterfaceTwo = 14 float inputs:matInterfaceThree def NodeGraph "Package" { float inputs:ngInterfaceOne.connect = float inputs:ngInterfaceTwo = 28 float inputs:ngInterfaceTwo.connect = float inputs:ngInterfaceThree = 64 float inputs:ngInterfaceThree.connect = def Shader "EmbeddedInNG" { float inputs:spOne = 4 float inputs:spOne.connect = float inputs:spTwo = 5 float inputs:spTwo.connect = float inputs:spThree = 6 float inputs:spThree.connect = } } } } } ~~~~~~~~~~~~~ # UsdShade Based Shader Definition {#UsdShadeShaderDefinition} UsdShade has an NdrParserPlugin (\ref UsdShadeShaderDefParserPlugin) that enables shader definitions to be encoded as USD scene description using the schemas available in UsdShade. A discovery plugin can open a USD stage containing shader definitions and populate the shader registry with nodes using the API \ref UsdShadeShaderDefUtils::GetNodeDiscoveryResults(). 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 \ref 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, - if the shader prim is named "MultiTexture", the family name of the SdrShaderNode will be "MultiTexture". The corresponding shader-node's implementation name will also be "MultiTexture" and its version will be empty. - if the shader prim is named "MultiTexture_float2", the family name of the shader will be "MultiTexture" and its implementation name will be "MultiTexture_float2". Its version will be empty. - if the shader prim is named "MultiTexture_3", the family name of the shader will be "MultiTexture". It's implementation name will also be "MultiTexture" and its version will be 3. - if the shader prim is named "MultiTexture_float2_3_1", the family name of the shader will be "MultiTexture". The implementation name will include the type information and be set to "Primvar_float2". - 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 \ref SdrNodeMetadata and \ref 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 SdrShaderProperty output 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. - sdrMetadata["implementationName"] specifies the name that will be returned by SdrShaderProperty::GetImplementationName(). Here's an example shader definition file with comments explaining the various bits. ~~~~~~~~~~~~~{.usd} #usda 1.0 # The prim name becomes the SdrShaderNode's identifier. def Shader "Primvar_float_2" ( doc = "Version 2 of a Primvar node that outputs a float" sdrMetadata = { # This identifies the shader's role in the shading network as being # a primvar reader. token role = "primvar" # The following sdr-metadatum could be authored on the node directly # in lieu of authoring primvarProperty="1" on # inputs:primvarName. # string primvars = "$primvarName" } ) { uniform token info:implementationSource = "sourceAsset" # If primvarReader.oso can be resolved to an existent asset, then a # SdrShaderNode is created with sourceType=OSL and sourceUri pointing # to the resolved primvarReader.oso file. uniform asset info:OSL:sourceAsset = @primvarReader.oso@ # If primvarReader.glslfx can be resolved to an existent asset, then # another SdrShaderNode is created with sourceType=glslfx and sourceUri # pointing to the resolved primvarReader.glslfx file. uniform asset info:glslfx:sourceAsset = @primvarReader.glslfx@ token inputs:primvarName ( connectability = "interfaceOnly" sdrMetadata = { # This causes '$primvarName' to be appended to the # SdrNodeMetadata->Primvars metadata on the SdrShaderNode. string primvarProperty = "1" } doc = """Name of the primvar to be fetched from the geometry.""" ) # Asset valued inputs are automatically tagged with # sdrMetadata[SdrPropertyMetadata->IsAssetIdentifier] = "1". asset inputs:primvarFile = @@ ( connectability = "interfaceOnly" doc = """File containing some primvar info.""" ) float inputs:fallback = 0.0 ( 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 } ~~~~~~~~~~~~~ */