/*!
\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
}
~~~~~~~~~~~~~
*/