1 //
2 // Copyright 2021 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/hdMtlx/hdMtlx.h"
25 #include "pxr/imaging/hd/material.h"
26
27 #include "pxr/base/gf/vec2f.h"
28 #include "pxr/base/gf/matrix3d.h"
29 #include "pxr/base/gf/matrix4d.h"
30
31 #include "pxr/usd/sdf/path.h"
32 #include "pxr/usd/sdr/registry.h"
33 #include "pxr/base/tf/diagnostic.h"
34
35 #include <MaterialXCore/Document.h>
36 #include <MaterialXCore/Node.h>
37 #include <MaterialXFormat/Util.h>
38 #include <MaterialXFormat/XmlIo.h>
39
40 namespace mx = MaterialX;
41
42 PXR_NAMESPACE_OPEN_SCOPE
43
44 // Return the MaterialX Node Type based on the corresponding NodeDef name,
45 // which is stored as the hdNodeType.
46 static TfToken
_GetMxNodeType(mx::DocumentPtr const & mxDoc,TfToken const & hdNodeType)47 _GetMxNodeType(mx::DocumentPtr const& mxDoc, TfToken const& hdNodeType)
48 {
49 mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(hdNodeType.GetString());
50 if (!mxNodeDef){
51 TF_WARN("Unsupported node type '%s' cannot find the associated NodeDef.",
52 hdNodeType.GetText());
53 return TfToken();
54 }
55 return TfToken(mxNodeDef->getNodeString());
56 }
57
58 // Determine if the given mxInputName is of type mx::Vector3
59 // Hd stores both mx::Vector3 and mx::Color3 as a GlfVec3f
60 static bool
_IsInputVector3(std::string const & mxInputName)61 _IsInputVector3(std::string const& mxInputName)
62 {
63 // mxInputs from UsdPreviewSurface and standard_surface nodes that are
64 // Vector3 types
65 static const mx::StringSet Vector3Inputs = {"normal",
66 "coat_normal",
67 "tangent"};
68 return Vector3Inputs.count(mxInputName) > 0;
69 }
70
71 // Find the HdNode and its corresponding NodePath in the given HdNetwork
72 // based on the given HdConnection
73 static bool
_FindConnectedNode(HdMaterialNetwork2 const & hdNetwork,HdMaterialConnection2 const & hdConnection,HdMaterialNode2 * hdNode,SdfPath * hdNodePath)74 _FindConnectedNode(
75 HdMaterialNetwork2 const& hdNetwork,
76 HdMaterialConnection2 const& hdConnection,
77 HdMaterialNode2 * hdNode,
78 SdfPath * hdNodePath)
79 {
80 // Get the path to the connected node
81 const SdfPath & connectionPath = hdConnection.upstreamNode;
82
83 // If this path is not in the network raise a warning
84 auto hdNodeIt = hdNetwork.nodes.find(connectionPath);
85 if (hdNodeIt == hdNetwork.nodes.end()) {
86 TF_WARN("Unknown material node '%s'", connectionPath.GetText());
87 return false;
88 }
89
90 // Otherwise return the HdNode and corresponding NodePath
91 *hdNode = hdNodeIt->second;
92 *hdNodePath = connectionPath;
93 return true;
94 }
95
96 // Add the mxNode to the mxNodeGraph, or get the mxNode from the NodeGraph
97 static mx::NodePtr
_AddNodeToNodeGraph(std::string const & mxNodeName,std::string const & mxNodeCategory,std::string const & mxNodeType,mx::NodeGraphPtr const & mxNodeGraph,mx::StringSet * addedNodeNames)98 _AddNodeToNodeGraph(
99 std::string const& mxNodeName,
100 std::string const& mxNodeCategory,
101 std::string const& mxNodeType,
102 mx::NodeGraphPtr const& mxNodeGraph,
103 mx::StringSet * addedNodeNames)
104 {
105 // Add the node to the mxNodeGraph if needed
106 if (addedNodeNames->find(mxNodeName) == addedNodeNames->end()) {
107 addedNodeNames->insert(mxNodeName);
108 return mxNodeGraph->addNode(mxNodeCategory, mxNodeName, mxNodeType);
109 }
110 // Otherwise get the existing node from the mxNodeGraph
111 return mxNodeGraph->getNode(mxNodeName);
112 }
113
114 // Convert the HdParameterValue to a string MaterialX can understand
115 std::string
HdMtlxConvertToString(VtValue const & hdParameterValue)116 HdMtlxConvertToString(VtValue const& hdParameterValue)
117 {
118 std::ostringstream valStream;
119 if (hdParameterValue.IsHolding<bool>()) {
120 return (hdParameterValue.UncheckedGet<bool>()) ? "false"
121 : "true";
122 }
123 else if (hdParameterValue.IsHolding<int>() ||
124 hdParameterValue.IsHolding<float>()) {
125 valStream << hdParameterValue;
126 return valStream.str();
127 }
128 else if (hdParameterValue.IsHolding<GfVec2f>()) {
129 const GfVec2f & value = hdParameterValue.UncheckedGet<GfVec2f>();
130 valStream << value.data()[0] << ", " << value.data()[1];
131 return valStream.str();
132 }
133 else if (hdParameterValue.IsHolding<GfVec3f>()) {
134 const GfVec3f & value = hdParameterValue.UncheckedGet<GfVec3f>();
135 valStream << value.data()[0] << ", " << value.data()[1] << ", "
136 << value.data()[2];
137 return valStream.str();
138 }
139 else if (hdParameterValue.IsHolding<GfVec4f>()) {
140 const GfVec4f & value = hdParameterValue.UncheckedGet<GfVec4f>();
141 valStream << value.data()[0] << ", " << value.data()[1] << ", "
142 << value.data()[2] << ", " << value.data()[3];
143 return valStream.str();
144 }
145 else if (hdParameterValue.IsHolding<GfMatrix3d>()) {
146 const GfMatrix3d & value = hdParameterValue.UncheckedGet<GfMatrix3d>();
147 valStream << value[0][0] << ", " << value[0][1] << ", "
148 << value[0][2] << ", "
149 << value[1][0] << ", " << value[1][1] << ", "
150 << value[1][2] << ", "
151 << value[2][0] << ", " << value[2][1] << ", "
152 << value[2][2] << ", ";
153 return valStream.str();
154 }
155 else if (hdParameterValue.IsHolding<GfMatrix4d>()) {
156 const GfMatrix4d & value = hdParameterValue.UncheckedGet<GfMatrix4d>();
157 valStream << value[0][0] << ", " << value[0][1] << ", "
158 << value[0][2] << ", " << value[0][3] << ", "
159 << value[1][0] << ", " << value[1][1] << ", "
160 << value[1][2] << ", " << value[1][3] << ", "
161 << value[2][0] << ", " << value[2][1] << ", "
162 << value[2][2] << ", " << value[2][3] << ", "
163 << value[3][0] << ", " << value[3][1] << ", "
164 << value[3][2] << ", " << value[3][3] << ", ";
165 return valStream.str();
166 }
167 else if (hdParameterValue.IsHolding<SdfAssetPath>()) {
168 return hdParameterValue.UncheckedGet<SdfAssetPath>().GetAssetPath();
169 }
170 else if (hdParameterValue.IsHolding<std::string>()) {
171 return hdParameterValue.UncheckedGet<std::string>();
172 }
173 else {
174 TF_WARN("Unsupported Parameter Type '%s'",
175 hdParameterValue.GetTypeName().c_str());
176 return mx::EMPTY_STRING;
177 }
178 }
179
180 // Get the MaterialX Input information from the mxNodeDef and hdParameter
181 static void
_GetMxInputInfo(std::pair<TfToken,VtValue> const & hdParameter,mx::NodeDefPtr const & mxNodeDef,std::string * mxInputName,std::string * mxInputValue,std::string * mxInputType)182 _GetMxInputInfo(
183 std::pair<TfToken, VtValue> const& hdParameter,
184 mx::NodeDefPtr const& mxNodeDef,
185 std::string * mxInputName,
186 std::string * mxInputValue,
187 std::string * mxInputType)
188 {
189 // Get the mxInputName from the HdParameter
190 *mxInputName = hdParameter.first.GetText();
191
192 // Get the mxInputValue from the Value
193 *mxInputValue = HdMtlxConvertToString(hdParameter.second);
194
195 // Get the mxInputType for the mxNodeDef
196 mx::InputPtr mxInput = mxNodeDef->getInput(*mxInputName);
197 if (mxInput) {
198 *mxInputType = mxInput->getType();
199 }
200 }
201
202 // Add a MaterialX version of the HdNode to the mxDoc/mxNodeGraph
203 static mx::NodePtr
_AddMaterialXNode(HdMaterialNetwork2 const & hdNetwork,HdMaterialNode2 const & hdNode,SdfPath const & hdNodePath,mx::DocumentPtr const & mxDoc,mx::NodeGraphPtr const & mxNodeGraph,mx::StringSet * addedNodeNames,std::set<SdfPath> * hdTextureNodes,std::string const & connectionName,mx::StringMap * mxHdTextureMap,std::set<SdfPath> * hdPrimvarNodes)204 _AddMaterialXNode(
205 HdMaterialNetwork2 const& hdNetwork,
206 HdMaterialNode2 const& hdNode,
207 SdfPath const& hdNodePath,
208 mx::DocumentPtr const& mxDoc,
209 mx::NodeGraphPtr const& mxNodeGraph,
210 mx::StringSet * addedNodeNames,
211 std::set<SdfPath> * hdTextureNodes,
212 std::string const& connectionName,
213 mx::StringMap * mxHdTextureMap,
214 std::set<SdfPath> * hdPrimvarNodes)
215 {
216 // Get the mxNode information
217 mx::NodeDefPtr mxNodeDef = mxDoc->getNodeDef(hdNode.nodeTypeId.GetString());
218 if (!mxNodeDef){
219 TF_WARN("NodeDef not found for Node '%s'", hdNode.nodeTypeId.GetText());
220 return mx::NodePtr();
221 }
222 const std::string & mxNodeCategory = mxNodeDef->getNodeString();
223 const std::string & mxNodeType = mxNodeDef->getType();
224 const std::string & mxNodeName = hdNodePath.GetName();
225
226 // Add the mxNode to the mxNodeGraph
227 mx::NodePtr mxNode = _AddNodeToNodeGraph(mxNodeName, mxNodeCategory,
228 mxNodeType, mxNodeGraph, addedNodeNames);
229
230 // For each of the HdNode parameters add the corresponding parameter/input
231 // to the mxNode
232 for (auto const& currParam : hdNode.parameters) {
233
234 // Get the MaterialX Parameter info
235 std::string mxInputName, mxInputValue, mxInputType;
236 _GetMxInputInfo(currParam, mxNodeDef, &mxInputName,
237 &mxInputValue, &mxInputType);
238 mxNode->setInputValue(mxInputName, mxInputValue, mxInputType);
239
240 // If this is a MaterialX Texture node
241 if (mxNodeCategory == "image" || mxNodeCategory == "tiledimage") {
242
243 // Save the corresponding MaterialX and Hydra names for ShaderGen
244 if (mxHdTextureMap) {
245 (*mxHdTextureMap)[hdNodePath.GetName()] = connectionName;
246 }
247
248 // Save the path to adjust the parameters after traversing the network
249 if (hdTextureNodes) {
250 hdTextureNodes->insert(hdNodePath);
251 }
252 }
253
254 // If this is a MaterialX primvar node
255 if (mxNodeCategory == "geompropvalue") {
256 if (hdPrimvarNodes) {
257 hdPrimvarNodes->insert(hdNodePath);
258 }
259 }
260 }
261 return mxNode;
262 }
263
264 // Recurrsively traverse the HdNetwork and gather the nodes in the MaterialX
265 // NodeGraph and Document
266 static void
_GatherUpstreamNodes(HdMaterialNetwork2 const & hdNetwork,HdMaterialConnection2 const & hdConnection,mx::DocumentPtr const & mxDoc,mx::NodeGraphPtr * mxNodeGraph,mx::StringSet * addedNodeNames,mx::NodePtr * mxUpstreamNode,std::set<SdfPath> * hdTextureNodes,std::string const & connectionName,mx::StringMap * mxHdTextureMap,std::set<SdfPath> * hdPrimvarNodes)267 _GatherUpstreamNodes(
268 HdMaterialNetwork2 const& hdNetwork,
269 HdMaterialConnection2 const& hdConnection, // connection from previous node
270 mx::DocumentPtr const& mxDoc,
271 mx::NodeGraphPtr * mxNodeGraph,
272 mx::StringSet * addedNodeNames,
273 mx::NodePtr * mxUpstreamNode,
274 std::set<SdfPath> * hdTextureNodes,
275 std::string const& connectionName,
276 mx::StringMap * mxHdTextureMap,
277 std::set<SdfPath> * hdPrimvarNodes)
278 {
279 // Get the connected node (hdNode) from the hdConnection
280 SdfPath hdNodePath;
281 HdMaterialNode2 hdNode; // HdNode -> mxCurrNode
282 bool found = _FindConnectedNode(hdNetwork, hdConnection,
283 &hdNode, &hdNodePath);
284
285 if (!found) {
286 TF_WARN("Could not find the connected Node with path '%s'",
287 hdConnection.upstreamNode.GetText());
288 return;
289 }
290
291 // Initilize the mxNodeGraph if needed
292 if (!(*mxNodeGraph)) {
293 const std::string & nodeGraphName = hdNodePath.GetParentPath().GetName();
294 *mxNodeGraph = mxDoc->addNodeGraph(nodeGraphName);
295 }
296
297 // Add the node to the mxNodeGraph/mxDoc.
298 mx::NodePtr mxCurrNode = _AddMaterialXNode(hdNetwork, hdNode, hdNodePath,
299 mxDoc, *mxNodeGraph, addedNodeNames,
300 hdTextureNodes, connectionName, mxHdTextureMap,
301 hdPrimvarNodes);
302
303 if (!mxCurrNode) {
304 return;
305 }
306
307 // Continue traversing the upsteam connections to create the mxNodeGraph
308 for (auto & inputConnections: hdNode.inputConnections) {
309
310 TfToken connName = inputConnections.first;
311 for (auto & currConnection : inputConnections.second) {
312
313 // Gather the nodes uptream from the mxCurrNode
314 _GatherUpstreamNodes(hdNetwork, currConnection, mxDoc, mxNodeGraph,
315 addedNodeNames, mxUpstreamNode, hdTextureNodes,
316 connName.GetString(), mxHdTextureMap,
317 hdPrimvarNodes);
318
319 // Connect mxCurrNode to the mxUpstreamNode
320 mx::NodePtr mxNextNode = *mxUpstreamNode;
321
322 // Make sure to not add the same input twice
323 mx::InputPtr mxInput = mxCurrNode->getInput(connName);
324 if (!mxInput){
325 mxInput = mxCurrNode->addInput(connName, mxNextNode->getType());
326 }
327 mxInput->setConnectedNode(mxNextNode);
328 }
329 }
330
331 *mxUpstreamNode = mxCurrNode;
332 }
333
334
335 // Create a MaterialX Document from the given HdMaterialNetwork
336 mx::DocumentPtr
HdMtlxCreateMtlxDocumentFromHdNetwork(HdMaterialNetwork2 const & hdNetwork,HdMaterialNode2 const & hdMaterialXNode,SdfPath const & materialPath,mx::DocumentPtr const & libraries,std::set<SdfPath> * hdTextureNodes,mx::StringMap * mxHdTextureMap,std::set<SdfPath> * hdPrimvarNodes)337 HdMtlxCreateMtlxDocumentFromHdNetwork(
338 HdMaterialNetwork2 const& hdNetwork,
339 HdMaterialNode2 const& hdMaterialXNode,
340 SdfPath const& materialPath,
341 mx::DocumentPtr const& libraries,
342 std::set<SdfPath> * hdTextureNodes, // Paths to the Hd Texture Nodes
343 mx::StringMap * mxHdTextureMap, // Mx-Hd texture name counterparts
344 std::set<SdfPath> * hdPrimvarNodes) // Paths to the Hd primvar nodes
345 {
346 // Initialize a MaterialX Document
347 mx::DocumentPtr mxDoc = mx::createDocument();
348 mxDoc->importLibrary(libraries);
349
350 // Create a material that instantiates the shader
351 const std::string & materialName = materialPath.GetName();
352 TfToken mxType = _GetMxNodeType(mxDoc, hdMaterialXNode.nodeTypeId);
353 mx::NodePtr mxShaderNode = mxDoc->addNode(mxType.GetString(),
354 "SR_" + materialName,
355 "surfaceshader");
356 mx::NodePtr mxMaterial = mxDoc->addMaterialNode(materialName, mxShaderNode);
357
358 // Create mxNodeGraph from the inputConnections in the HdMaterialNetwork
359 mx::NodeGraphPtr mxNodeGraph;
360 mx::StringSet addedNodeNames; // Set of NodeNames in the mxNodeGraph
361 for (auto inputConns : hdMaterialXNode.inputConnections) {
362
363 const std::string & mxNodeGraphOutput = inputConns.first.GetString();
364 for (HdMaterialConnection2 & currConnection : inputConns.second) {
365
366 // Gather the nodes uptream from the hdMaterialXNode
367 mx::NodePtr mxUpstreamNode;
368 _GatherUpstreamNodes(hdNetwork, currConnection, mxDoc, &mxNodeGraph,
369 &addedNodeNames, &mxUpstreamNode, hdTextureNodes,
370 mxNodeGraphOutput, mxHdTextureMap, hdPrimvarNodes);
371
372 if (!mxUpstreamNode) {
373 continue;
374 }
375
376 // Connect currNode to the upstream Node
377 std::string fullOutputName = mxNodeGraphOutput + "_" +
378 currConnection.upstreamOutputName.GetString();
379 mx::OutputPtr mxOutput = mxNodeGraph->addOutput(fullOutputName,
380 mxUpstreamNode->getType());
381 mxOutput->setConnectedNode(mxUpstreamNode);
382
383 // Connect NodeGraph Output to the ShaderNode
384 mx::InputPtr mxInput = mxShaderNode->addInput(mxNodeGraphOutput,
385 mxOutput->getType());
386 mxInput->setConnectedOutput(mxOutput);
387 }
388 }
389
390 // Add Inputs - The StandardSurface or USDPreviewSurface inputs
391 for (auto currParameter : hdMaterialXNode.parameters) {
392
393 const std::string & mxInputName = currParameter.first.GetString();
394 mx::InputPtr mxInput = mxShaderNode->addInput(mxInputName);
395
396 // Convert the parameter to the appropriate MaterialX input format
397 VtValue hdParamValue = currParameter.second;
398 if (hdParamValue.IsHolding<bool>()) {
399 bool value = hdParamValue.UncheckedGet<bool>();
400 mxInput->setValue(value);
401 }
402 else if (hdParamValue.IsHolding<int>()) {
403 int value = hdParamValue.UncheckedGet<int>();
404 mxInput->setValue(value);
405 }
406 else if (hdParamValue.IsHolding<float>()) {
407 float value = hdParamValue.UncheckedGet<float>();
408 mxInput->setValue(value);
409 }
410 else if (hdParamValue.IsHolding<GfVec3f>()) {
411
412 const GfVec3f & value = hdParamValue.UncheckedGet<GfVec3f>();
413 // Check if the parameter is a mx::vector3 or mx::color3
414 if (_IsInputVector3(mxInputName)) {
415 mxInput->setValue(mx::Vector3(value.data()[0],
416 value.data()[1],
417 value.data()[2]));
418 }
419 else {
420 mxInput->setValue(mx::Color3(value.data()[0],
421 value.data()[1],
422 value.data()[2]));
423 }
424 }
425 else {
426 mxShaderNode->removeInput(mxInputName);
427 TF_WARN("Unsupported Input Type '%s' for mxNode '%s' of type '%s'",
428 hdParamValue.GetTypeName().c_str(), mxInputName.c_str(),
429 mxType.GetText());
430 }
431 }
432
433 // Validate the MaterialX Document.
434 std::string message;
435 if (!mxDoc->validate(&message)) {
436 TF_WARN("Validation warnings for generated MaterialX file.\n%s\n",
437 message.c_str());
438 }
439
440 return mxDoc;
441 }
442
443 PXR_NAMESPACE_CLOSE_SCOPE
444