Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/cycles/hydra/material.cpp')
-rw-r--r--intern/cycles/hydra/material.cpp589
1 files changed, 589 insertions, 0 deletions
diff --git a/intern/cycles/hydra/material.cpp b/intern/cycles/hydra/material.cpp
new file mode 100644
index 00000000000..a595102a605
--- /dev/null
+++ b/intern/cycles/hydra/material.cpp
@@ -0,0 +1,589 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/material.h"
+#include "hydra/node_util.h"
+#include "hydra/session.h"
+#include "scene/scene.h"
+#include "scene/shader.h"
+#include "scene/shader_graph.h"
+#include "scene/shader_nodes.h"
+
+#include <pxr/imaging/hd/sceneDelegate.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+// clang-format off
+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)
+);
+// clang-format on
+
+namespace {
+
+// Simple class to handle remapping of USDPreviewSurface nodes and parameters to Cycles equivalents
+class UsdToCyclesMapping {
+ using ParamMap = std::unordered_map<TfToken, ustring, TfToken::HashFunctor>;
+
+ public:
+ UsdToCyclesMapping(const char *nodeType, ParamMap paramMap)
+ : _nodeType(nodeType), _paramMap(std::move(paramMap))
+ {
+ }
+
+ ustring nodeType() const
+ {
+ return _nodeType;
+ }
+
+ virtual std::string parameterName(const TfToken &name,
+ const ShaderInput *inputConnection,
+ VtValue *value = nullptr) const
+ {
+ // UsdNode.name -> Node.input
+ // These all follow a simple pattern that we can just remap
+ // based on the name or 'Node.input' type
+ if (inputConnection) {
+ if (name == CyclesMaterialTokens->a) {
+ return "alpha";
+ }
+ if (name == CyclesMaterialTokens->rgb) {
+ return "color";
+ }
+ // TODO: Is there a better mapping than 'color'?
+ if (name == CyclesMaterialTokens->r || name == CyclesMaterialTokens->g ||
+ name == CyclesMaterialTokens->b) {
+ return "color";
+ }
+
+ if (name == CyclesMaterialTokens->result) {
+ switch (inputConnection->socket_type.type) {
+ case SocketType::BOOLEAN:
+ case SocketType::FLOAT:
+ case SocketType::INT:
+ case SocketType::UINT:
+ return "alpha";
+ case SocketType::COLOR:
+ case SocketType::VECTOR:
+ case SocketType::POINT:
+ case SocketType::NORMAL:
+ default:
+ return "color";
+ }
+ }
+ }
+
+ // Simple mapping case
+ const auto it = _paramMap.find(name);
+ return it != _paramMap.end() ? it->second.string() : name.GetString();
+ }
+
+ private:
+ const ustring _nodeType;
+ ParamMap _paramMap;
+};
+
+class UsdToCyclesTexture : public UsdToCyclesMapping {
+ public:
+ using UsdToCyclesMapping::UsdToCyclesMapping;
+
+ std::string parameterName(const TfToken &name,
+ const ShaderInput *inputConnection,
+ VtValue *value) const override
+ {
+ if (value) {
+ // Remap UsdUVTexture.wrapS and UsdUVTexture.wrapT to cycles_image_texture.extension
+ if (name == CyclesMaterialTokens->wrapS || name == CyclesMaterialTokens->wrapT) {
+ std::string valueString = VtValue::Cast<std::string>(*value).Get<std::string>();
+
+ // A value of 'repeat' in USD is equivalent to 'periodic' in Cycles
+ if (valueString == "repeat") {
+ *value = VtValue(CyclesMaterialTokens->periodic);
+ }
+
+ return "extension";
+ }
+ }
+
+ return UsdToCyclesMapping::parameterName(name, inputConnection, value);
+ }
+};
+
+class UsdToCycles {
+ const UsdToCyclesMapping UsdPreviewSurface = {
+ "principled_bsdf",
+ {
+ {TfToken("diffuseColor"), ustring("base_color")},
+ {TfToken("emissiveColor"), ustring("emission")},
+ {TfToken("specularColor"), ustring("specular")},
+ {TfToken("clearcoatRoughness"), ustring("clearcoat_roughness")},
+ {TfToken("opacity"), ustring("alpha")},
+ // opacityThreshold
+ // occlusion
+ // displacement
+ }};
+ const UsdToCyclesTexture UsdUVTexture = {
+ "image_texture",
+ {
+ {CyclesMaterialTokens->st, ustring("vector")},
+ {CyclesMaterialTokens->wrapS, ustring("extension")},
+ {CyclesMaterialTokens->wrapT, ustring("extension")},
+ {TfToken("file"), ustring("filename")},
+ {TfToken("sourceColorSpace"), ustring("colorspace")},
+ }};
+ const UsdToCyclesMapping UsdPrimvarReader = {"attribute",
+ {{TfToken("varname"), ustring("attribute")}}};
+
+ public:
+ const UsdToCyclesMapping *findUsd(const TfToken &usdNodeType)
+ {
+ if (usdNodeType == CyclesMaterialTokens->UsdPreviewSurface) {
+ return &UsdPreviewSurface;
+ }
+ if (usdNodeType == CyclesMaterialTokens->UsdUVTexture) {
+ return &UsdUVTexture;
+ }
+ if (usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float2 ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float3 ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float4 ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_int) {
+ return &UsdPrimvarReader;
+ }
+
+ return nullptr;
+ }
+ const UsdToCyclesMapping *findCycles(const ustring &cyclesNodeType)
+ {
+ return nullptr;
+ }
+};
+TfStaticData<UsdToCycles> sUsdToCyles;
+
+} // namespace
+
+struct HdCyclesMaterial::NodeDesc {
+ ShaderNode *node;
+ const UsdToCyclesMapping *mapping;
+};
+
+HdCyclesMaterial::HdCyclesMaterial(const SdfPath &sprimId) : HdMaterial(sprimId)
+{
+}
+
+HdCyclesMaterial::~HdCyclesMaterial()
+{
+}
+
+HdDirtyBits HdCyclesMaterial::GetInitialDirtyBitsMask() const
+{
+ return DirtyBits::DirtyResource | DirtyBits::DirtyParams;
+}
+
+void HdCyclesMaterial::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits)
+{
+ if (*dirtyBits == DirtyBits::Clean) {
+ return;
+ }
+
+ Initialize(renderParam);
+
+ const SceneLock lock(renderParam);
+
+ const bool dirtyParams = (*dirtyBits & DirtyBits::DirtyParams);
+ const bool dirtyResource = (*dirtyBits & DirtyBits::DirtyResource);
+
+ VtValue value;
+ const SdfPath &id = GetId();
+
+ if (dirtyResource || dirtyParams) {
+ value = sceneDelegate->GetMaterialResource(id);
+
+#if 1
+ const HdMaterialNetwork2 *network = nullptr;
+ std::unique_ptr<HdMaterialNetwork2> networkConverted;
+ if (value.IsHolding<HdMaterialNetwork2>()) {
+ network = &value.UncheckedGet<HdMaterialNetwork2>();
+ }
+ else if (value.IsHolding<HdMaterialNetworkMap>()) {
+ const auto &networkOld = value.UncheckedGet<HdMaterialNetworkMap>();
+ // In the case of only parameter updates, there is no need to waste time converting to a
+ // HdMaterialNetwork2, as supporting HdMaterialNetworkMap for parameters only is trivial.
+ if (!_nodes.empty() && !dirtyResource) {
+ for (const auto &networkEntry : networkOld.map) {
+ UpdateParameters(networkEntry.second);
+ }
+ _shader->tag_modified();
+ }
+ else {
+ networkConverted = std::make_unique<HdMaterialNetwork2>();
+ HdMaterialNetwork2ConvertFromHdMaterialNetworkMap(networkOld, networkConverted.get());
+ network = networkConverted.get();
+ }
+ }
+ else {
+ TF_RUNTIME_ERROR("Could not get a HdMaterialNetwork2.");
+ }
+
+ if (network) {
+ if (!_nodes.empty() && !dirtyResource) {
+ UpdateParameters(*network);
+ _shader->tag_modified();
+ }
+ else {
+ PopulateShaderGraph(*network);
+ }
+ }
+#endif
+ }
+
+ if (_shader->is_modified()) {
+ _shader->tag_update(lock.scene);
+ }
+
+ *dirtyBits = DirtyBits::Clean;
+}
+
+void HdCyclesMaterial::UpdateParameters(NodeDesc &nodeDesc,
+ const std::map<TfToken, VtValue> &parameters,
+ const SdfPath &nodePath)
+{
+ for (const std::pair<TfToken, VtValue> &param : parameters) {
+ VtValue value = param.second;
+
+ // See if the parameter name is in USDPreviewSurface terms, and needs to be converted
+ const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
+ const std::string inputName = inputMapping ?
+ inputMapping->parameterName(param.first, nullptr, &value) :
+ param.first.GetString();
+
+ // Find the input to write the parameter value to
+ const SocketType *input = nullptr;
+ for (const SocketType &socket : nodeDesc.node->type->inputs) {
+ if (string_iequals(socket.name.string(), inputName) || socket.ui_name == inputName) {
+ input = &socket;
+ break;
+ }
+ }
+
+ if (!input) {
+ TF_WARN("Could not find parameter '%s' on node '%s' ('%s')",
+ param.first.GetText(),
+ nodePath.GetText(),
+ nodeDesc.node->name.c_str());
+ continue;
+ }
+
+ SetNodeValue(nodeDesc.node, *input, value);
+ }
+}
+
+void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork &network)
+{
+ for (const HdMaterialNode &nodeEntry : network.nodes) {
+ const SdfPath &nodePath = nodeEntry.path;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
+ continue;
+ }
+
+ UpdateParameters(nodeIt->second, nodeEntry.parameters, nodePath);
+ }
+}
+
+void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork2 &network)
+{
+ for (const std::pair<SdfPath, HdMaterialNode2> &nodeEntry : network.nodes) {
+ const SdfPath &nodePath = nodeEntry.first;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
+ continue;
+ }
+
+ UpdateParameters(nodeIt->second, nodeEntry.second.parameters, nodePath);
+ }
+}
+
+void HdCyclesMaterial::UpdateConnections(NodeDesc &nodeDesc,
+ const HdMaterialNode2 &matNode,
+ const SdfPath &nodePath,
+ ShaderGraph *shaderGraph)
+{
+ for (const std::pair<TfToken, std::vector<HdMaterialConnection2>> &connection :
+ matNode.inputConnections) {
+ const TfToken &dstSocketName = connection.first;
+
+ const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
+ const std::string inputName = inputMapping ?
+ inputMapping->parameterName(dstSocketName, nullptr) :
+ dstSocketName.GetString();
+
+ // Find the input to connect to on the passed in node
+ ShaderInput *input = nullptr;
+ for (ShaderInput *in : nodeDesc.node->inputs) {
+ if (string_iequals(in->socket_type.name.string(), inputName)) {
+ input = in;
+ break;
+ }
+ }
+
+ if (!input) {
+ TF_WARN("Ignoring connection on '%s.%s', input '%s' was not found",
+ nodePath.GetText(),
+ dstSocketName.GetText(),
+ dstSocketName.GetText());
+ continue;
+ }
+
+ // Now find the output to connect from
+ const auto &connectedNodes = connection.second;
+ if (connectedNodes.empty()) {
+ continue;
+ }
+
+ // TODO: Hydra allows multiple connections of the same input
+ // Unsure how to handle this in Cycles, so just use the first
+ if (connectedNodes.size() > 1) {
+ TF_WARN(
+ "Ignoring multiple connections to '%s.%s'", nodePath.GetText(), dstSocketName.GetText());
+ }
+
+ const SdfPath &upstreamNodePath = connectedNodes.front().upstreamNode;
+ const TfToken &upstreamOutputName = connectedNodes.front().upstreamOutputName;
+
+ const auto srcNodeIt = _nodes.find(upstreamNodePath);
+ if (srcNodeIt == _nodes.end()) {
+ TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', node '%s' was not found",
+ upstreamNodePath.GetText(),
+ upstreamOutputName.GetText(),
+ nodePath.GetText(),
+ dstSocketName.GetText(),
+ upstreamNodePath.GetText());
+ continue;
+ }
+
+ const UsdToCyclesMapping *outputMapping = srcNodeIt->second.mapping;
+ const std::string outputName = outputMapping ?
+ outputMapping->parameterName(upstreamOutputName, input) :
+ upstreamOutputName.GetString();
+
+ ShaderOutput *output = nullptr;
+ for (ShaderOutput *out : srcNodeIt->second.node->outputs) {
+ if (string_iequals(out->socket_type.name.string(), outputName)) {
+ output = out;
+ break;
+ }
+ }
+
+ if (!output) {
+ TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', output '%s' was not found",
+ upstreamNodePath.GetText(),
+ upstreamOutputName.GetText(),
+ nodePath.GetText(),
+ dstSocketName.GetText(),
+ upstreamOutputName.GetText());
+ continue;
+ }
+
+ shaderGraph->connect(output, input);
+ }
+}
+
+void HdCyclesMaterial::PopulateShaderGraph(const HdMaterialNetwork2 &networkMap)
+{
+ _nodes.clear();
+
+ auto graph = new ShaderGraph();
+
+ // Iterate all the nodes first and build a complete but unconnected graph with parameters set
+ for (const std::pair<SdfPath, HdMaterialNode2> &nodeEntry : networkMap.nodes) {
+ NodeDesc nodeDesc = {};
+ const SdfPath &nodePath = nodeEntry.first;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ // Create new node only if it does not exist yet
+ if (nodeIt != _nodes.end()) {
+ nodeDesc = nodeIt->second;
+ }
+ else {
+ // E.g. cycles_principled_bsdf or UsdPreviewSurface
+ const std::string &nodeTypeId = nodeEntry.second.nodeTypeId.GetString();
+
+ ustring cyclesType(nodeTypeId);
+ // Interpret a node type ID prefixed with cycles_<type> or cycles:<type> as a node of <type>
+ if (nodeTypeId.rfind("cycles", 0) == 0) {
+ cyclesType = nodeTypeId.substr(7);
+ nodeDesc.mapping = sUsdToCyles->findCycles(cyclesType);
+ }
+ else {
+ // Check if any remapping is needed (e.g. for USDPreviewSurface to Cycles nodes)
+ nodeDesc.mapping = sUsdToCyles->findUsd(nodeEntry.second.nodeTypeId);
+ if (nodeDesc.mapping) {
+ cyclesType = nodeDesc.mapping->nodeType();
+ }
+ }
+
+ // If it's a native Cycles' node-type, just do the lookup now.
+ if (const NodeType *nodeType = NodeType::find(cyclesType)) {
+ nodeDesc.node = static_cast<ShaderNode *>(nodeType->create(nodeType));
+ nodeDesc.node->set_owner(graph);
+
+ graph->add(nodeDesc.node);
+
+ _nodes.emplace(nodePath, nodeDesc);
+ }
+ else {
+ TF_RUNTIME_ERROR("Could not create node '%s'", nodePath.GetText());
+ continue;
+ }
+ }
+
+ UpdateParameters(nodeDesc, nodeEntry.second.parameters, nodePath);
+ }
+
+ // Now that all nodes have been constructed, iterate the network again and build up any
+ // connections between nodes
+ for (const std::pair<SdfPath, HdMaterialNode2> &nodeEntry : networkMap.nodes) {
+ const SdfPath &nodePath = nodeEntry.first;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not find node '%s' to connect", nodePath.GetText());
+ continue;
+ }
+
+ UpdateConnections(nodeIt->second, nodeEntry.second, nodePath, graph);
+ }
+
+ // Finally connect the terminals to the graph output (Surface, Volume, Displacement)
+ for (const std::pair<TfToken, HdMaterialConnection2> &terminalEntry : networkMap.terminals) {
+ const TfToken &terminalName = terminalEntry.first;
+ const HdMaterialConnection2 &connection = terminalEntry.second;
+
+ const auto nodeIt = _nodes.find(connection.upstreamNode);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not find terminal node '%s'", connection.upstreamNode.GetText());
+ continue;
+ }
+
+ ShaderNode *const node = nodeIt->second.node;
+
+ const char *inputName = nullptr;
+ const char *outputName = nullptr;
+ if (terminalName == HdMaterialTerminalTokens->surface ||
+ terminalName == CyclesMaterialTokens->cyclesSurface) {
+ inputName = "Surface";
+ // Find default output name based on the node if none is provided
+ if (node->type->name == "add_closure" || node->type->name == "mix_closure") {
+ outputName = "Closure";
+ }
+ else if (node->type->name == "emission") {
+ outputName = "Emission";
+ }
+ else {
+ outputName = "BSDF";
+ }
+ }
+ else if (terminalName == HdMaterialTerminalTokens->displacement ||
+ terminalName == CyclesMaterialTokens->cyclesDisplacement) {
+ inputName = outputName = "Displacement";
+ }
+ else if (terminalName == HdMaterialTerminalTokens->volume ||
+ terminalName == CyclesMaterialTokens->cyclesVolume) {
+ inputName = outputName = "Volume";
+ }
+
+ if (!connection.upstreamOutputName.IsEmpty()) {
+ outputName = connection.upstreamOutputName.GetText();
+ }
+
+ ShaderInput *const input = inputName ? graph->output()->input(inputName) : nullptr;
+ if (!input) {
+ TF_RUNTIME_ERROR("Could not find terminal input '%s.%s'",
+ connection.upstreamNode.GetText(),
+ inputName ? inputName : "<null>");
+ continue;
+ }
+
+ ShaderOutput *const output = outputName ? node->output(outputName) : nullptr;
+ if (!output) {
+ TF_RUNTIME_ERROR("Could not find terminal output '%s.%s'",
+ connection.upstreamNode.GetText(),
+ outputName ? outputName : "<null>");
+ continue;
+ }
+
+ graph->connect(output, input);
+ }
+
+ // Create the instanceId AOV output
+ {
+ const ustring instanceId(HdAovTokens->instanceId.GetString());
+
+ OutputAOVNode *aovNode = graph->create_node<OutputAOVNode>();
+ aovNode->set_name(instanceId);
+ graph->add(aovNode);
+
+ AttributeNode *instanceIdNode = graph->create_node<AttributeNode>();
+ instanceIdNode->set_attribute(instanceId);
+ graph->add(instanceIdNode);
+
+ graph->connect(instanceIdNode->output("Fac"), aovNode->input("Value"));
+ }
+
+ _shader->set_graph(graph);
+}
+
+void HdCyclesMaterial::Finalize(HdRenderParam *renderParam)
+{
+ if (!_shader) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ _nodes.clear();
+
+ lock.scene->delete_node(_shader);
+ _shader = nullptr;
+}
+
+void HdCyclesMaterial::Initialize(HdRenderParam *renderParam)
+{
+ if (_shader) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ _shader = lock.scene->create_node<Shader>();
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE