Blender V5.0
material.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2022 NVIDIA Corporation
2 * SPDX-FileCopyrightText: 2022 Blender Foundation
3 *
4 * SPDX-License-Identifier: Apache-2.0 */
5
6#include "hydra/material.h"
7#include "hydra/node_util.h"
8#include "hydra/session.h"
9#include "scene/scene.h"
10#include "scene/shader.h"
11#include "scene/shader_graph.h"
12#include "scene/shader_nodes.h"
13
14#include <pxr/imaging/hd/sceneDelegate.h>
15
17
18// clang-format off
19TF_DEFINE_PRIVATE_TOKENS(CyclesMaterialTokens,
20 ((cyclesSurface, "cycles:surface"))
21 ((cyclesDisplacement, "cycles:displacement"))
22 ((cyclesVolume, "cycles:volume"))
23 (UsdPreviewSurface)
24 (UsdUVTexture)
25 (UsdPrimvarReader_float)
26 (UsdPrimvarReader_float2)
27 (UsdPrimvarReader_float3)
28 (UsdPrimvarReader_float4)
29 (UsdPrimvarReader_int)
30 (UsdTransform2d)
31 (a)
32 (rgb)
33 (r)
34 (g)
35 (b)
36 (result)
37 (st)
38 (wrapS)
39 (wrapT)
40 (periodic)
41);
42// clang-format on
43
44// Simple class to handle remapping of USDPreviewSurface nodes and parameters to Cycles equivalents
46 using ParamMap = std::unordered_map<TfToken, ustring, TfToken::HashFunctor>;
47
48 public:
49 UsdToCyclesMapping(const char *nodeType, ParamMap paramMap)
50 : _nodeType(nodeType), _paramMap(std::move(paramMap))
51 {
52 }
53
54 ustring nodeType() const
55 {
56 return _nodeType;
57 }
58
59 virtual std::string parameterName(const TfToken &name,
60 const ShaderInput *inputConnection,
61 VtValue * /*value*/ = nullptr) const
62 {
63 // UsdNode.name -> Node.input
64 // These all follow a simple pattern that we can just remap
65 // based on the name or 'Node.input' type
66 if (inputConnection) {
67 if (name == CyclesMaterialTokens->a) {
68 return "alpha";
69 }
70 if (name == CyclesMaterialTokens->rgb) {
71 return "color";
72 }
73 // TODO: Is there a better mapping than 'color'?
74 if (name == CyclesMaterialTokens->r || name == CyclesMaterialTokens->g ||
75 name == CyclesMaterialTokens->b)
76 {
77 return "color";
78 }
79
80 if (name == CyclesMaterialTokens->result) {
81 switch (inputConnection->socket_type.type) {
84 case SocketType::INT:
86 return "alpha";
91 default:
92 return "color";
93 }
94 }
95 }
96
97 // Simple mapping case
98 const auto it = _paramMap.find(name);
99 return it != _paramMap.end() ? it->second.string() : name.GetString();
100 }
101
102 private:
103 const ustring _nodeType;
104 ParamMap _paramMap;
105};
106
108 public:
110
111 std::string parameterName(const TfToken &name,
112 const ShaderInput *inputConnection,
113 VtValue *value) const override
114 {
115 if (value) {
116 // Remap UsdUVTexture.wrapS and UsdUVTexture.wrapT to cycles_image_texture.extension
117 if (name == CyclesMaterialTokens->wrapS || name == CyclesMaterialTokens->wrapT) {
118 const std::string valueString = VtValue::Cast<std::string>(*value).Get<std::string>();
119
120 // A value of 'repeat' in USD is equivalent to 'periodic' in Cycles
121 if (valueString == "repeat") {
122 *value = VtValue(CyclesMaterialTokens->periodic);
123 }
124
125 return "extension";
126 }
127 }
128
129 return UsdToCyclesMapping::parameterName(name, inputConnection, value);
130 }
131};
132
133namespace {
134
135class UsdToCycles {
136 const UsdToCyclesMapping UsdPreviewSurface = {
137 "principled_bsdf",
138 {
139 {TfToken("diffuseColor"), ustring("base_color")},
140 {TfToken("emissiveColor"), ustring("emission")},
141 {TfToken("specularColor"), ustring("specular")},
142 {TfToken("clearcoatRoughness"), ustring("coat_roughness")},
143 {TfToken("opacity"), ustring("alpha")},
144 // opacityThreshold
145 // occlusion
146 // displacement
147 }};
148 const UsdToCyclesTexture UsdUVTexture = {
149 "image_texture",
150 {
151 {CyclesMaterialTokens->st, ustring("vector")},
152 {CyclesMaterialTokens->wrapS, ustring("extension")},
153 {CyclesMaterialTokens->wrapT, ustring("extension")},
154 {TfToken("file"), ustring("filename")},
155 {TfToken("sourceColorSpace"), ustring("colorspace")},
156 }};
157 const UsdToCyclesMapping UsdPrimvarReader = {"attribute",
158 {{TfToken("varname"), ustring("attribute")}}};
159
160 public:
161 const UsdToCyclesMapping *findUsd(const TfToken &usdNodeType)
162 {
163 if (usdNodeType == CyclesMaterialTokens->UsdPreviewSurface) {
164 return &UsdPreviewSurface;
165 }
166 if (usdNodeType == CyclesMaterialTokens->UsdUVTexture) {
167 return &UsdUVTexture;
168 }
169 if (usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float ||
170 usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float2 ||
171 usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float3 ||
172 usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float4 ||
173 usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_int)
174 {
175 return &UsdPrimvarReader;
176 }
177
178 return nullptr;
179 }
180 const UsdToCyclesMapping *findCycles(const ustring & /*cyclesNodeType*/)
181 {
182 return nullptr;
183 }
184};
185TfStaticData<UsdToCycles> sUsdToCyles;
186
187} // namespace
188
189HdCyclesMaterial::HdCyclesMaterial(const SdfPath &sprimId) : HdMaterial(sprimId) {}
190
192
194{
195 return DirtyBits::DirtyResource | DirtyBits::DirtyParams;
196}
197
198void HdCyclesMaterial::Sync(HdSceneDelegate *sceneDelegate,
199 HdRenderParam *renderParam,
200 HdDirtyBits *dirtyBits)
201{
202 if (*dirtyBits == DirtyBits::Clean) {
203 return;
204 }
205
206 Initialize(renderParam);
207
208 const SceneLock lock(renderParam);
209
210 const bool dirtyParams = (*dirtyBits & DirtyBits::DirtyParams);
211 const bool dirtyResource = (*dirtyBits & DirtyBits::DirtyResource);
212
213 VtValue value;
214 const SdfPath &id = GetId();
215
216 if (dirtyResource || dirtyParams) {
217 value = sceneDelegate->GetMaterialResource(id);
218
219 const HdMaterialNetwork2 *network = nullptr;
220 std::unique_ptr<HdMaterialNetwork2> networkConverted;
221 if (value.IsHolding<HdMaterialNetwork2>()) {
222 network = &value.UncheckedGet<HdMaterialNetwork2>();
223 }
224 else if (value.IsHolding<HdMaterialNetworkMap>()) {
225 const auto &networkOld = value.UncheckedGet<HdMaterialNetworkMap>();
226 // In the case of only parameter updates, there is no need to waste time converting to a
227 // HdMaterialNetwork2, as supporting HdMaterialNetworkMap for parameters only is trivial.
228 if (!_nodes.empty() && !dirtyResource) {
229 for (const auto &networkEntry : networkOld.map) {
230 UpdateParameters(networkEntry.second);
231 }
232 _shader->tag_modified();
233 }
234 else {
235 networkConverted = std::make_unique<HdMaterialNetwork2>();
236#if PXR_VERSION >= 2205
237 *networkConverted = HdConvertToHdMaterialNetwork2(networkOld);
238#else
239 HdMaterialNetwork2ConvertFromHdMaterialNetworkMap(networkOld, networkConverted.get());
240#endif
241 network = networkConverted.get();
242 }
243 }
244 else {
245 TF_RUNTIME_ERROR("Could not get a HdMaterialNetwork2.");
246 }
247
248 if (network) {
249 if (!_nodes.empty() && !dirtyResource) {
250 UpdateParameters(*network);
251 _shader->tag_modified();
252 }
253 else {
254 PopulateShaderGraph(*network);
255 }
256 }
257 }
258
259 if (_shader->is_modified()) {
260 _shader->tag_update(lock.scene);
261 }
262
263 *dirtyBits = DirtyBits::Clean;
264}
265
266void HdCyclesMaterial::UpdateParameters(NodeDesc &nodeDesc,
267 const std::map<TfToken, VtValue> &parameters,
268 const SdfPath &nodePath)
269{
270 for (const auto &param : parameters) {
271 VtValue value = param.second;
272
273 // See if the parameter name is in USDPreviewSurface terms, and needs to be converted
274 const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
275 const std::string inputName = inputMapping ?
276 inputMapping->parameterName(param.first, nullptr, &value) :
277 param.first.GetString();
278
279 // Find the input to write the parameter value to
280 const SocketType *input = nullptr;
281 for (const SocketType &socket : nodeDesc.node->type->inputs) {
282 if (string_iequals(socket.name.string(), inputName) || socket.ui_name == inputName) {
283 input = &socket;
284 break;
285 }
286 }
287
288 if (!input) {
289 TF_WARN("Could not find parameter '%s' on node '%s' ('%s')",
290 param.first.GetText(),
291 nodePath.GetText(),
292 nodeDesc.node->name.c_str());
293 continue;
294 }
295
296 SetNodeValue(nodeDesc.node, *input, value);
297 }
298}
299
300void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork &network)
301{
302 for (const HdMaterialNode &nodeEntry : network.nodes) {
303 const SdfPath &nodePath = nodeEntry.path;
304
305 const auto nodeIt = _nodes.find(nodePath);
306 if (nodeIt == _nodes.end()) {
307 TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
308 continue;
309 }
310
311 UpdateParameters(nodeIt->second, nodeEntry.parameters, nodePath);
312 }
313}
314
315void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork2 &network)
316{
317 for (const auto &nodeEntry : network.nodes) {
318 const SdfPath &nodePath = nodeEntry.first;
319
320 const auto nodeIt = _nodes.find(nodePath);
321 if (nodeIt == _nodes.end()) {
322 TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
323 continue;
324 }
325
326 UpdateParameters(nodeIt->second, nodeEntry.second.parameters, nodePath);
327 }
328}
329
330void HdCyclesMaterial::UpdateConnections(NodeDesc &nodeDesc,
331 const HdMaterialNode2 &matNode,
332 const SdfPath &nodePath,
333 ShaderGraph *shaderGraph)
334{
335 for (const auto &connection : matNode.inputConnections) {
336 const TfToken &dstSocketName = connection.first;
337
338 const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
339 const std::string inputName = inputMapping ?
340 inputMapping->parameterName(dstSocketName, nullptr) :
341 dstSocketName.GetString();
342
343 // Find the input to connect to on the passed in node
344 ShaderInput *input = nullptr;
345 for (ShaderInput *in : nodeDesc.node->inputs) {
346 if (string_iequals(in->socket_type.name.string(), inputName)) {
347 input = in;
348 break;
349 }
350 }
351
352 if (!input) {
353 TF_WARN("Ignoring connection on '%s.%s', input '%s' was not found",
354 nodePath.GetText(),
355 dstSocketName.GetText(),
356 dstSocketName.GetText());
357 continue;
358 }
359
360 // Now find the output to connect from
361 const auto &connectedNodes = connection.second;
362 if (connectedNodes.empty()) {
363 continue;
364 }
365
366 // TODO: Hydra allows multiple connections of the same input
367 // Unsure how to handle this in Cycles, so just use the first
368 if (connectedNodes.size() > 1) {
369 TF_WARN(
370 "Ignoring multiple connections to '%s.%s'", nodePath.GetText(), dstSocketName.GetText());
371 }
372
373 const SdfPath &upstreamNodePath = connectedNodes.front().upstreamNode;
374 const TfToken &upstreamOutputName = connectedNodes.front().upstreamOutputName;
375
376 const auto srcNodeIt = _nodes.find(upstreamNodePath);
377 if (srcNodeIt == _nodes.end()) {
378 TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', node '%s' was not found",
379 upstreamNodePath.GetText(),
380 upstreamOutputName.GetText(),
381 nodePath.GetText(),
382 dstSocketName.GetText(),
383 upstreamNodePath.GetText());
384 continue;
385 }
386
387 const UsdToCyclesMapping *outputMapping = srcNodeIt->second.mapping;
388 const std::string outputName = outputMapping ?
389 outputMapping->parameterName(upstreamOutputName, input) :
390 upstreamOutputName.GetString();
391
392 ShaderOutput *output = nullptr;
393 for (ShaderOutput *out : srcNodeIt->second.node->outputs) {
394 if (string_iequals(out->socket_type.name.string(), outputName)) {
395 output = out;
396 break;
397 }
398 }
399
400 if (!output) {
401 TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', output '%s' was not found",
402 upstreamNodePath.GetText(),
403 upstreamOutputName.GetText(),
404 nodePath.GetText(),
405 dstSocketName.GetText(),
406 upstreamOutputName.GetText());
407 continue;
408 }
409
410 shaderGraph->connect(output, input);
411 }
412}
413
414void HdCyclesMaterial::PopulateShaderGraph(const HdMaterialNetwork2 &networkMap)
415{
416 _nodes.clear();
417
418 unique_ptr<ShaderGraph> graph = make_unique<ShaderGraph>();
419
420 // Iterate all the nodes first and build a complete but unconnected graph with parameters set
421 for (const auto &nodeEntry : networkMap.nodes) {
422 NodeDesc nodeDesc = {};
423 const SdfPath &nodePath = nodeEntry.first;
424
425 const auto nodeIt = _nodes.find(nodePath);
426 // Create new node only if it does not exist yet
427 if (nodeIt != _nodes.end()) {
428 nodeDesc = nodeIt->second;
429 }
430 else {
431 // E.g. cycles_principled_bsdf or UsdPreviewSurface
432 const std::string &nodeTypeId = nodeEntry.second.nodeTypeId.GetString();
433
434 ustring cyclesType(nodeTypeId);
435 // Interpret a node type ID prefixed with cycles_<type> or cycles:<type> as a node of <type>
436 if (nodeTypeId.rfind("cycles", 0) == 0) {
437 cyclesType = nodeTypeId.substr(7);
438 nodeDesc.mapping = sUsdToCyles->findCycles(cyclesType);
439 }
440 else {
441 // Check if any remapping is needed (e.g. for USDPreviewSurface to Cycles nodes)
442 nodeDesc.mapping = sUsdToCyles->findUsd(nodeEntry.second.nodeTypeId);
443 if (nodeDesc.mapping) {
444 cyclesType = nodeDesc.mapping->nodeType();
445 }
446 }
447
448 // If it's a native Cycles' node-type, just do the lookup now.
449 if (const NodeType *nodeType = NodeType::find(cyclesType)) {
450 nodeDesc.node = graph->create_node(nodeType);
451 _nodes.emplace(nodePath, nodeDesc);
452 }
453 else {
454 TF_RUNTIME_ERROR("Could not create node '%s'", nodePath.GetText());
455 continue;
456 }
457 }
458
459 UpdateParameters(nodeDesc, nodeEntry.second.parameters, nodePath);
460 }
461
462 // Now that all nodes have been constructed, iterate the network again and build up any
463 // connections between nodes
464 for (const auto &nodeEntry : networkMap.nodes) {
465 const SdfPath &nodePath = nodeEntry.first;
466
467 const auto nodeIt = _nodes.find(nodePath);
468 if (nodeIt == _nodes.end()) {
469 TF_RUNTIME_ERROR("Could not find node '%s' to connect", nodePath.GetText());
470 continue;
471 }
472
473 UpdateConnections(nodeIt->second, nodeEntry.second, nodePath, graph.get());
474 }
475
476 // Finally connect the terminals to the graph output (Surface, Volume, Displacement)
477 for (const auto &terminalEntry : networkMap.terminals) {
478 const TfToken &terminalName = terminalEntry.first;
479 const HdMaterialConnection2 &connection = terminalEntry.second;
480
481 const auto nodeIt = _nodes.find(connection.upstreamNode);
482 if (nodeIt == _nodes.end()) {
483 TF_RUNTIME_ERROR("Could not find terminal node '%s'", connection.upstreamNode.GetText());
484 continue;
485 }
486
487 ShaderNode *const node = nodeIt->second.node;
488
489 const char *inputName = nullptr;
490 const char *outputName = nullptr;
491 if (terminalName == HdMaterialTerminalTokens->surface ||
492 terminalName == CyclesMaterialTokens->cyclesSurface)
493 {
494 inputName = "Surface";
495 // Find default output name based on the node if none is provided
496 if (node->type->name == "add_closure" || node->type->name == "mix_closure") {
497 outputName = "Closure";
498 }
499 else if (node->type->name == "emission") {
500 outputName = "Emission";
501 }
502 else {
503 outputName = "BSDF";
504 }
505 }
506 else if (terminalName == HdMaterialTerminalTokens->displacement ||
507 terminalName == CyclesMaterialTokens->cyclesDisplacement)
508 {
509 inputName = outputName = "Displacement";
510 }
511 else if (terminalName == HdMaterialTerminalTokens->volume ||
512 terminalName == CyclesMaterialTokens->cyclesVolume)
513 {
514 inputName = outputName = "Volume";
515 }
516
517 if (!connection.upstreamOutputName.IsEmpty()) {
518 outputName = connection.upstreamOutputName.GetText();
519 }
520
521 ShaderInput *const input = inputName ? graph->output()->input(inputName) : nullptr;
522 if (!input) {
523 TF_RUNTIME_ERROR("Could not find terminal input '%s.%s'",
524 connection.upstreamNode.GetText(),
525 inputName ? inputName : "<null>");
526 continue;
527 }
528
529 ShaderOutput *const output = outputName ? node->output(outputName) : nullptr;
530 if (!output) {
531 TF_RUNTIME_ERROR("Could not find terminal output '%s.%s'",
532 connection.upstreamNode.GetText(),
533 outputName ? outputName : "<null>");
534 continue;
535 }
536
537 graph->connect(output, input);
538 }
539
540 // Create the instanceId AOV output
541 {
542 const ustring instanceId(HdAovTokens->instanceId.GetString());
543
544 OutputAOVNode *aovNode = graph->create_node<OutputAOVNode>();
545 aovNode->set_name(instanceId);
546
547 AttributeNode *instanceIdNode = graph->create_node<AttributeNode>();
548 instanceIdNode->set_attribute(instanceId);
549
550 graph->connect(instanceIdNode->output("Fac"), aovNode->input("Value"));
551 }
552
553 _shader->set_graph(std::move(graph));
554}
555
556void HdCyclesMaterial::Finalize(HdRenderParam *renderParam)
557{
558 if (!_shader) {
559 return;
560 }
561
562 const SceneLock lock(renderParam);
563 const bool keep_nodes = static_cast<const HdCyclesSession *>(renderParam)->keep_nodes;
564
565 _nodes.clear();
566
567 if (!keep_nodes) {
568 lock.scene->delete_node(_shader);
569 }
570 _shader = nullptr;
571}
572
573void HdCyclesMaterial::Initialize(HdRenderParam *renderParam)
574{
575 if (_shader) {
576 return;
577 }
578
579 const SceneLock lock(renderParam);
580
581 _shader = lock.scene->create_node<Shader>();
582}
583
volatile int lock
PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override
Definition material.cpp:193
void Finalize(PXR_NS::HdRenderParam *renderParam) override
Definition material.cpp:556
HdCyclesMaterial(const PXR_NS::SdfPath &sprimId)
Definition material.cpp:189
~HdCyclesMaterial() override
void Sync(PXR_NS::HdSceneDelegate *sceneDelegate, PXR_NS::HdRenderParam *renderParam, PXR_NS::HdDirtyBits *dirtyBits) override
Definition material.cpp:198
void connect(ShaderOutput *from, ShaderInput *to)
const SocketType & socket_type
ShaderInput * input(const char *name)
ShaderOutput * output(const char *name)
UsdToCyclesMapping(const char *nodeType, ParamMap paramMap)
Definition material.cpp:49
virtual std::string parameterName(const TfToken &name, const ShaderInput *inputConnection, VtValue *=nullptr) const
Definition material.cpp:59
ustring nodeType() const
Definition material.cpp:54
UsdToCyclesMapping(const char *nodeType, ParamMap paramMap)
Definition material.cpp:49
std::string parameterName(const TfToken &name, const ShaderInput *inputConnection, VtValue *value) const override
Definition material.cpp:111
#define input
#define in
#define out
#define output
#define HDCYCLES_NAMESPACE_CLOSE_SCOPE
HDCYCLES_NAMESPACE_OPEN_SCOPE TF_DEFINE_PRIVATE_TOKENS(CyclesMaterialTokens,((cyclesSurface, "cycles:surface"))((cyclesDisplacement, "cycles:displacement"))((cyclesVolume, "cycles:volume"))(UsdPreviewSurface)(UsdUVTexture)(UsdPrimvarReader_float)(UsdPrimvarReader_float2)(UsdPrimvarReader_float3)(UsdPrimvarReader_float4)(UsdPrimvarReader_int)(UsdTransform2d)(a)(rgb)(r)(g)(b)(result)(st)(wrapS)(wrapT)(periodic))
void SetNodeValue(Node *node, const SocketType &socket, const VtValue &value)
const char * name
bool string_iequals(const string &a, const string &b)
Definition string.cpp:55
static const NodeType * find(ustring name)
ustring name
Definition node_type.h:125
const NodeType * type
Definition graph/node.h:178
ustring name
Definition node_type.h:81
ustring ui_name
Definition node_type.h:88
Type type
Definition node_type.h:82