Blender V4.3
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 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#if 1
220 const HdMaterialNetwork2 *network = nullptr;
221 std::unique_ptr<HdMaterialNetwork2> networkConverted;
222 if (value.IsHolding<HdMaterialNetwork2>()) {
223 network = &value.UncheckedGet<HdMaterialNetwork2>();
224 }
225 else if (value.IsHolding<HdMaterialNetworkMap>()) {
226 const auto &networkOld = value.UncheckedGet<HdMaterialNetworkMap>();
227 // In the case of only parameter updates, there is no need to waste time converting to a
228 // HdMaterialNetwork2, as supporting HdMaterialNetworkMap for parameters only is trivial.
229 if (!_nodes.empty() && !dirtyResource) {
230 for (const auto &networkEntry : networkOld.map) {
231 UpdateParameters(networkEntry.second);
232 }
233 _shader->tag_modified();
234 }
235 else {
236 networkConverted = std::make_unique<HdMaterialNetwork2>();
237# if PXR_VERSION >= 2205
238 *networkConverted = HdConvertToHdMaterialNetwork2(networkOld);
239# else
240 HdMaterialNetwork2ConvertFromHdMaterialNetworkMap(networkOld, networkConverted.get());
241# endif
242 network = networkConverted.get();
243 }
244 }
245 else {
246 TF_RUNTIME_ERROR("Could not get a HdMaterialNetwork2.");
247 }
248
249 if (network) {
250 if (!_nodes.empty() && !dirtyResource) {
251 UpdateParameters(*network);
252 _shader->tag_modified();
253 }
254 else {
255 PopulateShaderGraph(*network);
256 }
257 }
258#endif
259 }
260
261 if (_shader->is_modified()) {
262 _shader->tag_update(lock.scene);
263 }
264
265 *dirtyBits = DirtyBits::Clean;
266}
267
268void HdCyclesMaterial::UpdateParameters(NodeDesc &nodeDesc,
269 const std::map<TfToken, VtValue> &parameters,
270 const SdfPath &nodePath)
271{
272 for (const auto &param : parameters) {
273 VtValue value = param.second;
274
275 // See if the parameter name is in USDPreviewSurface terms, and needs to be converted
276 const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
277 const std::string inputName = inputMapping ?
278 inputMapping->parameterName(param.first, nullptr, &value) :
279 param.first.GetString();
280
281 // Find the input to write the parameter value to
282 const SocketType *input = nullptr;
283 for (const SocketType &socket : nodeDesc.node->type->inputs) {
284 if (string_iequals(socket.name.string(), inputName) || socket.ui_name == inputName) {
285 input = &socket;
286 break;
287 }
288 }
289
290 if (!input) {
291 TF_WARN("Could not find parameter '%s' on node '%s' ('%s')",
292 param.first.GetText(),
293 nodePath.GetText(),
294 nodeDesc.node->name.c_str());
295 continue;
296 }
297
298 SetNodeValue(nodeDesc.node, *input, value);
299 }
300}
301
302void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork &network)
303{
304 for (const HdMaterialNode &nodeEntry : network.nodes) {
305 const SdfPath &nodePath = nodeEntry.path;
306
307 const auto nodeIt = _nodes.find(nodePath);
308 if (nodeIt == _nodes.end()) {
309 TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
310 continue;
311 }
312
313 UpdateParameters(nodeIt->second, nodeEntry.parameters, nodePath);
314 }
315}
316
317void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork2 &network)
318{
319 for (const auto &nodeEntry : network.nodes) {
320 const SdfPath &nodePath = nodeEntry.first;
321
322 const auto nodeIt = _nodes.find(nodePath);
323 if (nodeIt == _nodes.end()) {
324 TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
325 continue;
326 }
327
328 UpdateParameters(nodeIt->second, nodeEntry.second.parameters, nodePath);
329 }
330}
331
332void HdCyclesMaterial::UpdateConnections(NodeDesc &nodeDesc,
333 const HdMaterialNode2 &matNode,
334 const SdfPath &nodePath,
335 ShaderGraph *shaderGraph)
336{
337 for (const auto &connection : matNode.inputConnections) {
338 const TfToken &dstSocketName = connection.first;
339
340 const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
341 const std::string inputName = inputMapping ?
342 inputMapping->parameterName(dstSocketName, nullptr) :
343 dstSocketName.GetString();
344
345 // Find the input to connect to on the passed in node
346 ShaderInput *input = nullptr;
347 for (ShaderInput *in : nodeDesc.node->inputs) {
348 if (string_iequals(in->socket_type.name.string(), inputName)) {
349 input = in;
350 break;
351 }
352 }
353
354 if (!input) {
355 TF_WARN("Ignoring connection on '%s.%s', input '%s' was not found",
356 nodePath.GetText(),
357 dstSocketName.GetText(),
358 dstSocketName.GetText());
359 continue;
360 }
361
362 // Now find the output to connect from
363 const auto &connectedNodes = connection.second;
364 if (connectedNodes.empty()) {
365 continue;
366 }
367
368 // TODO: Hydra allows multiple connections of the same input
369 // Unsure how to handle this in Cycles, so just use the first
370 if (connectedNodes.size() > 1) {
371 TF_WARN(
372 "Ignoring multiple connections to '%s.%s'", nodePath.GetText(), dstSocketName.GetText());
373 }
374
375 const SdfPath &upstreamNodePath = connectedNodes.front().upstreamNode;
376 const TfToken &upstreamOutputName = connectedNodes.front().upstreamOutputName;
377
378 const auto srcNodeIt = _nodes.find(upstreamNodePath);
379 if (srcNodeIt == _nodes.end()) {
380 TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', node '%s' was not found",
381 upstreamNodePath.GetText(),
382 upstreamOutputName.GetText(),
383 nodePath.GetText(),
384 dstSocketName.GetText(),
385 upstreamNodePath.GetText());
386 continue;
387 }
388
389 const UsdToCyclesMapping *outputMapping = srcNodeIt->second.mapping;
390 const std::string outputName = outputMapping ?
391 outputMapping->parameterName(upstreamOutputName, input) :
392 upstreamOutputName.GetString();
393
394 ShaderOutput *output = nullptr;
395 for (ShaderOutput *out : srcNodeIt->second.node->outputs) {
396 if (string_iequals(out->socket_type.name.string(), outputName)) {
397 output = out;
398 break;
399 }
400 }
401
402 if (!output) {
403 TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', output '%s' was not found",
404 upstreamNodePath.GetText(),
405 upstreamOutputName.GetText(),
406 nodePath.GetText(),
407 dstSocketName.GetText(),
408 upstreamOutputName.GetText());
409 continue;
410 }
411
412 shaderGraph->connect(output, input);
413 }
414}
415
416void HdCyclesMaterial::PopulateShaderGraph(const HdMaterialNetwork2 &networkMap)
417{
418 _nodes.clear();
419
420 auto graph = new ShaderGraph();
421
422 // Iterate all the nodes first and build a complete but unconnected graph with parameters set
423 for (const auto &nodeEntry : networkMap.nodes) {
424 NodeDesc nodeDesc = {};
425 const SdfPath &nodePath = nodeEntry.first;
426
427 const auto nodeIt = _nodes.find(nodePath);
428 // Create new node only if it does not exist yet
429 if (nodeIt != _nodes.end()) {
430 nodeDesc = nodeIt->second;
431 }
432 else {
433 // E.g. cycles_principled_bsdf or UsdPreviewSurface
434 const std::string &nodeTypeId = nodeEntry.second.nodeTypeId.GetString();
435
436 ustring cyclesType(nodeTypeId);
437 // Interpret a node type ID prefixed with cycles_<type> or cycles:<type> as a node of <type>
438 if (nodeTypeId.rfind("cycles", 0) == 0) {
439 cyclesType = nodeTypeId.substr(7);
440 nodeDesc.mapping = sUsdToCyles->findCycles(cyclesType);
441 }
442 else {
443 // Check if any remapping is needed (e.g. for USDPreviewSurface to Cycles nodes)
444 nodeDesc.mapping = sUsdToCyles->findUsd(nodeEntry.second.nodeTypeId);
445 if (nodeDesc.mapping) {
446 cyclesType = nodeDesc.mapping->nodeType();
447 }
448 }
449
450 // If it's a native Cycles' node-type, just do the lookup now.
451 if (const NodeType *nodeType = NodeType::find(cyclesType)) {
452 nodeDesc.node = static_cast<ShaderNode *>(nodeType->create(nodeType));
453 nodeDesc.node->set_owner(graph);
454
455 graph->add(nodeDesc.node);
456
457 _nodes.emplace(nodePath, nodeDesc);
458 }
459 else {
460 TF_RUNTIME_ERROR("Could not create node '%s'", nodePath.GetText());
461 continue;
462 }
463 }
464
465 UpdateParameters(nodeDesc, nodeEntry.second.parameters, nodePath);
466 }
467
468 // Now that all nodes have been constructed, iterate the network again and build up any
469 // connections between nodes
470 for (const auto &nodeEntry : networkMap.nodes) {
471 const SdfPath &nodePath = nodeEntry.first;
472
473 const auto nodeIt = _nodes.find(nodePath);
474 if (nodeIt == _nodes.end()) {
475 TF_RUNTIME_ERROR("Could not find node '%s' to connect", nodePath.GetText());
476 continue;
477 }
478
479 UpdateConnections(nodeIt->second, nodeEntry.second, nodePath, graph);
480 }
481
482 // Finally connect the terminals to the graph output (Surface, Volume, Displacement)
483 for (const auto &terminalEntry : networkMap.terminals) {
484 const TfToken &terminalName = terminalEntry.first;
485 const HdMaterialConnection2 &connection = terminalEntry.second;
486
487 const auto nodeIt = _nodes.find(connection.upstreamNode);
488 if (nodeIt == _nodes.end()) {
489 TF_RUNTIME_ERROR("Could not find terminal node '%s'", connection.upstreamNode.GetText());
490 continue;
491 }
492
493 ShaderNode *const node = nodeIt->second.node;
494
495 const char *inputName = nullptr;
496 const char *outputName = nullptr;
497 if (terminalName == HdMaterialTerminalTokens->surface ||
498 terminalName == CyclesMaterialTokens->cyclesSurface)
499 {
500 inputName = "Surface";
501 // Find default output name based on the node if none is provided
502 if (node->type->name == "add_closure" || node->type->name == "mix_closure") {
503 outputName = "Closure";
504 }
505 else if (node->type->name == "emission") {
506 outputName = "Emission";
507 }
508 else {
509 outputName = "BSDF";
510 }
511 }
512 else if (terminalName == HdMaterialTerminalTokens->displacement ||
513 terminalName == CyclesMaterialTokens->cyclesDisplacement)
514 {
515 inputName = outputName = "Displacement";
516 }
517 else if (terminalName == HdMaterialTerminalTokens->volume ||
518 terminalName == CyclesMaterialTokens->cyclesVolume)
519 {
520 inputName = outputName = "Volume";
521 }
522
523 if (!connection.upstreamOutputName.IsEmpty()) {
524 outputName = connection.upstreamOutputName.GetText();
525 }
526
527 ShaderInput *const input = inputName ? graph->output()->input(inputName) : nullptr;
528 if (!input) {
529 TF_RUNTIME_ERROR("Could not find terminal input '%s.%s'",
530 connection.upstreamNode.GetText(),
531 inputName ? inputName : "<null>");
532 continue;
533 }
534
535 ShaderOutput *const output = outputName ? node->output(outputName) : nullptr;
536 if (!output) {
537 TF_RUNTIME_ERROR("Could not find terminal output '%s.%s'",
538 connection.upstreamNode.GetText(),
539 outputName ? outputName : "<null>");
540 continue;
541 }
542
543 graph->connect(output, input);
544 }
545
546 // Create the instanceId AOV output
547 {
548 const ustring instanceId(HdAovTokens->instanceId.GetString());
549
550 OutputAOVNode *aovNode = graph->create_node<OutputAOVNode>();
551 aovNode->set_name(instanceId);
552 graph->add(aovNode);
553
554 AttributeNode *instanceIdNode = graph->create_node<AttributeNode>();
555 instanceIdNode->set_attribute(instanceId);
556 graph->add(instanceIdNode);
557
558 graph->connect(instanceIdNode->output("Fac"), aovNode->input("Value"));
559 }
560
561 _shader->set_graph(graph);
562}
563
564void HdCyclesMaterial::Finalize(HdRenderParam *renderParam)
565{
566 if (!_shader) {
567 return;
568 }
569
570 const SceneLock lock(renderParam);
571 const bool keep_nodes = static_cast<const HdCyclesSession *>(renderParam)->keep_nodes;
572
573 _nodes.clear();
574
575 if (!keep_nodes) {
576 lock.scene->delete_node(_shader);
577 }
578 _shader = nullptr;
579}
580
581void HdCyclesMaterial::Initialize(HdRenderParam *renderParam)
582{
583 if (_shader) {
584 return;
585 }
586
587 const SceneLock lock(renderParam);
588
589 _shader = lock.scene->create_node<Shader>();
590}
591
volatile int lock
PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override
Definition material.cpp:193
void Finalize(PXR_NS::HdRenderParam *renderParam) override
Definition material.cpp:564
HdCyclesMaterial(const PXR_NS::SdfPath &sprimId)
Definition material.cpp:189
~HdCyclesMaterial() override
Definition material.cpp:191
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)
virtual std::string parameterName(const TfToken &name, const ShaderInput *inputConnection, VtValue *value=nullptr) const
Definition material.cpp:59
UsdToCyclesMapping(const char *nodeType, ParamMap paramMap)
Definition material.cpp:49
ustring nodeType() const
Definition material.cpp:54
std::string parameterName(const TfToken &name, const ShaderInput *inputConnection, VtValue *value) const override
Definition material.cpp:111
local_group_size(16, 16) .push_constant(Type b
#define HDCYCLES_NAMESPACE_CLOSE_SCOPE
double parameters[NUM_PARAMETERS]
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)
bool string_iequals(const string &a, const string &b)
Definition string.cpp:55
static const NodeType * find(ustring name)
void set_owner(const NodeOwner *owner_)
Type type
Definition node_type.h:80