MaterialX icon indicating copy to clipboard operation
MaterialX copied to clipboard

Specification Proposal : Nodegraph Instancing

Open kwokcb opened this issue 10 months ago • 7 comments

Proposal

This is a placeholder for the idea of adding in the ability to specify a compound nodegraph once and then use "instances" of it. Each instance can have input overrides applied to it as desired. Overrides replace the values of inputs specified on compound nodegraph

Motivation

The primary motivation for this is to allow the ability for a user to reuse a nodegraph with different variations on the inputs to the graph. This avoids the more complicated alternative to creating a new definition from a nodegraph and instantiating an instance of that definition to allow reuse.

This provides an mapping to what is available in UsdShade so would improve interop.

Assumptions

  • This would apply to only to locations where an output of a compound nodegraph can be connected to a downstream input. Valid inputs include those on shader nodes, and nodegraphs for MaterialX.
  • It is not valid to add connections to other shader node, or nodegraph for input overrides. That is no topological changes are allowed for overrides.
  • It is also not valid to specify implicit values or streams (no defaults)

Tokens

  • If there are <token> interfaces on a nodegraph, then they can also be overridden in the same manner as <input> interfaces. They already disallow connections so would be considered to be constant value overrides used for name resolving. They would follow the same hierarchy rules where child overrides take precedence over parent overrides.
  • At the time of writing <token> interfaces are not supported in UsdShade so the assumption is that pre-resolving is required before converting to UsdShade.

Syntax

For discussion.

Example

The for example is to illustrate the point only. Not as a syntax proposal.

The nodegraph "foo" is used for an instance of it "foo_instance". "foot_instance" overrides the input input1 with the value of 0.5 instead of the default value of `1.0.

The instanced nodegraph can be used like any other nodegraph. In this case it is connected to a downstream "add" node's input.

<nodegraph "foo">
  <input name="input1" type="float" value="1.0">
  ...
</nodegraph>

<nodegraph "foo_instance" instanceof="foo">
  <input name="input1" type="float" value="0.5">
  ...
</nodegraph>

<add "myadd1" type="float">
  <input name="in1" type="float" nodegraph="foo">
  <input name="in1" type="float" nodegraph="foo_instance">
</add>

Without the "add" this would look something like this in USD:

def "MaterialX"
{
    def "Materials"
    {
       ...
       def "foo_instance" (
                prepend references = </MaterialX/NodeGraphs/foo>
            )
            {
                float inputs:input1= 0.5
            }
        }
    }   

    def "NodeGraphs"
    {
        def NodeGraph "foo"
        {
           float inputs:input1= 1.0           
        }
     }
}

The proposal for glTF is more restrictive as nodegraphs can only be connected to downstream materials.

"extensions": {
    "KHR_texture_procedurals": {
      "procedurals": [
        {
          "name": "foo",
          "nodetype": "nodegraph",
          "type": "float",
          "inputs": {
            "input1": {
              "nodetype": "input",
              "type": "float",
              "value": 1.0 // Default value
            },
           ...
     ]
}
}

  "materials": [
      {
        "name": "Gltf_pbr",
        "pbrMetallicRoughness": {
          "baseColorTexture": {
            "index": 0,
            "extensions": {
              "KHR_texture_procedurals": {
                "index": 0,  // Reference "foo"  nodegraph
                "inputs": {
                  "input1": 0.5  // Override  value
                }
              }
            }

kwokcb avatar Apr 30 '25 14:04 kwokcb

@kwokcb this looks like an interesting direction to explore. The first question that immediately jumps to my mind is if it's legal to add additional nodes to a nodegraph instance. ie.

<nodegraph "foo">
  <input name="input1" type="float" value="1.0">
  ...
</nodegraph>

<nodegraph "foo_instance" instanceof="foo">
  <input name="input1" type="float" value="0.5">
  ...

  <add name="newNode" type="float">
    ..
  </add>
  <output name="out" type="float" nodename="newNode"/>
</nodegraph>

<add "myadd1" type="float">
  <input name="in1" type="float" nodegraph="foo">
  <input name="in1" type="float" nodegraph="foo_instance">
</add>

Potentially even redefining which node is connected to the output.

I think the this isn't the intention of the proposal, but maybe this choice of syntax suggests that it might be legal?

ld-kerley avatar Apr 30 '25 15:04 ld-kerley

It's mostly just some made up syntax I came up with. This could be a new entity like a <nodegraph_instance> with strict rules.

I assume this is like a node "instance" except you can only ever specify input value overrides and nothing else. I think what you show is more of "inheritance" or a "mix-in".

kwokcb avatar Apr 30 '25 16:04 kwokcb

If you have a nodegraph with inputs, and you want to have different "instances" of this with different input values, how is that any different from defining a proper functional nodegraph? Especially if we add the capability discussed in the other proposal to encapsulate a nodegraph definition inside a nodedef for the interface.

dbsmythe avatar Apr 30 '25 19:04 dbsmythe

@kwokcb, assumption #2 is interesting.. we do not make that restriction in UsdShade, leaving it to downstream consumers (e.g. Hydra) to deal with consequences, i.e. needing to potentially "split" the nodegraph into multiple prototypes, if the encapsulated sharing is actually to be preserved within the renderer/shading-system. Currently, Hydra unrolls all instancing at the Material and NodeGraph level, though doing more shader-network-sharing is an aspirational goal.

I'm not suggesting MaterialX adopt this full promiscuity as well, but I do think it's useful and important to be able to drive the NodeGraph inputs with values authored on "the Material Interface". In UsdShade the material Interface is expressed as inputs on the Material prim itself, and there's a classification of connections that are allowed to be "interfaceOnly", so instanced NodeGraphs should minimally be allowed to have "interfaceOnly" connections, which would allow them to be driven by public inputs of the Material. Maybe this is what assumption 1 is saying? I'm not recalling properly if that's how the public interface is specified for MaterialX.

spiffmon avatar May 01 '25 01:05 spiffmon

@ld-kerley , I'd argue that you should not be able to add new nodes to NodeGraph instances, which is why I encouraged thinking about them as instances rather than references... adding nodes would change the topology, and limit possible sharing. BUt I guess that's a community tradeoff to make... in UsdShade you can decide if you want the referenced NodeGraph to be instanced or not, and if it's not you can modify it however desired. But now we're getting into more and more composition delicacies...

spiffmon avatar May 01 '25 01:05 spiffmon

If you have a nodegraph with inputs, and you want to have different "instances" of this with different input values, how is that any different from defining a proper functional nodegraph? Especially if we add the capability discussed in the other proposal to encapsulate a nodegraph definition inside a nodedef for the interface.

This is another approach to have nodegraph reuse. Both have their pros and cons. Having this would just mean you can directly map USD nodegraph referencing to the same concept in MaterialX. (e.g. when performing code generation). In the last USD/MTLX sync this alternative was brought up. (by @crydalch and @ld-kerley) For now this is of lesser priority. It seems worth considering the workflow where you want to modify the a reusable node graph until you are satisfied or want to create a new definition with a nodegraph implementation. If / when a definition with functional nodegraph is supported in UsdShade the nodegraph would be considered to be immutable. Sorry if it's all a bit "hand-waving" at the moment.

kwokcb avatar May 02 '25 13:05 kwokcb

@kwokcb, assumption #2 is interesting.. we do not make that restriction in UsdShade, leaving it to downstream consumers (e.g. Hydra) to deal with consequences, i.e. needing to potentially "split" the nodegraph into multiple prototypes, if the encapsulated sharing is actually to be preserved within the renderer/shading-system. Currently, Hydra unrolls all instancing at the Material and NodeGraph level, though doing more shader-network-sharing is an aspirational goal.

I'm not suggesting MaterialX adopt this full promiscuity as well, but I do think it's useful and important to be able to drive the NodeGraph inputs with values authored on "the Material Interface". In UsdShade the material Interface is expressed as inputs on the Material prim itself, and there's a classification of connections that are allowed to be "interfaceOnly", so instanced NodeGraphs should minimally be allowed to have "interfaceOnly" connections, which would allow them to be driven by public inputs of the Material. Maybe this is what assumption 1 is saying? I'm not recalling properly if that's how the public interface is specified for MaterialX.

For the point of interface connection, I think the key difference between MaterialX and UsdShade is there is are no interface routing for materials but only for inputs on nodes inside of nodegraphs. This is connected with 1. as "valid" means to allow only connections a nodegraph instance to a downstream shader or nodegraph.

The only "container" option is a nodegraph within another nodegraph (nesting). This is currently in the MaterialX spec but not implemented. If we follow the spec than I think it would be allowed to connect a parent nodegraph input interface to an input on a child nodegraph instance input (override).

<nodegraph "foo">
  <input name="input1" type="float" value="1.0">
  ...
</nodegraph>


<nodegraph "bar">
  <input name="bar_input" type="float" value="2.0" >
  ...
  <nodegraph "foo_instance" instanceof="foo">
    <input name="input1" type="float" interfacename="bar_input">
    ...
  </nodegraph>
</nodegraph>

BTW, glTF will be supporting only material connection and interface routing for referenced nodegraphs to match as much as possible with UsdShade.

kwokcb avatar May 02 '25 14:05 kwokcb