Files
workinf_Blender_Wasm/blender-5.2.0/intern/cycles/hydra/material.cpp
2026-08-12 04:47:48 -04:00

603 lines
20 KiB
C++

/* SPDX-FileCopyrightText: 2022 NVIDIA Corporation
* SPDX-FileCopyrightText: 2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "hydra/material.h"
#include "hydra/node_util.h"
#include "hydra/session.h"
#include "hydra/util.h"
#include "scene/scene.h"
#include "scene/shader.h"
#include "scene/shader_graph.h"
#include "scene/shader_nodes.h"
#include <pxr/imaging/hd/material.h>
#include <pxr/imaging/hd/materialConnectionSchema.h>
#include <pxr/imaging/hd/materialNetworkSchema.h>
#include <pxr/imaging/hd/materialNodeParameterSchema.h>
#include <pxr/imaging/hd/materialNodeSchema.h>
#include <pxr/imaging/hd/materialSchema.h>
#include <pxr/imaging/hd/sceneDelegate.h>
HDCYCLES_NAMESPACE_OPEN_SCOPE
/* Normalize a material network node name to a full SdfPath. The schema may
* provide either a full path or a bare identifier. */
static SdfPath MaterialNodeNameToSdfPath(const TfToken &nodeName)
{
const std::string &s = nodeName.GetString();
if (s.empty()) {
return SdfPath::EmptyPath();
}
if (s[0] == '/' && SdfPath::IsValidPathString(s)) {
return SdfPath(s);
}
return SdfPath::AbsoluteRootPath().AppendChild(nodeName);
}
// clang-format off
TF_DEFINE_PRIVATE_TOKENS(CyclesMaterialTokens,
(cycles)
((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
/* 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) {
const 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);
}
};
namespace {
class UsdToCycles {
const UsdToCyclesMapping UsdPreviewSurface = {
"principled_bsdf",
{
{TfToken("diffuseColor"), ustring("base_color")},
{TfToken("emissiveColor"), ustring("emission")},
{TfToken("specularColor"), ustring("specular")},
{TfToken("clearcoatRoughness"), ustring("coat_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
HdCyclesMaterial::HdCyclesMaterial(const SdfPath &sprimId) : HdMaterial(sprimId) {}
HdCyclesMaterial::~HdCyclesMaterial() = default;
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);
const SdfPath &id = GetId();
if (dirtyResource || dirtyParams) {
const HdSceneIndexPrim prim = GetPrim(sceneDelegate, id);
const HdContainerDataSourceHandle &primDs = prim.dataSource;
HdMaterialSchema matSchema = HdMaterialSchema::GetFromParent(primDs);
/* Prefer cycles network if it exists, otherwise use universal network. */
HdMaterialNetworkSchema network = matSchema.GetMaterialNetwork(CyclesMaterialTokens->cycles);
if (!network) {
network = matSchema.GetMaterialNetwork();
}
if (network) {
if (!_nodes.empty() && !dirtyResource) {
UpdateParameters(network);
_shader->tag_modified();
}
else {
PopulateShaderGraph(network);
}
}
else {
TF_RUNTIME_ERROR("Could not get a material network for %s.", id.GetText());
}
}
if (_shader->is_modified()) {
_shader->tag_update(lock.scene);
}
*dirtyBits = DirtyBits::Clean;
}
void HdCyclesMaterial::UpdateParameters(NodeDesc &nodeDesc,
HdMaterialNodeParameterContainerSchema params,
const SdfPath &nodePath)
{
for (const TfToken &paramName : params.GetNames()) {
auto valueDs = params.Get(paramName).GetValue();
if (!valueDs) {
continue;
}
VtValue value = valueDs->GetValue(0.0f);
/* 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(paramName, nullptr, &value) :
paramName.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')",
paramName.GetText(),
nodePath.GetText(),
nodeDesc.node->name.c_str());
continue;
}
SetNodeValue(nodeDesc.node, *input, value);
}
}
void HdCyclesMaterial::UpdateParameters(HdMaterialNetworkSchema network)
{
HdMaterialNodeContainerSchema nodes = network.GetNodes();
for (const TfToken &nodeName : nodes.GetNames()) {
const SdfPath nodePath = MaterialNodeNameToSdfPath(nodeName);
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, nodes.Get(nodeName).GetParameters(), nodePath);
}
}
void HdCyclesMaterial::UpdateConnections(NodeDesc &nodeDesc,
HdMaterialNodeSchema nodeSchema,
const SdfPath &nodePath,
ShaderGraph *shaderGraph)
{
HdMaterialConnectionVectorContainerSchema conns = nodeSchema.GetInputConnections();
for (const TfToken &dstSocketName : conns.GetNames()) {
HdMaterialConnectionVectorSchema connVec = conns.Get(dstSocketName);
const size_t count = connVec.GetNumElements();
if (count == 0) {
continue;
}
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;
}
/* USD allows N connections per input (MaterialX <switch>, <combine>, struct
* inputs etc). Cycles inputs are single-connection, and the right lowering
* depends on the node type, so just take the first and warn. */
if (count > 1) {
TF_WARN(
"Ignoring multiple connections to '%s.%s'", nodePath.GetText(), dstSocketName.GetText());
}
HdMaterialConnectionSchema connSchema = connVec.GetElement(0);
const SdfPath upstreamNodePath =
connSchema.GetUpstreamNodePath() ?
MaterialNodeNameToSdfPath(connSchema.GetUpstreamNodePath()->GetTypedValue(0.0f)) :
SdfPath();
const TfToken upstreamOutputName = connSchema.GetUpstreamNodeOutputName() ?
connSchema.GetUpstreamNodeOutputName()->GetTypedValue(
0.0f) :
TfToken();
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(HdMaterialNetworkSchema network)
{
_nodes.clear();
unique_ptr<ShaderGraph> graph = make_unique<ShaderGraph>();
HdMaterialNodeContainerSchema nodes = network.GetNodes();
/* Iterate all the nodes first and build a complete but unconnected graph with parameters set. */
for (const TfToken &nodeName : nodes.GetNames()) {
HdMaterialNodeSchema nodeSchema = nodes.Get(nodeName);
const SdfPath nodePath = MaterialNodeNameToSdfPath(nodeName);
NodeDesc nodeDesc = {};
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 TfToken nodeTypeIdToken = nodeSchema.GetNodeIdentifier() ?
nodeSchema.GetNodeIdentifier()->GetTypedValue(0.0f) :
TfToken();
const std::string &nodeTypeId = nodeTypeIdToken.GetString();
ustring cyclesType(nodeTypeId);
if (nodeTypeId.starts_with("cycles_") || nodeTypeId.starts_with("cycles:")) {
/* Native Cycles note embedded in USDShade. */
cyclesType = nodeTypeId.substr(strlen("cycles_"));
nodeDesc.mapping = sUsdToCyles->findCycles(cyclesType);
}
else {
/* Check if any remapping is needed (e.g. for USDPreviewSurface to Cycles nodes). */
nodeDesc.mapping = sUsdToCyles->findUsd(nodeTypeIdToken);
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 = graph->create_node(nodeType);
_nodes.emplace(nodePath, nodeDesc);
}
else {
TF_RUNTIME_ERROR("Could not create node '%s'", nodePath.GetText());
continue;
}
}
UpdateParameters(nodeDesc, nodeSchema.GetParameters(), nodePath);
}
/* Now that all nodes have been constructed, iterate the network again and build up any
* connections between nodes. */
for (const TfToken &nodeName : nodes.GetNames()) {
const SdfPath nodePath = MaterialNodeNameToSdfPath(nodeName);
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, nodes.Get(nodeName), nodePath, graph.get());
}
/* Finally connect the terminals to the graph output (Surface, Volume, Displacement). */
HdMaterialConnectionContainerSchema terminals = network.GetTerminals();
for (const TfToken &terminalName : terminals.GetNames()) {
HdMaterialConnectionSchema termSchema = terminals.Get(terminalName);
const SdfPath upstreamNodePath =
termSchema.GetUpstreamNodePath() ?
MaterialNodeNameToSdfPath(termSchema.GetUpstreamNodePath()->GetTypedValue(0.0f)) :
SdfPath();
const TfToken upstreamOutputName = termSchema.GetUpstreamNodeOutputName() ?
termSchema.GetUpstreamNodeOutputName()->GetTypedValue(
0.0f) :
TfToken();
const auto nodeIt = _nodes.find(upstreamNodePath);
if (nodeIt == _nodes.end()) {
TF_RUNTIME_ERROR("Could not find terminal node '%s'", upstreamNodePath.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";
}
/* For native Cycles nodes we use the upstream output name as is, for
* mapping from e.g. UsdPreviewSurface we need to use the default output
* name that is known to exist. */
if (!upstreamOutputName.IsEmpty() && nodeIt->second.mapping == nullptr) {
outputName = upstreamOutputName.GetText();
}
ShaderInput *const input = inputName ? graph->output()->input(inputName) : nullptr;
if (!input) {
TF_RUNTIME_ERROR("Could not find terminal input '%s.%s'",
upstreamNodePath.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'",
upstreamNodePath.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);
AttributeNode *instanceIdNode = graph->create_node<AttributeNode>();
instanceIdNode->set_attribute(instanceId);
graph->connect(instanceIdNode->output("Fac"), aovNode->input("Value"));
}
_shader->set_graph(std::move(graph));
}
void HdCyclesMaterial::Finalize(HdRenderParam *renderParam)
{
if (!_shader) {
return;
}
const SceneLock lock(renderParam);
const bool keep_nodes = static_cast<const HdCyclesSession *>(renderParam)->keep_nodes;
_nodes.clear();
if (!keep_nodes) {
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