Skip to content

Commit

Permalink
[UsdMtlx] Add support for nodes outside nodegraphs
Browse files Browse the repository at this point in the history
If material inputs are coming from a node output, we create
a nodegraph around that node as well as any upstream nodes
or other nodes providing connections to different material
inputs. Essentially doing what we previously required the
user to do; contain all nodes that ultimately feed into the
material/surfaceshader nodes, inside a nodegraph.

Fixes #1636

(Internal change: 2242218)
(Internal change: 2242222)
  • Loading branch information
klucknav authored and pixar-oss committed Jul 21, 2022
1 parent 6280d71 commit 65ac7ba
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 8 deletions.
2 changes: 1 addition & 1 deletion pxr/usd/usdMtlx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pxr_register_test(testUsdMtlxDiscovery
pxr_register_test(testUsdMtlxFileFormat
PYTHON
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMtlxFileFormat"
DIFF_COMPARE Looks.usda NodeGraphs.usda usd_preview_surface_gold.usda Include.usda Include_From_Usdz.usda CustomNodeDef.usda
DIFF_COMPARE Looks.usda NodeGraphs.usda GraphlessNodes.usda usd_preview_surface_gold.usda Include.usda Include_From_Usdz.usda CustomNodeDef.usda
EXPECTED_RETURN_CODE 0
)

Expand Down
3 changes: 0 additions & 3 deletions pxr/usd/usdMtlx/overview.dox
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ no corresponding concept.
technically not unsupported since support is optional and only meaningful
for applications that need it, which USD does not.
+ interfacename attributes are not supported
+ UsdMtlx expects additional nodes, other than the material and surface shader
nodes, to be contained inside a nodegraph. These additional nodes will be lost
when translating into UsdShade, if not contained in a nodegraph.
+ Shared library custom nodes are not fully supported. The nodedef needs to be
defined in a separate mtlx file such that the nodedef and material mtlx files
are not be siblings, and the nodedef filepath should be locatable by the
Expand Down
56 changes: 52 additions & 4 deletions pxr/usd/usdMtlx/reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,12 @@ _NodeGraphBuilder::Build(ShaderNamesByOutputName* outputs)

// Build the graph of nodes.
for (auto& mtlxNode: _mtlxContainer->getChildrenOfType<mx::Node>()) {
// If the _mtlxContainer is the document (there is no nodegraph) the
// nodes gathered here will include the material and surfaceshader
// nodes which are not part of the implicit nodegraph. Ignore them.
const std::string &nodeType = _Attr(mtlxNode, names.type);
if (nodeType == "material" || nodeType == "surfaceshader")
continue;
_AddNode(mtlxNode, usdPrim);
}
_ConnectNodes();
Expand Down Expand Up @@ -1571,6 +1577,20 @@ _Context::AddShaderNode(const mx::ConstNodePtr& mtlxShaderNode)
usdNodeGraph);
}
}

// Check if this input is directly connected to (references) a node
// Meaning the material inputs are coming from nodes not explicitly
// contained in a nodegraph.
if (auto connNode = _Attr(mtlxInput, names.nodename)) {
// Create an implicit nodegrah to contain these nodes
if (_NodeGraph usdNodeGraph =
AddImplicitNodeGraph(mtlxInput->getDocument())) {
_BindNodeGraph(mtlxInput,
_usdMaterial.GetPath(),
UsdShadeConnectableAPI(usdShader),
usdNodeGraph);
}
}
}
if (auto primvars = UsdGeomPrimvarsAPI(_usdMaterial)) {
for (auto mtlxToken: mtlxShaderNode->getChildren()) {
Expand Down Expand Up @@ -1866,7 +1886,7 @@ _Context::_BindNodeGraph(
// Reference the instantiation.
SdfPath referencingPath = referencingPathParent.AppendChild(
usdNodeGraph.GetOwnerPrim().GetPath().GetNameToken());
TF_DEBUG(USDMTLX_READER).Msg("_BindNodeGraph %s %s\n",
TF_DEBUG(USDMTLX_READER).Msg("_BindNodeGraph %s - %s\n",
mtlxInput->getName().c_str(),
referencingPath.GetString().c_str());
_NodeGraph refNodeGraph = usdNodeGraph.AddReference(referencingPath);
Expand All @@ -1875,15 +1895,43 @@ _Context::_BindNodeGraph(
}

// Connect the input to the nodegraph's output.
if (UsdShadeOutput output =
refNodeGraph.GetOutputByName(_Attr(mtlxInput, names.output))) {
const std::string &outputName = _Attr(mtlxInput, names.output);
if (UsdShadeOutput output = refNodeGraph.GetOutputByName(outputName)) {
UsdShadeConnectableAPI::ConnectToSource(
_AddInput(mtlxInput, connectable),
output);
}
// If this input is connected to a node's output.
else if (auto nodename =_Attr(mtlxInput, names.nodename)) {
// Find the conected node's UsdShadeShader node and output
const TfToken outputToken = (outputName.empty())
? UsdMtlxTokens->DefaultOutputName
: TfToken(outputName);
const SdfPath shaderPath = referencingPath.AppendChild(TfToken(nodename));
if (UsdShadeShader usdShader = UsdShadeShader::Get(
usdNodeGraph.GetOwnerPrim().GetStage(),
referencingPath.AppendChild(TfToken(nodename)))) {
if (UsdShadeOutput output = usdShader.GetOutput(outputToken)) {
UsdShadeConnectableAPI::ConnectToSource(
_AddInput(mtlxInput, connectable),
output);
}
else {
TF_WARN("No output \"%s\" for input \"%s\" on <%s>",
outputToken.GetText(),
_Name(mtlxInput).c_str(),
shaderPath.GetText());
}
}
else {
TF_WARN("Shader not found at <%s> for input \"%s\"",
shaderPath.GetText(),
_Name(mtlxInput).c_str());
}
}
else {
TF_WARN("No output \"%s\" for input \"%s\" on <%s>",
_Attr(mtlxInput, names.output).c_str(),
outputName.c_str(),
_Name(mtlxInput).c_str(),
connectable.GetPath().GetText());
}
Expand Down
9 changes: 9 additions & 0 deletions pxr/usd/usdMtlx/testenv/testUsdMtlxFileFormat.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ def test_MultiOutputNodes(self):
conn = node.GetInput('in').GetConnectedSource()
self.assertEqual(conn[0].GetPrim().GetPath().name, connNodeName)
self.assertEqual(conn[1], connectionName)

def test_nodesWithoutNodegraphs(self):
"""
Test MaterialX material with nodes not contained in a nodegraph and no
explicit outputs
"""

stage = UsdMtlx._TestFile('GraphlessNodes.mtlx')
stage.GetRootLayer().Export('GraphlessNodes.usda')

def test_Looks(self):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<materialx version="1.38">

<constant name="redClorVal" type="color3">
<input name="value" type="color3" value="1,0,0" />
</constant>
<constant name="blueColorVal" type="color3">
<input name="value" type="color3" value="0,0,1" />
</constant>
<multiply name="multi1" type="color3">
<input name="in1" type="color3" nodename="redClorVal" />
<input name="in2" type="float" value="0.5" />
</multiply>
<add name="myDiffColor" type="color3">
<input name="in1" type="color3" nodename="multi1" />
<input name="in2" type="color3" nodename="blueColorVal" />
</add>
<constant name="mySpecColor" type="color3">
<input name="value" type="color3" value="0,1,0" />
</constant>

<UsdPreviewSurface name="surface_shader_node" type="surfaceshader">
<input name="diffuseColor" type="color3" nodename="myDiffColor" />
<input name="useSpecularWorkflow" type="integer" value="1" />
<input name="specularColor" type="color3" nodename="mySpecColor" />
<input name="roughness" type="float" value="0.25" />
<input name="ior" type="float" value=".9" />
</UsdPreviewSurface>
<surfacematerial name="surface_material_node" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="surface_shader_node" />
</surfacematerial>
</materialx>
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#usda 1.0

def "MaterialX"
{
def "Materials"
{
def Material "surface_material_node"
{
float inputs:clearcoat
float inputs:clearcoatRoughness
color3f inputs:diffuseColor
float inputs:displacement
color3f inputs:emissiveColor
float inputs:ior = 0.9
float inputs:metallic
float3 inputs:normal
float inputs:occlusion
float inputs:opacity
float inputs:opacityThreshold
float inputs:roughness = 0.25
color3f inputs:specularColor
int inputs:useSpecularWorkflow = 1
token outputs:mtlx:surface.connect = </MaterialX/Materials/surface_material_node/ND_UsdPreviewSurface_surfaceshader.outputs:surface>

def Shader "ND_UsdPreviewSurface_surfaceshader" (
prepend references = </MaterialX/Shaders/ND_UsdPreviewSurface_surfaceshader>
)
{
float inputs:clearcoat.connect = </MaterialX/Materials/surface_material_node.inputs:clearcoat>
float inputs:clearcoatRoughness.connect = </MaterialX/Materials/surface_material_node.inputs:clearcoatRoughness>
color3f inputs:diffuseColor.connect = </MaterialX/Materials/surface_material_node/NodeGraphs/myDiffColor.outputs:out>
float inputs:displacement.connect = </MaterialX/Materials/surface_material_node.inputs:displacement>
color3f inputs:emissiveColor.connect = </MaterialX/Materials/surface_material_node.inputs:emissiveColor>
float inputs:ior.connect = </MaterialX/Materials/surface_material_node.inputs:ior>
float inputs:metallic.connect = </MaterialX/Materials/surface_material_node.inputs:metallic>
float3 inputs:normal.connect = </MaterialX/Materials/surface_material_node.inputs:normal>
float inputs:occlusion.connect = </MaterialX/Materials/surface_material_node.inputs:occlusion>
float inputs:opacity.connect = </MaterialX/Materials/surface_material_node.inputs:opacity>
float inputs:opacityThreshold.connect = </MaterialX/Materials/surface_material_node.inputs:opacityThreshold>
float inputs:roughness.connect = </MaterialX/Materials/surface_material_node.inputs:roughness>
color3f inputs:specularColor.connect = </MaterialX/Materials/surface_material_node/NodeGraphs/mySpecColor.outputs:out>
int inputs:useSpecularWorkflow.connect = </MaterialX/Materials/surface_material_node.inputs:useSpecularWorkflow>
}

def "NodeGraphs" (
prepend references = </MaterialX/NodeGraphs>
)
{
}
}
}

def "Shaders"
{
def Shader "ND_UsdPreviewSurface_surfaceshader"
{
uniform token info:id = "ND_UsdPreviewSurface_surfaceshader"
token outputs:surface
}
}

def "NodeGraphs"
{
def Shader "redClorVal"
{
uniform token info:id = "ND_constant_color3"
color3f inputs:value = (1, 0, 0)
color3f outputs:out
}

def Shader "blueColorVal"
{
uniform token info:id = "ND_constant_color3"
color3f inputs:value = (0, 0, 1)
color3f outputs:out
}

def Shader "multi1"
{
uniform token info:id = "ND_multiply_color3FA"
color3f inputs:in1.connect = </MaterialX/NodeGraphs/redClorVal.outputs:out>
float inputs:in2 = 0.5
color3f outputs:out
}

def Shader "myDiffColor"
{
uniform token info:id = "ND_add_color3"
color3f inputs:in1.connect = </MaterialX/NodeGraphs/multi1.outputs:out>
color3f inputs:in2.connect = </MaterialX/NodeGraphs/blueColorVal.outputs:out>
color3f outputs:out
}

def Shader "mySpecColor"
{
uniform token info:id = "ND_constant_color3"
color3f inputs:value = (0, 1, 0)
color3f outputs:out
}
}
}

0 comments on commit 65ac7ba

Please sign in to comment.