The overall development system is shown below:
Not all components have been made public at time of writing. The following are:
glTF
and MaterialX JSON
. This includes plugin integration with the QuiltiX
graph editor. AMD GPUOpen
and PhysicallyBased
.glTF
binaries for Qt
WebEngine pages. OCIO
.Numerous Python utilities exist in the pymaterialx
folder of the MaterialX Elements
github repo. These have not been formalized. Many are explained in the published Jupyter
notebooks. This includes Blender
and OpenUSD
data-model conversion, command line rendering, graph editing, and so forth.
At time of writing not all documentation for all components are included. Please stay-tuned for updates as they become available.
To allow for interactive graph viewing and editing an underlying representation and a library which can handle user display and interaction is required.
The decision was made to use the litegraph
library due based on the following
requirements:
Custom definitions can be dynamically creted / modified via a mechanism which takes as input one or more MaterialX definitions (nodedef
) and generate Javascript functions which define all node characteristics.
This Javascript code is then executed as require to dynamically create / update a set of node definitions which can be instantiated. By default "standard" libraries which come as part of the MaterialX distribution is used. Additional definitions can be added via MaterialX documents provided by the user.
Below is an example of custom definitions from Maya (Lambert), and Houdini ("facing ratio") being loaded in and nodes instantiated in a sample graph. The OpenPBR
node definition which is part of the 1.39 release of MaterialX was also loaded in using a 1.38.9 version of MaterialX.
Note that it is also possible to disallow allow this and only load in definitions from the Javascript definition code. This has the advantage of being able to load in content saved in native litegraph
format with the only dependence being the execution of the Javascript definition code.
The following is an example of dynamically generated Javascript code for the float version of ramplr
Characteristics of node:
1. UI elements have been include here such as node colors, and icon and swatch previews in additional to MaterialX provided UI meta-data such as ui min, ui max etc.
2. Explicit nodedef
ids are included along with node name, type, group etc.
3. Default values are explicitly provided including default geometric stream bindings.
4. Meta-data for color management or real-world units is included.
5. Callbacks for node specific changes are included here. In this example a specific "change" monitor
is added. See the "Change Management" section for more details.
LiteGraph
definition registration is shown at the end of the example. This will hook into all places where definitions are used including the provided node creation UI.
/**
* @brief mtlx_procedural2d_ramplr_float
* @details Library: mtlx. Category: constant. Type: float
* LiteGraph id: mtlx/procedural2d/ramplr_float
*/
function mtlx_procedural2d_ramplr_float() {
this.nodedef_icon = '';
this.nodedef_name = 'ND_ramplr_float';
this.nodedef_type = 'float';
this.nodedef_node = 'ramplr';
this.nodedef_href = 'https://kwokcb.github.io/MaterialX_Learn/documents/definitions/ramplr.html';
this.nodedef_swatch = 'https://kwokcb.github.io/MaterialX_Learn/resources/mtlx/nodedef_materials/material_ramplr_float_out_genglsl.png';
this.nodedef_group = 'procedural2d';
this.addInput('valuel','float');
this.addProperty('valuel', 0.0, 'float',{"colorspace":"","unit":"","unittype":"","uiname":"","uimin":null,"uimax":null,"uifolder":"","defaultgeomprop":""});
this.addInput('valuer','float');
this.addProperty('valuer', 0.0, 'float',{"colorspace":"","unit":"","unittype":"","uiname":"","uimin":null,"uimax":null,"uifolder":"","defaultgeomprop":""});
this.addInput('texcoord','vector2');
this.addProperty('texcoord', [0.0, 0.0], 'vector2',{"colorspace":"","unit":"","unittype":"","uiname":"","uimin":null,"uimax":null,"uifolder":"","defaultgeomprop":"UV0"});
this.addOutput('out','float');
this.title = 'ramplr_float';
this.desc = "MaterialX:mtlx/procedural2d/ramplr_float";
this.onNodeCreated = function() {
// Handled globally
}
this.onRemoved = function() {
// Handled globally
}
this.onPropertyChanged = function(name, value, prev_value) {
MxShadingGraphEditor.theEditor.monitor.onPropertyChanged(this.title, name, value, prev_value, this);
}
this.onPropertyInfoChanged = function(name, info, value, prev_value) {
MxShadingGraphEditor.theEditor.monitor.onPropertyInfoChanged(this.title, name, info, value, prev_value, this);
}
this.onConnectOutput = function(slot, input_type, input, target_node, target_slot) {
MxShadingGraphEditor.theEditor.monitor.onConnectOutput(slot, input_type, input, target_node, target_slot, this);
}
this.onConnectInput = function(target_slot, output_type, output, source, slot) {
MxShadingGraphEditor.theEditor.monitor.onConnectInput(target_slot, output_type, output, source, slot, this);
}
this.bgcolor = '#111';
this.color = '#222';
this.shape = LiteGraph.ROUND_SHAPE;
this.boxcolor = '#161';
}
mtlx_procedural2d_ramplr_float.nodedef_name = 'ND_ramplr_float';
mtlx_procedural2d_ramplr_float.nodedef_node = 'ramplr';
mtlx_procedural2d_ramplr_float.nodedef_href = 'https://kwokcb.github.io/MaterialX_Learn/documents/definitions/ramplr.html';
LiteGraph.registerNodeType('mtlx/procedural2d/ramplr_float',mtlx_procedural2d_ramplr_float);
The design of document and change management is shown in the following diagram:
The left-hand side shows how documents are managed. This includes how definitions are handled. The right-handed side shows how graph updates can be monitored with an example connection to a renderer.
This section goes into the details for: 1. Producing MaterialX documents are used for interop, runtime graph handling, and rendering. 2. Producing runtime (Javascript) node definitions as described in the previous section
By default the MaterialX standard
definitions are loaded in and kept in a MaterialX library document. This is fairly common practice. Any additional definitions that are loaded in from a working document are added to a one or more MaterialX additional library documents.
Secondary references are disallowed for web access. This will affect two areas. It is recommended to pre-package content and unpacking contents as required. At time of writing unpacking logic is not provided (e.g. such as for a USDz or glTF binary).
Note that MaterialX itself has no packaging mechanism nor any way to embed resources into a document.
Direct references to an additional files via the include mechanism for XML is ignored as this is disallowed.
Any file textures cannot be resolved as again these references are disallowed.
There are a few categories of changes that are worthwhile to monitor to perform updates. This includes:
Two test cases will be examined: 1. Property Editor Updates 2. Rendering Updates
In general the editor will be the one triggering changes so each attribute or value change should trigger the appropriate notification. This is not done directly but indirectly when value, attribute or link changes occur.
The sample editor provided does however keep track of selection context so this is additional monitoring logic added for this purpose.
Assuming that shader code have been produced and compiled into runtime shaders, it is possible to directly "poke" into the shader's uniforms on node input value changes.
This is possible by keeping track of shader reflection data which provides a correspondence between the runtime node path and the shader uniform. This is provide as part of MaterialX code generation and used by the sample renderer provided. ( Refer to the shader generation /reflection utility for more details)
This does not handle any string or filename changes which require a remapping to a shader uniform. - In general the string changes will require shader code to be regenerated and recompiled to produce a new runtime shader. It would be better if there are no nodes with string inputs but currently even the standard library has instances of these. - Note that string changes includes color management and unit changes. This entails more than a remapping as code is injected into the graph at code generation time. - filename changes requires access to an external resource which must be loaded and converted into a hardware texture and then bound.
At time of writing value changes which require shader builds are automatically performed. These are indicated by dotted the lines for boxes and data flow links in the diagram.