Feature Proposal: Support Nested NodeGraphs
MaterialX Nested Nodegraphs
This document outlines the proposal to support a <nodegraph> which is a child of another <nodegraph>. As there are no syntax changes, the proposal is for a 1.38.x incremental release.
Authors
Niklas Harrysson, Bernard Kwok, Jonathan Stone
Motivation
Currently compound nodegraphs can be specified at the document level. The extension to this is to allow nesting at any level in a graph hierarchy.
This allows for packaging of sub-parts of a graph into logical units for better organization and readability. When a graph becomes large it becomes convenient to have the ability to wrap parts of it into compounds where it make sense.
For example, in the nodegraph for standard_surface there are several parts of the graph that can be separated into child compounds that calculate a particular piece of logic such as:
- Tangent rotations
- How coat affect roughness
- How coat effect diffuse and sss colors
- How fresnel effects the EDF
Even the logical layering of BSDFs can probably have been separated out into compounds for better readability in a graph editor.
As an artist this becomes a useful tool in the toolbox for organizing graphs.
In addition, when such a compound is found to be reusable it can be published as a new definition (nodedef) with a functional nodegraph allowing it to be instantiated and reused. Without the ability to create child compounds an artist currently has to first move the graph up to the document level, then recreate the logic there, and then perform the "publish" to a nodedef.
Another motivation is that a user is presented with the ability to construct nested nodegraphs in the Graph Editor, but cannot save the result to a document.
Syntax Changes
None
Connection Logic Changes
Connection handling becomes more consistent between nodes and nodegraphs by generalizing connections. This is achieved by removing the restriction that some things you can do on nodes are not allowed on nodegraphs. Namely:
-
interfacenamecan be specified on nodegraph<input>s to allow connections between inputs on a child nodegraph and a parent nodegraph.
Possible Configurations
Given a parent <nodegraph> called parentNG and a child nodegraph childNG, and child nodes childNodeUp and childNodeDown:
-
The
<input>onchildNGis connected to the output of an upstream nodechildNodeUp<input ... node="childNodeUp"> [output="output on childNodeUp"]>graph TB; subgraph parentNG childNodeUp childNG end subgraph childNG input([input]) end childNodeUp --> input style input fill:#0bb, color:#111The
outputis specified ifchildNodeUphas multiple outputs. If the upstream node was a nodegraph then syntax would benodegraph=instead ofnode=. (See *) -
The
<output>onchildNGis connected to the input of a downstream nodechildNodeDown<input ... nodegraph="childNG"> [output="output on childNG"]>graph TB; subgraph parentNG childNodeDown childNG end subgraph childNG output([output]) end output --> childNodeDown style output fill:#0b0, color:#111The
outputis specified ifchildNGhas multiple outputs. -
The
<input>onchildNGis connected to the interface<input>calledparentNGInputof the parent nodegraphparentNG<input ... interfacename="parentNGInput"`>graph TB; subgraph parentNG parentNGInput([parentNGInput]) childNG end subgraph childNG input([input]) end parentNGInput --interfacename--> input style input fill:#0bb, color:#111 style parentNGInput fill:#0bb, color:#111 -
The
<output>onchildNGis connected to the<output>of the parent nodegraphparentNG<output ... nodegraph="childNG" [output="output on childNG"]>graph TB; subgraph parentNG outputparentNG([output]) childNG end subgraph childNG output([output]) end output --> outputparentNG style output fill:#0b0, color:#111 style outputparentNG fill:#0b0, color:#111with the
outputbeing specified ifchildNGhas multiple outputs.
(*) Note that <nodegraph> to <nodegraph> connections are already supported so is similar to the node to nodegraph connection scenario.
Interface Example
Example shows interfaces being connected between parent and child nodegraphs
<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">
<nodegraph name="parentNG">
<input name="parentNGInput" type="color3" value="1, 1, 0.2" />
<input name="parentNGInput2" type="color3" value="0.2, 1, 1" />
<nodegraph name="childNG">
<input name="childNGInput" type="color3" interfacename="parentNGInput" />
<input name="childNGInput2" type="color3" interfacename="parentNGInput2" />
<multiply name="multiplyNode" type="color3">
<input name="in1" type="color3" interfacename="childNGInput" />
<input name="in2" type="color3" interfacename="childNGInput2" />
</multiply>
<output name="childNGOutput" type="color3" nodename="multiplyNode" />
</nodegraph>
<output name="parentNGOutput" type="color3" nodegraph="childNG" />
</nodegraph>
</materialx>
graph TD;
top_shader[top_shader]
parentNG_childNG_multiplyNode[multiplyNode] --> childNGOutput
parentNG_childNG_childNGInputINT([childNGInput]) ==.in1==> parentNG_childNG_multiplyNode[multiplyNode]
style parentNG_childNG_childNGInputINT fill:#0bb, color:#111
parentNG_childNG_childNGInput2INT([childNGInput2]) ==.in2==> parentNG_childNG_multiplyNode[multiplyNode]
style parentNG_childNG_childNGInput2INT fill:#0bb, color:#111
parentNGOutput --".base_color"--> top_shader[top_shader]
subgraph parentNG
parentNG_childNG
parentNG_parentInput([parentInput])
parentNG_parentInput2([parentInput2])
style parentNG_parentInput2 fill:#0bb, color:#111
style parentNG_parentInput fill:#0bb, color:#111
parentNGOutput([parentNGOutput])
style parentNGOutput fill:#0b0, color:#111
end
subgraph parentNG_childNG[childNG]
parentNG_childNG_childNGInput2INT
parentNG_childNG_childNGInputINT
parentNG_childNG_multiplyNode
childNGOutput([childNGOutput]) --> parentNGOutput
style childNGOutput fill:#0b0, color:#111
end
parentNG_parentInput([parentInput]) --> parentNG_childNG_childNGInputINT
parentNG_parentInput2([parentInput2]) --> parentNG_childNG_childNGInput2INT
The resulting rendering in MaterialXView would look something like this,
where the parent nodegraph inputs (parentNGInput, parentNGInput2) are exposed as input uniforms via shader code generation.

Implementation Requirements
- Graph traversal logic, shader generation, and value evaluation handles
interfacenameon nodegraph inputs, and traversal from a parent nodegraphoutputto a child nodegraphoutput - "Inherited" properties (such as
fileprefix,colorspace) evaluate properly through parent/childnodegraphs. This should already be the case. - No "upgrade" path is required.
Based on this Slack thread, for interop this would allow for future support so that USD (hdMtlx) would no longer need to pre-flatten it's graphs for conversion to MaterialX. The flip side would be that conversion to USD could support nested graphs, with a possible utility pre-flatten within MaterialX.
This is implied that only connections within the current scope are allowed as re-confirmed by Doug Smythe.
See this Slack thread for additional discussion.
Looks really good to me!
Just some thoughts on the connection:
<input ... nodegraph="childNG"> [output="output on childNG"]>
It's logical to use nodegraph= to specify the "childNG" as input, since node graphs are not quite nodes (shaders) in MaterialX. Though in DCC, node graphs can be thought as a node with children inside, so there it may be permissible to also think of the connection as node="childNG".
I think we should indeed use nodegraph="childNG" for MaterialX. I just wanted to mention it, in case there are some other implications (eg, on scripting, etc).
Hi @rafalSFX ,
Indeed, If we treat a nodegraph as something you can encapsulate as a node we'd only need node connection concept.
The notation of using nodegraph= and node= is historical, (as is interfacename=), so just following what's there now.
We had a proposal to generalize syntax to use 'connect=