Skip to content

Commit

Permalink
[usdMtlx, hdMtlx, hdSt] Add Texture support for MaterialX custom node…
Browse files Browse the repository at this point in the history
…s defined in libraries

Custom nodes defined in a separate library indicated with the
PXR_MTLX_PLUGIN_SEARCH_PATHS envar that use textures need
to have the 'textureuser' group so that UsdMtlx and hdMtlx know
they use textures and can properly initialize texture coordinates.
Similarly custom nodes that use texcoords need the
'texcoorduser' group. These custom nodes now work in both
Storm and HdPrman.
Changes in hdSt are since custom nodes can name the input for
the file anything instead of 'file' which the stdlib texture nodes use.

Note:
- users need to add 'textureuser' or 'texcoorduser' as the custom node's nodegroup
- need to use the fallback texture coordinates
- cannot use mutliple textures inside one custom node

Fixes #1636
Fixes #1786

(Internal change: 2242208)
  • Loading branch information
klucknav authored and pixar-oss committed Jul 21, 2022
1 parent a307b28 commit 6280d71
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 47 deletions.
25 changes: 14 additions & 11 deletions pxr/imaging/hdMtlx/hdMtlx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,26 +194,27 @@ _AddMaterialXNode(
// Get the mxNode information
TfToken hdNodeType = netInterface->GetNodeType(hdNodeName);
mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(hdNodeType.GetString());
if (!mxNodeDef){
if (!mxNodeDef) {
TF_WARN("NodeDef not found for Node '%s'", hdNodeType.GetText());
return mx::NodePtr();
}
const SdfPath hdNodePath(hdNodeName.GetString());
const std::string & mxNodeCategory = mxNodeDef->getNodeString();
const std::string & mxNodeType = mxNodeDef->getType();
const std::string & mxNodeName = hdNodePath.GetName();
const std::string &mxNodeCategory = mxNodeDef->getNodeString();
const std::string &mxNodeType = mxNodeDef->getType();
const std::string &mxNodeGroup = mxNodeDef->getNodeGroup();
const std::string &mxNodeName = hdNodePath.GetName();

// Add the mxNode to the mxNodeGraph
mx::NodePtr mxNode =
_AddNodeToNodeGraph(mxNodeName, mxNodeCategory,
mxNodeType, mxNodeGraph, addedNodeNames);

if(mxNode->getNodeDef()) {
if (mxNode->getNodeDef()) {
// Sometimes mxNode->getNodeDef() starts failing.
// It seems to happen when there are connections with mismatched types.
// Explicitly setting the node def string appparently fixes the problem.
// If we don't do this code gen may fail.
if(mxNode->getNodeDefString().empty()) {
if (mxNode->getNodeDefString().empty()) {
mxNode->setNodeDefString(hdNodeType.GetText());
}
}
Expand All @@ -236,8 +237,9 @@ _AddMaterialXNode(
mxNode->setInputValue(mxInputName, mxInputValue, mxInputType);
}

// If this is a MaterialX Texture node
if (mxNodeCategory == "image" || mxNodeCategory == "tiledimage") {
// Stdlib MaterialX Texture node or a custom node that uses a texture
if (mxNodeCategory == "image" || mxNodeCategory == "tiledimage" ||
mxNodeGroup == "textureuser") {
// Save the corresponding MaterialX and Hydra names for ShaderGen
if (mxHdTextureMap) {
(*mxHdTextureMap)[mxNodeName] = connectionName;
Expand All @@ -249,15 +251,16 @@ _AddMaterialXNode(
}
}

// If this is a MaterialX primvar node
// MaterialX primvar node
if (mxNodeCategory == "geompropvalue") {
// Save the path to have the primvarName declared in ShaderGen
if (hdPrimvarNodes) {
hdPrimvarNodes->insert(hdNodePath);
}
}
// If this is a MaterialX texture coordinate node
if (mxNodeCategory == "texcoord") {
// Stdlib MaterialX texture coordinate node or a custom node that
// uses texture coordinates
if (mxNodeCategory == "texcoord" || mxNodeGroup == "texcoorduser") {
if (hdPrimvarNodes) {
// Make sure it has the index parameter set.
if (std::find(hdNodeParamNames.begin(), hdNodeParamNames.end(),
Expand Down
18 changes: 16 additions & 2 deletions pxr/imaging/hdSt/materialXFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,6 @@ _GetTextureCoordinateName(
// Save the default texture coordinate name for the glslfx header
*defaultTexcoordName = primvarName;
}

}
}

Expand Down Expand Up @@ -421,7 +420,22 @@ _UpdateTextureNodes(
// HdStMaterialXShaderGen match up correctly
std::string newConnName = texturePath.GetName() + "_" +
(*mxHdTextureMap)[texturePath.GetName()];
(*mxHdTextureMap)[texturePath.GetName()] = newConnName;

// Replace the texturePath.GetName() in the textureMap to the variable
// name used in the shader: textureName_fileInputName
std::string fileInputName = "file";
const mx::NodeDefPtr mxNodeDef =
mxDoc->getNodeDef(hdTextureNode.nodeTypeId.GetString());
if (mxNodeDef) {
for (auto mxInput : mxNodeDef->getInputs()) {
if (mxInput->getType() == "filename") {
fileInputName = mxInput->getName();
}
}
}
mxHdTextureMap->erase(texturePath.GetName());
mxHdTextureMap->insert(std::pair<std::string, std::string>(
texturePath.GetName() + "_" + fileInputName, newConnName));

// Make and add a new connection to the terminal node
HdMaterialConnection2 textureConn;
Expand Down
4 changes: 2 additions & 2 deletions pxr/imaging/hdSt/materialXShaderGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ HdStMaterialXShaderGen::_EmitMxFunctions(
if (texturePair.first == "domeLightFallback") {
continue;
}
emitLine(TfStringPrintf("#define %s_file HdGetSampler_%s()",
emitLine(TfStringPrintf("#define %s HdGetSampler_%s()",
texturePair.first.c_str(),
texturePair.second.c_str()),
mxStage, false);
Expand Down Expand Up @@ -626,7 +626,7 @@ HdStMaterialXShaderGen::_EmitMxInitFunction(
if (texturePair.first == "domeLightFallback") {
continue;
}
emitLine(texturePair.first + "_file = "
emitLine(texturePair.first + " = "
"HdGetSampler_" + texturePair.second + "()", mxStage);
}
emitLineBreak(mxStage);
Expand Down
20 changes: 14 additions & 6 deletions pxr/usd/usdMtlx/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,11 @@ ParseMetadata(
{
const auto& value = element->getAttribute(attribute);
if (!value.empty()) {
// Change the MaterialX Texture node role from 'texture2d' to 'texture'
if (key == SdrNodeMetadata->Role && value == "texture2d") {
// Change the 'texture2d' role for stdlib MaterialX Texture nodes,
// and the 'textureuser' role for custom MaterialX nodes that use
// textures, to 'texture' for Sdr.
if (key == SdrNodeMetadata->Role &&
(value == "texture2d" || value == "textureuser")) {
builder->metadata[key] = "texture";
}
else {
Expand Down Expand Up @@ -335,6 +338,13 @@ ParseElement(ShaderBuilder* builder, const mx::ConstNodeDefPtr& nodeDef)
if (nodeDef->getName() == "ND_texcoord_vector2") {
primvars.push_back(_GetPrimaryUvSetName());
}
// Add the default texturecoord primvar for custom nodes that use
// textures/texcoords
// XXX custom nodes must use the default texture coord name
if (nodeDef->getNodeGroup() == "textureuser" ||
nodeDef->getNodeGroup() == "texcoorduser") {
primvars.push_back(_GetPrimaryUvSetName());
}

// Also check internalgeomprops.
static const std::string internalgeompropsName("internalgeomprops");
Expand Down Expand Up @@ -380,8 +390,7 @@ class UsdMtlxParserPlugin : public NdrParserPlugin {
};

NdrNodeUniquePtr
UsdMtlxParserPlugin::Parse(
const NdrNodeDiscoveryResult& discoveryResult)
UsdMtlxParserPlugin::Parse(const NdrNodeDiscoveryResult& discoveryResult)
{
MaterialX::ConstDocumentPtr document = nullptr;
// Get the MaterialX document.
Expand All @@ -393,8 +402,7 @@ UsdMtlxParserPlugin::Parse(
return GetInvalidNode(discoveryResult);
}
} else if (!discoveryResult.sourceCode.empty()) {
document = UsdMtlxGetDocumentFromString(
discoveryResult.sourceCode);
document = UsdMtlxGetDocumentFromString(discoveryResult.sourceCode);
if (!document) {
TF_WARN("Invalid mtlx source code.");
return GetInvalidNode(discoveryResult);
Expand Down
55 changes: 29 additions & 26 deletions pxr/usd/usdMtlx/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,33 @@ UsdMtlxGetDocumentFromString(const std::string &mtlxXml)
return document;
}

static void
_ImportLibraries(mx::DocumentPtr *document, const NdrStringVec &searchPaths)
{
for (auto&& fileResult : NdrFsHelpersDiscoverFiles(searchPaths,
UsdMtlxStandardFileExtensions(), false)) {

// Read the file. If this fails due to an exception, a runtime
// error will be raised so we can just skip to the next file.
auto doc = UsdMtlxReadDocument(fileResult.resolvedUri);
if (!doc) {
continue;
}

try {
// Merge this document into the global library
// This properly sets the attributes on the destination
// elements, like source URI and namespace
(*document)->importLibrary(doc);
}
catch (mx::Exception& x) {
TF_RUNTIME_ERROR("MaterialX error reading '%s': %s",
fileResult.resolvedUri.c_str(),
x.what());
}
}
}

mx::ConstDocumentPtr
UsdMtlxGetDocument(const std::string& resolvedUri)
{
Expand All @@ -387,32 +414,8 @@ UsdMtlxGetDocument(const std::string& resolvedUri)
// Read the file or the standard library files.
if (resolvedUri.empty()) {
document = mx::createDocument();
for (auto&& fileResult:
NdrFsHelpersDiscoverFiles(
UsdMtlxStandardLibraryPaths(),
UsdMtlxStandardFileExtensions(),
false)) {

// Read the file. If this fails due to an exception, a runtime
// error will be raised so we can just skip to the next file.
auto doc = UsdMtlxReadDocument(fileResult.resolvedUri);
if (!doc) {
continue;
}

try {

// Merge this document into the global library
// This properly sets the attributes on the destination
// elements, like source URI and namespace
document->importLibrary(doc);
}
catch (mx::Exception& x) {
TF_RUNTIME_ERROR("MaterialX error reading '%s': %s",
fileResult.resolvedUri.c_str(),
x.what());
}
}
_ImportLibraries(&document, UsdMtlxStandardLibraryPaths());
_ImportLibraries(&document, UsdMtlxCustomSearchPaths());
}
else {
document = UsdMtlxReadDocument(resolvedUri);
Expand Down
19 changes: 19 additions & 0 deletions pxr/usdImaging/usdImagingGL/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3376,6 +3376,25 @@ if (MaterialX_FOUND AND ${PXR_ENABLE_MATERIALX_SUPPORT})
EXPECTED_RETURN_CODE 0
TESTENV testUsdImagingGLMaterialX
)

pxr_install_test_dir(
SRC testenv/testUsdImagingGLMaterialXCustomNodes
DEST testUsdImagingGLMaterialXCustomNodes
)

pxr_register_test(testUsdImagingGLMaterialXCustomNodes_customTexNode
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImagingGLBasicDrawing -lighting -sceneLights -shading smooth -complexity 1.3 -offscreen -camera /Camera -stage customTexNode.usda -write customTexNode.png"
IMAGE_DIFF_COMPARE
customTexNode.png
FAIL 0.5
FAIL_PERCENT 0.005
WARN 0.05
WARN_PERCENT 0.2
EXPECTED_RETURN_CODE 0
TESTENV testUsdImagingGLMaterialXCustomNodes
ENV
PXR_MTLX_PLUGIN_SEARCH_PATHS=./customNodeDefs
)
endif()

pxr_register_test(testUsdImagingGLInstancePrimvars
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0"?>
<materialx version="1.38">

<!-- custom node using texture coordinates -->
<nodedef name="ND_checker_float" node="checker3" nodegroup="texcoorduser">
<input name="scale" type="vector2" value="8.0, 8.0" />
<output name="out" type="color3" />
</nodedef>
<nodegraph name="NG_checker_float" nodedef="ND_checker_float">
<texcoord name="texcoord1" type="vector2">
<input name="index" type="integer" value="0" />
</texcoord>
<multiply name="mult1" type="vector2">
<input name="in1" type="vector2" nodename="texcoord1" />
<input name="in2" type="vector2" interfacename="scale" />
</multiply>
<swizzle name="swizz_x" type="float">
<input name="in" type="vector2" nodename="mult1" />
<input name="channels" type="string" value="x" />
</swizzle>
<swizzle name="swizz_y" type="float">
<input name="in" type="vector2" nodename="mult1" />
<input name="channels" type="string" value="y" />
</swizzle>
<floor name="floor1" type="float">
<input name="in" type="float" nodename="swizz_x" />
</floor>
<floor name="floor2" type="float">
<input name="in" type="float" nodename="swizz_y" />
</floor>
<add name="add1" type="float">
<input name="in1" type="float" nodename="floor1" />
<input name="in2" type="float" nodename="floor2" />
</add>
<modulo name="mod1" type="float">
<input name="in1" type="float" nodename="add1" />
<input name="in2" type="float" value="2.0" />
</modulo>
<swizzle name="swizz_xxx" type="color3">
<input name="in" type="float" nodename="mod1" />
<input name="channels" type="string" value="xxx" />
</swizzle>
<output name="out" type="color3" nodename="swizz_xxx" />
</nodegraph>

<!-- custom node using textures -->
<nodedef name="custom_image_nodedef" node="custom_image_node" nodegroup="textureuser">
<input name="file_input" type="filename" />
<output name="out_color" type="color3" />
</nodedef>
<nodegraph name="custom_image_nodegraph" nodedef="custom_image_nodedef">
<image name="image_node" type="color3">
<input name="file" type="filename" colorspace="gamma22" interfacename="file_input" />
</image>
<output name="out_color" type="color3" nodename="image_node" />
</nodegraph>

</materialx>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#usda 1.0
(
upAxis = "Z"
)

def "CustomNodeDef" (
references = @texturedSphere.usda@
)
{
rel material:binding = </MaterialX/Materials/test_material>
color3f[] primvars:displayColor = [(0.1, 0.5, 0.8)]
double radius = 9.0
matrix4d xformOp:transform = ( (1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(-10, 0, 0, 1) )
uniform token[] xformOpOrder = ["xformOp:transform"]
}

def "ImageCustomNodeDef" (
references = @texturedSphere.usda@
)
{
rel material:binding = </MaterialX/Materials/test_image_material>
color3f[] primvars:displayColor = [(0.1, 0.5, 0.8)]
matrix4d xformOp:transform = ( (1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(10, 0, 0, 1) )
uniform token[] xformOpOrder = ["xformOp:transform"]
}

def Scope "MaterialX"(
references = @./material.mtlx@</MaterialX>
)
{
}

def Xform "lights"
{
def DomeLight "DomeLight"
{
asset inputs:texture:file = @./StinsonBeach.hdr@
float xformOp:rotateX:Zup = 90
uniform token[] xformOpOrder = ["xformOp:rotateX:Zup"]
}
def SphereLight "Light"
{
float inputs:radius = 20
float inputs:intensity = 5
Vec3f xformOp:translate = (0, -50, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}

def Camera "Camera"
{
double3 xformOp:translate = (0, 0, 125)
float xformOp:rotateX:Zup = 90
uniform token[] xformOpOrder = ["xformOp:rotateX:Zup","xformOp:translate"]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6280d71

Please sign in to comment.