603 lines
20 KiB
C++
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 ¶mName : 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
|