๐ญ Iโm currently open for work on open source projects. The Academy Software Foundation material standard: MaterialX is my primary focus along with
it's interrelation with industry standards: Khronos glTF and Pixar USD. Also of focus is MaterialX's interlation with standards such as OCIO (colour management),
and shading languages: OSL (Open Shading Language), MDL, and GLSL for both desktop and web rendering.
Some industry presentations can be found on the main MaterialX site
๐ฌ Feel free to connect with me for areas such as: software engineering, design / architacture, project management, remote working, open source / standards.
A shader graph is composed of various combinations of <nodes> and <nodegraphs> elements which can be connected together via child <inputs> and <outputs>.
This document first provides some background on the composition and semantics for shader graphs and then discuss usage in some user scenarios.
Basic Components
A <node> is atomic and is not represented by other nodes. Each node has a unique name. A valid contains only alphanumeric characters and cannot
contain a path separator: /.
graph TD
node1
node2
node3
Both <nodegraph>'s and documents are categorized as a graph elements.
Nodegraphs can contain 0 or more nodes or nodegraphs and 0 or more direct child <input>s or <output>s.
Nodes within a nodegraph or document are considered to be within the "scope" of the graph element. Nodegraph inputs and outputs are in the same scope as direct child nodes.
Though a document is considered to be a graph element direct child <input>s and <output>s are not allowed. A document node graph where no data flows in or out. At this time, documents cannot contain or
reference other documents as children.
graph TD
subgraph document
my_node
my_nodegraph
end
<input>s and <output>s on <node>s or <nodegraph>s define what is connectable. There can be 0 or more inputs and 1 or more outputs on a node or nodegraph.
Having no outputs is "allowed" but these nodes /nodgraphs are generally of no use as there is no starting point / root for evaluation.
classDiagram
class node {
+string name
+float in1
+color3 in2
+color3 out1
+int out2
}
class nodegraph {
+string name
+float in1
+color3 in2
+color3 out1
+int out2
}
Connectivity
For subsequent diagrams the connections are shown with an
upstream element connected to a downstream element by an arrow.
The child element of the downstream element is shown if required to
avoid ambiguity.
Elements may be <input>s or <output>s depending on specification
rules.
Rules
Shader graphs are directed acyclic graphs.
A <node> or <nodegraph><output> may be connected one or more <input> on another node or nodegraph<input> within the same scope.
The act of adding or removing nodegraphinputs and outputs can be thought of as publishing the public interface for the graph.
A node should never have inputs / outputs added or removed which are not part of their definition -- in fact this will be tagged a node instance which has no matching definition if a validation check is performed.
Compound and Functional Nodegraphs and "Flattening" and "Publishing"
If node is implemented by a nodegraph, then the nodegraph is called a functional nodegraph. Otherwise it is called a compound nodegraph.
Thus a more complete definition of a shader graph is that:
It is composed of a series of connected nodes and compound nodegraphs.
One or more of these nodes may be implemented as functional graphs.
If the node instance is replaced with the functional graph implementation then the node essentially "becomes" a compound graph. This operation is called flattening a node instance.
If all nodes are taken out of the scope of their parent nodegraphs then all that is left is a series of connected nodes. This operation is is called flattening a nodegraph.
The reverse operation to add nodes to nodegraphs allows users to logically group nodes and/or publisih it's public interface.
If a new definition <nodedef> is created and the nodegraph becomes a functional graph then can be thought of as publishing a new definition.
if flattened would look something like this if the parent of the nodegraph was a document. The interface <input> and <output> elements are not present as this is disallowed within the document scope.
The reverse process could create a nodegraph, with inputs and outputs added to create the public interface for the nodegraph.
Shader Generation Graphs
When dealing with shader generation shader graphs are simplified to having only nodes with inputs and outputs "ports".
The key components are:
Shader Node: Basically corresponds to a node.
Shader Port: A connectable attribute of a Shader Node. Can either be Input or Output Ports.
Public Ports: Are bindable as shader code inputs or exposed as the root for evaluation as an output.
Private Ports: Inputs and Outputs which are not exposed.
All original nodes are flattened to remove the notion of a graph hierarchy
and replace any nodes which are represented as functional graphs.
Upstream Traversal
Upstream traversal is fairly simple where the root to start from should be an <output> on a node or nodegraph. Traversal will naturally only follow direct connections.
This is straightforward when:
There are no nodegraphs within the shader graph.
Or all of the nodes reside within a single nodegraph.
In this case only node inputs connect to upstream outputs.
Examples
Interior of nodegraph to upstream nodegraphs and nodes
Logically a both a Document and a NodeGraph are considered to be containers of nodes or a "node graph".
However a Node which may have an implementation as a graph is not considered to be a container. This means
traversal up and downstream from a given node requires special casing.
Another issue is that in order to keep a minimal size, only those inputs and which are explicit connected to or
assigned non-default values will exist on the instance of a Node or Nodegraph, even though they are part of the
interface definition.
Any connections which within Nodegraphs which connect interior Node inputs to Nodegraph inputs also currently have
special syntax.
All connections are specified on a downstream input instead of as a structure which provides both the input and output. This makes downstream traversal much less straightforward to perform than upstream.
Materials as of 1.38 are nodes and traversal can find upstream shaders to allow a full material graph to be traversed.
Recommendations
In order to create a "complete" graph, when instances are created all inputs and outputs should be instantiated. This can be done via utilities which scan the associated NodeDef (definition) and add inputs and outputs with default values.
An NodeGraph itself should have all of it's inputs specified even if it's a functional graph for the same reason.
This is a proposal to update the way that connections are
specified in MaterialX. To understand the proposal basic
background is provided first.
Background: Node Connection Combinations
A node can be connected to any other node with a nodegraph (node container)
If you consider a document to also be a node container then nodes can reside a the "top level" of a document as well.
Any output of a node can be connected to the input of a node or nodegraph if they are of the same type.
Any output of a nodegraph can be connected the input of a node or nodegraph if the inputs are of the same type.
*Note(): It is not valid to have both a value and a connection specified.
Examples
graph TD;
node0-->node1;
subgraph NodeGraph
NodeGraph/node0-->NodeGraph/node1;
end
nodeGraph1-->node2;
node3-->nodegraph2
nodeGraph3-->nodeGraph4
Background: Nodegraph Input / Output Variations
A <nodegraph> can contain a set of nodes.
Though it is possible have no inputs nor outputs this would be of no practical use.
Any unconnected input on a nodegraph node can be connected to a nodegraph's input.
This is currently via a interfacename reference on the node's input.
An output of a nodegraph node can can be connnected to a nodegraph <output>.
The unique interface "signature" of a nodegraph is defined by it's inputs and outputs.
Examples of Valid Configurations:
No inputs / Single output
graph LR;
subgraph NG1
NG1/node1-->NG1/output1
end
NG1/output1-->bxdf1
Single input / multiple outputs
graph LR;
subgraph NG1
NG1/node1-->NG1/output1
NG1/node1-->NG1/output2
end
NG1/output1-->bxdf3
NG1/output2-->bxdf3
Single input / single output
graph LR;
subgraph NG1
NG1/input1-->NG1/node1
NG1/node1-->NG1/output1
end
NG1/output1-->bxdf1
Multiple inputs / single output
graph LR;
subgraph NG1
NG1/input1-->NG1/node1
NG1/input2-->NG1/node1
NG1/node1-->NG1/output1
end
NG1/output1-->bxdf1
It is not allowed to connect an output of a node or nodegraph directly to an input of a node within a nodegraph. Similarly,
it is not allowed to connect input of a node inside a nodegraph directory to the output of another node or nodegraph outside the node's nodegraph
graph LR;
nodeBad1-->NG1/node1
subgraph NG1
NG1/node1
end
NG1/node1-->badNode2
graph LR;
subgraph NG1
NG1/node1
end
subgraph NG0
NG0/node1-->NG1/node1
end
subgraph NG2
NG1/node1-->NG2/node1
end
Proposed Changes
Connection Syntax
Currently connections are specified on an input using addition attributes:
node if connected to an upstream node output
nodegraph if connected to an upstream nodegraph output
interfacename if connected to the input interface of a nodegraph.
output This must be specified if the upstream node has one or more outputs to avoid ambiguity.
channel Based on the current 1.39 proposal a numeric channel can be specified for an given upstream output.
The proposal is to replace this with a MaterialX path and a single attribute name to representation a connection:
The attributes:node, nodegraph, interfacename and channel will be replaced with a single string attribute named connection.
The assigned value will be a MaterialX path which has been extended to support input interface and channel notation.
Paths are specified relative to the current scope. This could be within a nodegraph or directly under the root document.
Thus absolute paths are considered invalid. Syntactically a path starting with a / is not valid.
It is still possible to specify only the path to a upstream node without explicitly specifying it's output if and only if the node only has one output.
Otherwise the reference is considered to be ambiguous.
A fallback logic of using the first output can still be supported. )
Inputs which reference an interface input using interfacename will use a path notation of this form: .<input name>.
Note that this means that nodegraph inputs can be connected to node inputs. This makes it orthogonal to how node outputs can be connected to nodegraph outputs. These type of connections are still considered to be invalid if specified outside a nodegraph.
The 1.39 channel attribute (and the existing channels attribute are represented as part of the path notation using an array notation: [#]. # is the channel number 0..N. Where N+1 is the number of channels.
<namespace> can still be supported as part of the path name (as it currently is now). That is it is still allowed to connected between namespaces.
Connections Using A Common Parent
The following are some examples of connections where the connections occur within the parent.
Connection from node1 in the same parent:
<inputname="in1"type="color3"connection="node1" />
<!-- or -->
<inputname="in1"type="color3"connection="node1.out" />
Connection from channel "1" on node1 in the same parent:
Changing scope using something like "../" is not allowed. That is it is not permitted to specify connection to an upstream input or output which is not under the same parent.
Notes
It is assumed that connection path parsing will occur during document input and output conversion from / to a document.
This scheme will work with nested nodegraphs assuming that the same connection rules are maintained.