1 //
2 // Copyright 2020 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #include "pxr/imaging/hdSt/materialXFilter.h"
25 #include "pxr/imaging/hdSt/materialXShaderGen.h"
26 #include "pxr/imaging/hdMtlx/hdMtlx.h"
27
28 #include "pxr/usd/sdr/registry.h"
29 #include "pxr/imaging/hio/glslfx.h"
30
31 #include "pxr/base/gf/vec2f.h"
32 #include "pxr/base/gf/matrix3d.h"
33 #include "pxr/base/gf/matrix4d.h"
34
35 #include "pxr/base/tf/diagnostic.h"
36
37 #include <MaterialXGenShader/Util.h>
38 #include <MaterialXGenShader/Shader.h>
39 #include <MaterialXRender/Util.h>
40 #include <MaterialXRender/LightHandler.h>
41
42 namespace mx = MaterialX;
43
44 PXR_NAMESPACE_OPEN_SCOPE
45
46
47 TF_DEFINE_PRIVATE_TOKENS(
48 _tokens,
49 (mtlx)
50
51 // Default Texture Coordinate Token
52 (st)
53 (texcoord)
54 (geomprop)
55
56 // Opacity Parameters
57 (opacity)
58 (opacityThreshold)
59 (transmission)
60 );
61
62
63 ////////////////////////////////////////////////////////////////////////////////
64 // Shader Gen Functions
65
66 // Generate the Glsl Pixel Shader based on the given mxContext and mxElement
67 // Based on MaterialXViewer Material::generateShader()
68 static std::string
_GenPixelShader(mx::GenContext & mxContext,mx::ElementPtr const & mxElem)69 _GenPixelShader(mx::GenContext & mxContext, mx::ElementPtr const& mxElem)
70 {
71 bool hasTransparency = mx::isTransparentSurface(mxElem,
72 mxContext.getShaderGenerator());
73
74 mx::GenContext materialContext = mxContext;
75 materialContext.getOptions().hwTransparency = hasTransparency;
76 materialContext.getOptions().hwShadowMap =
77 materialContext.getOptions().hwShadowMap && !hasTransparency;
78
79 // Use the domeLightPrefilter texture instead of sampling the Environment Map
80 materialContext.getOptions().hwSpecularEnvironmentMethod =
81 mx::HwSpecularEnvironmentMethod::SPECULAR_ENVIRONMENT_PREFILTER;
82
83 mx::ShaderPtr mxShader = mx::createShader("Shader", materialContext, mxElem);
84 if (mxShader) {
85 return mxShader->getSourceCode(mx::Stage::PIXEL);
86 }
87 return mx::EMPTY_STRING;
88 }
89
90 // Results in lightData.type = 1 for point lights in the Mx Shader
91 static const std::string mxDirectLightString =
92 R"(
93 <?xml version="1.0"?>
94 <materialx version="1.37">
95 <point_light name="pt_light" type="lightshader">
96 </point_light>
97 </materialx>
98 )";
99
100 // Use the given mxDocument to generate the corresponding glsl source code
101 // Based on MaterialXViewer Viewer::loadDocument()
102 std::string
HdSt_GenMaterialXShaderCode(mx::DocumentPtr const & mxDoc,mx::FileSearchPath const & searchPath,MxHdInfo const & mxHdInfo)103 HdSt_GenMaterialXShaderCode(
104 mx::DocumentPtr const& mxDoc,
105 mx::FileSearchPath const& searchPath,
106 MxHdInfo const& mxHdInfo)
107 {
108 // Initialize the Context for shaderGen.
109 mx::GenContext mxContext = HdStMaterialXShaderGen::create(mxHdInfo);
110 mxContext.registerSourceCodeSearchPath(searchPath);
111
112 // Add the Direct Light mtlx file to the mxDoc
113 mx::DocumentPtr lightDoc = mx::createDocument();
114 mx::readFromXmlString(lightDoc, mxDirectLightString);
115 mxDoc->importLibrary(lightDoc);
116
117 // Make sure the Light data properties are added to the mxLightData struct
118 mx::LightHandler lightHandler;
119 std::vector<mx::NodePtr> lights;
120 lightHandler.findLights(mxDoc, lights);
121 lightHandler.registerLights(mxDoc, lights, mxContext);
122
123 // Find renderable elements in the Mtlx Document.
124 std::vector<mx::TypedElementPtr> renderableElements;
125 mx::findRenderableElements(mxDoc, renderableElements);
126
127 // Should have exactly one renderable element (material).
128 if (renderableElements.size() != 1) {
129 TF_CODING_ERROR("Generated MaterialX Document does not "
130 "have 1 material");
131 return mx::EMPTY_STRING;
132 }
133
134 // Extract out the Surface Shader Node for the Material Node
135 mx::TypedElementPtr renderableElem = renderableElements.at(0);
136 mx::NodePtr node = renderableElem->asA<mx::Node>();
137 if (node && node->getType() == mx::MATERIAL_TYPE_STRING) {
138 std::unordered_set<mx::NodePtr> mxShaderNodes;
139 mxShaderNodes = mx::getShaderNodes(node, mx::SURFACE_SHADER_TYPE_STRING);
140 if (!mxShaderNodes.empty()) {
141 renderableElem = *mxShaderNodes.begin();
142 }
143 }
144 // Generate the PixelShader for the renderable element (surfaceshader).
145 const mx::ElementPtr & mxElem = mxDoc->getDescendant(
146 renderableElem->getNamePath());
147 mx::TypedElementPtr typedElem = mxElem ? mxElem->asA<mx::TypedElement>()
148 : nullptr;
149 if (typedElem) {
150 return _GenPixelShader(mxContext, typedElem);
151 }
152 return mx::EMPTY_STRING;
153 }
154
155
156 ////////////////////////////////////////////////////////////////////////////////
157 // Helper Functions to convert MX texture node parameters to Hd parameters
158
159 // Get the Hydra VtValue for the given MaterialX input value
160 static VtValue
_GetHdFilterValue(std::string const & mxInputValue)161 _GetHdFilterValue(std::string const& mxInputValue)
162 {
163 if(mxInputValue == "closest") {
164 return VtValue(HdStTextureTokens->nearestMipmapNearest);
165 }
166 return VtValue(HdStTextureTokens->linearMipmapLinear);
167 }
168
169 // Get the Hydra VtValue for the given MaterialX input value
170 static VtValue
_GetHdSamplerValue(std::string const & mxInputValue)171 _GetHdSamplerValue(std::string const& mxInputValue)
172 {
173 if (mxInputValue == "constant") {
174 return VtValue(HdStTextureTokens->black);
175 }
176 if (mxInputValue == "clamp") {
177 return VtValue(HdStTextureTokens->clamp);
178 }
179 if (mxInputValue == "mirror") {
180 return VtValue(HdStTextureTokens->mirror);
181 }
182 return VtValue(HdStTextureTokens->repeat);
183 }
184
185 // Translate the MaterialX texture node input into the Hydra equivalents
186 static void
_GetHdTextureParameters(std::string const & mxInputName,std::string const & mxInputValue,std::map<TfToken,VtValue> * hdTextureParams)187 _GetHdTextureParameters(
188 std::string const& mxInputName,
189 std::string const& mxInputValue,
190 std::map<TfToken, VtValue>* hdTextureParams)
191 {
192 // MaterialX has two texture2d node types <image> and <tiledimage>
193
194 // Properties common to both <image> and <tiledimage> texture nodes:
195 if (mxInputName == "filtertype") {
196 (*hdTextureParams)[HdStTextureTokens->minFilter] =
197 _GetHdFilterValue(mxInputValue);
198 (*hdTextureParams)[HdStTextureTokens->magFilter] =
199 VtValue(HdStTextureTokens->linear);
200 }
201
202 // Properties specific to <image> nodes:
203 else if (mxInputName == "uaddressmode") {
204 (*hdTextureParams)[HdStTextureTokens->wrapS] =
205 _GetHdSamplerValue(mxInputValue);
206 }
207 else if (mxInputName == "vaddressmode") {
208 (*hdTextureParams)[HdStTextureTokens->wrapT] =
209 _GetHdSamplerValue(mxInputValue);
210 }
211
212 // Properties specific to <tiledimage> nodes:
213 else if (mxInputName == "uvtiling" || mxInputName == "uvoffset" ||
214 mxInputName == "realworldimagesize" ||
215 mxInputName == "realworldtilesize") {
216 (*hdTextureParams)[HdStTextureTokens->wrapS] =
217 VtValue(HdStTextureTokens->repeat);
218 (*hdTextureParams)[HdStTextureTokens->wrapT] =
219 VtValue(HdStTextureTokens->repeat);
220 }
221 }
222
223 // Find the HdNode and its corresponding NodePath in the given HdNetwork
224 // based on the given HdConnection
225 static bool
_FindConnectedNode(HdMaterialNetwork2 const & hdNetwork,HdMaterialConnection2 const & hdConnection,HdMaterialNode2 * hdNode,SdfPath * hdNodePath)226 _FindConnectedNode(
227 HdMaterialNetwork2 const& hdNetwork,
228 HdMaterialConnection2 const& hdConnection,
229 HdMaterialNode2* hdNode,
230 SdfPath* hdNodePath)
231 {
232 // Get the path to the connected node
233 const SdfPath & connectionPath = hdConnection.upstreamNode;
234
235 // If this path is not in the network raise a warning
236 auto hdNodeIt = hdNetwork.nodes.find(connectionPath);
237 if (hdNodeIt == hdNetwork.nodes.end()) {
238 TF_WARN("Unknown material node '%s'", connectionPath.GetText());
239 return false;
240 }
241
242 // Otherwise return the HdNode and corresponding NodePath
243 *hdNode = hdNodeIt->second;
244 *hdNodePath = connectionPath;
245 return true;
246 }
247
248 // Get the Texture coordinate name if specified, otherwise get the default name
249 static void
_GetTextureCoordinateName(mx::DocumentPtr const & mxDoc,HdMaterialNetwork2 * hdNetwork,HdMaterialNode2 * hdTextureNode,SdfPath const & hdTextureNodePath,mx::StringMap * mxHdPrimvarMap,std::string * defaultTexcoordName)250 _GetTextureCoordinateName(
251 mx::DocumentPtr const &mxDoc,
252 HdMaterialNetwork2* hdNetwork,
253 HdMaterialNode2* hdTextureNode,
254 SdfPath const& hdTextureNodePath,
255 mx::StringMap* mxHdPrimvarMap,
256 std::string* defaultTexcoordName)
257 {
258 // Get the Texture Coordinate name through the connected node
259 bool textureCoordSet = false;
260 for (auto& inputConnections : hdTextureNode->inputConnections) {
261
262 // Texture Coordinates are connected through the 'texcoord' input
263 if ( inputConnections.first != _tokens->texcoord) {
264 continue;
265 }
266
267 for (auto& currConnection : inputConnections.second) {
268
269 // Get the connected Texture Coordinate node
270 SdfPath hdCoordNodePath;
271 HdMaterialNode2 hdCoordNode;
272 const bool found = _FindConnectedNode(*hdNetwork, currConnection,
273 &hdCoordNode, &hdCoordNodePath);
274 if (!found) {
275 continue;
276 }
277
278 // Get the texture coordinate name from the 'geomprop' parameter
279 auto coordNameIt = hdCoordNode.parameters.find(_tokens->geomprop);
280 if (coordNameIt != hdCoordNode.parameters.end()) {
281
282 std::string const& texcoordName =
283 HdMtlxConvertToString(coordNameIt->second);
284
285 // Set the 'st' parameter as a TfToken
286 // hdTextureNode->parameters[_tokens->st] =
287 hdNetwork->nodes[hdTextureNodePath].parameters[_tokens->st] =
288 TfToken(texcoordName.c_str());
289
290 // Save texture coordinate primvar name for the glslfx header;
291 // figure out the mx typename
292 mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(
293 hdCoordNode.nodeTypeId.GetString());
294 if (mxNodeDef) {
295 (*mxHdPrimvarMap)[texcoordName] = mxNodeDef->getType();
296 textureCoordSet = true;
297 break;
298 }
299 }
300 }
301 }
302
303 // If we did not have a connected node, and the 'st' parameter is not set
304 // get the default texture cordinate name from the textureNodes sdr metadata
305 if ( !textureCoordSet && hdTextureNode->parameters.find(_tokens->st) ==
306 hdTextureNode->parameters.end()) {
307
308 // Get the sdr node for the mxTexture node
309 SdrRegistry &sdrRegistry = SdrRegistry::GetInstance();
310 const SdrShaderNodeConstPtr sdrTextureNode =
311 sdrRegistry.GetShaderNodeByIdentifierAndType(
312 hdTextureNode->nodeTypeId, _tokens->mtlx);
313
314 if (sdrTextureNode) {
315
316 // Get the primvarname from the sdrTextureNode metadata
317 auto metadata = sdrTextureNode->GetMetadata();
318 auto primvarName = metadata[SdrNodeMetadata->Primvars];
319
320 // Set the 'st' parameter as a TfToken
321 hdNetwork->nodes[hdTextureNodePath].parameters[_tokens->st] =
322 TfToken(primvarName.c_str());
323
324 // Save the default texture coordinate name for the glslfx header
325 *defaultTexcoordName = primvarName;
326 }
327
328 }
329 }
330
331 // Add the Hydra texture node parameters to the texture nodes and connect the
332 // texture nodes to the terminal node
333 static void
_UpdateTextureNodes(mx::DocumentPtr const & mxDoc,HdMaterialNetwork2 * hdNetwork,SdfPath const & hdTerminalNodePath,std::set<SdfPath> const & hdTextureNodes,mx::StringMap * mxHdTextureMap,mx::StringMap * mxHdPrimvarMap,std::string * defaultTexcoordName)334 _UpdateTextureNodes(
335 mx::DocumentPtr const &mxDoc,
336 HdMaterialNetwork2* hdNetwork,
337 SdfPath const& hdTerminalNodePath,
338 std::set<SdfPath> const& hdTextureNodes,
339 mx::StringMap* mxHdTextureMap,
340 mx::StringMap* mxHdPrimvarMap,
341 std::string* defaultTexcoordName)
342 {
343 for (auto const& texturePath : hdTextureNodes) {
344 HdMaterialNode2 hdTextureNode = hdNetwork->nodes[texturePath];
345
346 _GetTextureCoordinateName(mxDoc, hdNetwork, &hdTextureNode, texturePath,
347 mxHdPrimvarMap, defaultTexcoordName);
348
349 // Gather the Hydra Texture Parameters
350 std::map<TfToken, VtValue> hdParameters;
351 for (auto const& currParam : hdTextureNode.parameters) {
352
353 // Get the MaterialX Input Value string
354 std::string mxInputValue = HdMtlxConvertToString(currParam.second);
355
356 // Get the Hydra equivalents for the MX Texture node parameters
357 std::string mxInputName = currParam.first.GetText();
358 _GetHdTextureParameters(mxInputName, mxInputValue, &hdParameters);
359 }
360
361 // Add the Hydra Texture Parameters to the Texture Node
362 for (auto param : hdParameters) {
363 hdNetwork->nodes[texturePath].parameters[param.first] = param.second;
364 }
365
366 // Connect the texture node to the terminal node for HdStMaterialNetwork
367 // Create a unique name for the new connection, and update the
368 // mxHdTextureMap with this connection name so Hydra's codegen and
369 // HdStMaterialXShaderGen match up correctly
370 std::string newConnName = texturePath.GetName() + "_" +
371 (*mxHdTextureMap)[texturePath.GetName()];;
372 (*mxHdTextureMap)[texturePath.GetName()] = newConnName;
373
374 // Make and add a new connection to the terminal node
375 HdMaterialConnection2 textureConn;
376 textureConn.upstreamNode = texturePath;
377 textureConn.upstreamOutputName = TfToken(newConnName);
378 hdNetwork->nodes[hdTerminalNodePath].
379 inputConnections[textureConn.upstreamOutputName] = {textureConn};
380 }
381 }
382
383 // Connect the primvar nodes to the terminal node
384 static void
_UpdatePrimvarNodes(mx::DocumentPtr const & mxDoc,HdMaterialNetwork2 * hdNetwork,SdfPath const & hdTerminalNodePath,std::set<SdfPath> const & hdPrimvarNodes,mx::StringMap * mxHdPrimvarMap)385 _UpdatePrimvarNodes(
386 mx::DocumentPtr const &mxDoc,
387 HdMaterialNetwork2* hdNetwork,
388 SdfPath const& hdTerminalNodePath,
389 std::set<SdfPath> const& hdPrimvarNodes,
390 mx::StringMap* mxHdPrimvarMap)
391 {
392 for (auto const& primvarPath : hdPrimvarNodes) {
393 HdMaterialNode2 hdPrimvarNode = hdNetwork->nodes[primvarPath];
394
395 // Save primvar name for the glslfx header
396 auto primvarNameIt = hdPrimvarNode.parameters.find(_tokens->geomprop);
397 if (primvarNameIt != hdPrimvarNode.parameters.end()) {
398 std::string const& primvarName =
399 HdMtlxConvertToString(primvarNameIt->second);
400
401 // Figure out the mx typename
402 mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(
403 hdPrimvarNode.nodeTypeId.GetString());
404 if (mxNodeDef) {
405 (*mxHdPrimvarMap)[primvarName] = mxNodeDef->getType();
406 }
407 }
408
409 // Connect the primvar node to the terminal node for HdStMaterialNetwork
410 // Create a unique name for the new connection.
411 std::string newConnName = primvarPath.GetName() + "_primvarconn";
412 HdMaterialConnection2 primvarConn;
413 primvarConn.upstreamNode = primvarPath;
414 primvarConn.upstreamOutputName = TfToken(newConnName);
415 hdNetwork->nodes[hdTerminalNodePath]
416 .inputConnections[primvarConn.upstreamOutputName] = {primvarConn};
417 }
418 }
419
420 static std::string const&
_GetMaterialTag(HdMaterialNode2 const & terminal)421 _GetMaterialTag(HdMaterialNode2 const& terminal)
422 {
423 // Masked MaterialTag:
424 // UsdPreviewSurface: terminal.opacityThreshold value > 0
425 // StandardSurface materials do not have an opacityThreshold parameter
426 // so we StandardSurface will not use the Masked materialTag.
427 for (auto const& currParam : terminal.parameters) {
428 if (currParam.first != _tokens->opacityThreshold) continue;
429
430 if (currParam.second.Get<float>() > 0.0f) {
431 return HdStMaterialTagTokens->masked.GetString();
432 }
433 }
434
435 // Translucent MaterialTag
436 bool isTranslucent = false;
437
438 // UsdPreviewSurface uses the opacity parameter to indicate the transparency
439 // when 1.0 the material is fully opaque, the smaller the value the more
440 // translucent the material, with a default value of 1.0
441 // StandardSurface indicates material transparency through two different
442 // parameters; the transmission parameter (float) where the greater the
443 // value the more transparent the material and a default value of 0.0,
444 // the opacity parameter (color3) indicating the opacity of the entire
445 // material, where the default value of (1,1,1) is fully opaque.
446
447 // First check the opacity and transmission connections
448 auto const& opacityConnIt = terminal.inputConnections.find(_tokens->opacity);
449 if (opacityConnIt != terminal.inputConnections.end()) {
450 return HdStMaterialTagTokens->translucent.GetString();
451 }
452
453 auto const& transmissionConnIt = terminal.inputConnections.find(
454 _tokens->transmission);
455 isTranslucent = (transmissionConnIt != terminal.inputConnections.end());
456
457 // Then check the opacity and transmission parameter value
458 if (!isTranslucent) {
459 for (auto const& currParam : terminal.parameters) {
460
461 // UsdPreviewSurface
462 if (currParam.first == _tokens->opacity &&
463 currParam.second.IsHolding<float>()) {
464 isTranslucent = currParam.second.Get<float>() < 1.0f;
465 break;
466 }
467 // StandardSurface
468 if (currParam.first == _tokens->opacity &&
469 currParam.second.IsHolding<GfVec3f>()) {
470 GfVec3f opacityColor = currParam.second.Get<GfVec3f>();
471 isTranslucent |= ( opacityColor[0] < 1.0f
472 || opacityColor[1] < 1.0f
473 || opacityColor[2] < 1.0f );
474 }
475 if (currParam.first == _tokens->transmission &&
476 currParam.second.IsHolding<float>()) {
477 isTranslucent |= currParam.second.Get<float>() > 0.0f;
478 }
479 }
480 }
481
482 if (isTranslucent) {
483 return HdStMaterialTagTokens->translucent.GetString();
484 }
485 return HdStMaterialTagTokens->defaultMaterialTag.GetString();
486 }
487
488
489 void
HdSt_ApplyMaterialXFilter(HdMaterialNetwork2 * hdNetwork,SdfPath const & materialPath,HdMaterialNode2 const & terminalNode,SdfPath const & terminalNodePath)490 HdSt_ApplyMaterialXFilter(
491 HdMaterialNetwork2* hdNetwork,
492 SdfPath const& materialPath,
493 HdMaterialNode2 const& terminalNode,
494 SdfPath const& terminalNodePath)
495 {
496 // Check if the Terminal is a MaterialX Node
497 SdrRegistry &sdrRegistry = SdrRegistry::GetInstance();
498 const SdrShaderNodeConstPtr mtlxSdrNode =
499 sdrRegistry.GetShaderNodeByIdentifierAndType(terminalNode.nodeTypeId,
500 _tokens->mtlx);
501
502 if (mtlxSdrNode) {
503
504 // Load Standard Libraries/setup SearchPaths (for mxDoc and mxShaderGen)
505 mx::FilePathVec libraryFolders;
506 mx::FileSearchPath searchPath;
507 searchPath.append(mx::FilePath(PXR_MATERIALX_STDLIB_DIR));
508 mx::DocumentPtr stdLibraries = mx::createDocument();
509 mx::loadLibraries(libraryFolders, searchPath, stdLibraries);
510
511 // Create the MaterialX Document from the HdMaterialNetwork
512 MxHdInfo mxHdInfo; // Hydra information for MaterialX glslfx shaderGen
513 std::set<SdfPath> hdTextureNodes;
514 std::set<SdfPath> hdPrimvarNodes;
515 mx::DocumentPtr mtlxDoc = HdMtlxCreateMtlxDocumentFromHdNetwork(
516 *hdNetwork,
517 terminalNode, // MaterialX HdNode
518 materialPath,
519 stdLibraries,
520 &hdTextureNodes,
521 &mxHdInfo.textureMap,
522 &hdPrimvarNodes);
523
524 // Add Hydra parameters for each of the Texture nodes
525 _UpdateTextureNodes(mtlxDoc, hdNetwork, terminalNodePath, hdTextureNodes,
526 &mxHdInfo.textureMap, &mxHdInfo.primvarMap,
527 &mxHdInfo.defaultTexcoordName);
528
529 _UpdatePrimvarNodes(mtlxDoc, hdNetwork, terminalNodePath,
530 hdPrimvarNodes, &mxHdInfo.primvarMap);
531
532 mxHdInfo.materialTag = _GetMaterialTag(terminalNode);
533
534 // Load MaterialX Document and generate the glslfxSource
535 std::string glslfxSource = HdSt_GenMaterialXShaderCode(mtlxDoc,
536 searchPath, mxHdInfo);
537
538 // Create a new terminal node with the new glslfxSource
539 SdrShaderNodeConstPtr sdrNode =
540 sdrRegistry.GetShaderNodeFromSourceCode(glslfxSource,
541 HioGlslfxTokens->glslfx,
542 NdrTokenMap()); // metadata
543 HdMaterialNode2 newTerminalNode;
544 newTerminalNode.nodeTypeId = sdrNode->GetIdentifier();
545 newTerminalNode.inputConnections = terminalNode.inputConnections;
546 newTerminalNode.parameters = terminalNode.parameters;
547
548 // Replace the original terminalNode with this newTerminalNode
549 hdNetwork->nodes[terminalNodePath] = newTerminalNode;
550 }
551 }
552
553 PXR_NAMESPACE_CLOSE_SCOPE
554