Support group nodes #22

Merged
Bogdan Nagirniak merged 18 commits from BogdanNagirniak/blender:matx-group-nodes into matx-export-material 2023-09-18 12:49:19 +02:00
13 changed files with 345 additions and 50 deletions

View File

@ -81,6 +81,16 @@ void MaterialData::init()
MaterialX::DocumentPtr doc = blender::nodes::materialx::export_to_materialx(
scene_delegate_->depsgraph, (Material *)id);
pxr::UsdMtlxRead(doc, stage);
/* Logging stage: creating lambda stage_str() for not to call stage->ExportToString()
* if log won't be printed. */
auto stage_str = [&stage]() {
std::string str;
stage->ExportToString(&str);
return str;
};
ID_LOGN(2, "Stage:\n%s", stage_str().c_str());
if (pxr::UsdPrim materials = stage->GetPrimAtPath(pxr::SdfPath("/MaterialX/Materials"))) {
pxr::UsdPrimSiblingRange children = materials.GetChildren();
if (!children.empty()) {

View File

@ -150,10 +150,12 @@ if(WITH_MATERIALX)
materialx/material.cc
materialx/node_item.cc
materialx/node_parser.cc
materialx/group_nodes.cc
materialx/material.h
materialx/node_item.h
materialx/node_parser.h
materialx/group_nodes.h
)
list(APPEND LIB
MaterialXCore

View File

@ -0,0 +1,151 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "group_nodes.h"
#include "node_parser.h"
#include "BLI_vector.hh"
namespace blender::nodes::materialx {
NodeItem GroupNodeParser::compute()
{
NodeItem res = empty();
const bNodeTree *ngroup = reinterpret_cast<const bNodeTree *>(node_->id);
ngroup->ensure_topology_cache();
const bNode *node_out = ngroup->group_output_node();
if (!node_out) {
return res;
}
MaterialX::GraphElement *graph = graph_;
#ifdef USE_MATERIALX_NODEGRAPH
std::string name = MaterialX::createValidName(ngroup->id.name);
MaterialX::NodeGraphPtr group_graph = graph_->getChildOfType<MaterialX::NodeGraph>(name);
if (!group_graph) {
CLOG_INFO(LOG_MATERIALX_SHADER, 1, "<nodegraph name=%s>", name.c_str());
group_graph = graph_->addChild<MaterialX::NodeGraph>(name);
}
graph = group_graph.get();
#endif
NodeItem out =
GroupOutputNodeParser(
graph, depsgraph_, material_, node_out, socket_out_, NodeItem::Type::Any, this)
.compute_full();
#ifdef USE_MATERIALX_NODEGRAPH
/* We have to be in NodeParser's graph_, therefore copying output */
res.output = out.output;
#else
res = out;
#endif
return res;
}
NodeItem GroupNodeParser::compute_full()
{
NodeItem res = compute();
if (NodeItem::is_arithmetic(to_type_)) {
res = res.convert(to_type_);
}
return res;
}
NodeItem GroupOutputNodeParser::compute()
{
#ifdef USE_MATERIALX_NODEGRAPH
Vector<NodeItem> values;
for (auto socket_in : node_->input_sockets()) {
NodeItem value = get_input_value(socket_in->index(), NodeItem::Type::Any);
if (value.value) {
NodeItem constant = create_node("constant", value.type());
constant.set_input("value", value);
value = constant;
}
values.append(value);
}
Vector<NodeItem> outputs;
for (int i = 0; i < values.size(); ++i) {
if (values[i]) {
outputs.append(create_output("output" + std::to_string(i + 1), values[i]));
}
}
return outputs[socket_out_->index()];
#else
return get_input_value(socket_out_->index(), NodeItem::Type::Any);
#endif
}
NodeItem GroupOutputNodeParser::compute_full()
{
#ifdef USE_MATERIALX_NODEGRAPH
NodeItem res = empty();
/* Checking if output was already computed */
res.output = graph_->getOutput("output" + std::to_string(socket_out_->index() + 1));
if (res.output) {
return res;
}
CLOG_INFO(LOG_MATERIALX_SHADER,
1,
"%s [%d] => %s",
node_->name,
node_->typeinfo->type,
NodeItem::type(to_type_).c_str());
res = compute();
return res;
#else
return NodeParser::compute_full();
#endif
}
NodeItem GroupInputNodeParser::compute()
{
#ifdef USE_MATERIALX_NODEGRAPH
NodeItem value = group_parser_->get_input_link(socket_out_->index(), to_type_);
if (!value) {
return empty();
}
if (value.value) {
NodeItem constant = create_node("constant", value.type());
constant.set_input("value", value);
value = constant;
}
return create_input("input" + std::to_string(socket_out_->index() + 1), value);
#else
return group_parser_->get_input_link(socket_out_->index(), to_type_);
#endif
}
NodeItem GroupInputNodeParser::compute_full()
{
#ifdef USE_MATERIALX_NODEGRAPH
NodeItem res = empty();
/* Checking if output was already computed */
res.input = graph_->getInput("input" + std::to_string(socket_out_->index() + 1));
if (res.input) {
return res;
}
CLOG_INFO(LOG_MATERIALX_SHADER,
1,
"%s [%d] => %s",
node_->name,
node_->typeinfo->type,
NodeItem::type(to_type_).c_str());
res = compute();
return res;
#else
return NodeParser::compute_full();
#endif
}
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node_parser.h"
/* TODO: pxr::UsdMtlxRead() doesn't perform nodegraphs.
* Uncomment USE_MATERIALX_NODEGRAPH after fixing it. */
//#define USE_MATERIALX_NODEGRAPH
namespace blender::nodes::materialx {
class GroupInputNodeParser;
class GroupNodeParser : public NodeParser {
friend NodeParser;
friend GroupInputNodeParser;
public:
using NodeParser::NodeParser;
NodeItem compute() override;
NodeItem compute_full() override;
};
class GroupOutputNodeParser : public NodeParser {
public:
using NodeParser::NodeParser;
NodeItem compute() override;
NodeItem compute_full() override;
};
class GroupInputNodeParser : public NodeParser {
public:
using NodeParser::NodeParser;
NodeItem compute() override;
NodeItem compute_full() override;
};
} // namespace blender::nodes::materialx

View File

@ -62,18 +62,18 @@ MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, Material *mater
bNode *output_node = ntreeShaderOutputNode(material->nodetree, SHD_OUTPUT_ALL);
if (output_node) {
NodeParserData data = {
doc.get(), depsgraph, material, NodeItem::Type::Material, NodeItem(doc.get())};
doc.get(), depsgraph, material, NodeItem::Type::Material, nullptr, NodeItem(doc.get())};
output_node->typeinfo->materialx_fn(&data, output_node, nullptr);
}
else {
DefaultMaterialNodeParser(
doc.get(), depsgraph, material, nullptr, nullptr, NodeItem::Type::Material)
doc.get(), depsgraph, material, nullptr, nullptr, NodeItem::Type::Material, nullptr)
.compute_error();
}
}
else {
DefaultMaterialNodeParser(
doc.get(), depsgraph, material, nullptr, nullptr, NodeItem::Type::Material)
doc.get(), depsgraph, material, nullptr, nullptr, NodeItem::Type::Material, nullptr)
.compute();
}

View File

@ -66,7 +66,7 @@ std::string NodeItem::type(Type type)
{
switch (type) {
case Type::Any:
return "";
return "any";
case Type::Multioutput:
return "multioutput";
case Type::String:
@ -103,9 +103,14 @@ std::string NodeItem::type(Type type)
return "";
}
bool NodeItem::is_arithmetic(Type type)
{
return type >= Type::Float && type <= Type::Color4;
}
NodeItem::operator bool() const
{
return value || node;
return value || node || input || output;
}
NodeItem NodeItem::operator+(const NodeItem &other) const
@ -604,8 +609,8 @@ NodeItem NodeItem::if_else(CompareOp op,
NodeItem NodeItem::extract(const int index) const
{
NodeItem res = empty();
res = create_node("extract", Type::Float);
/* TODO: Add check if (value) { ... } */
NodeItem res = create_node("extract", Type::Float);
res.set_input("in", *this);
res.set_input("index", val(index));
return res;
@ -624,6 +629,9 @@ NodeItem::Type NodeItem::type() const
if (node) {
return type(node->getType());
}
if (output) {
return type(output->getType());
}
return Type::Empty;
}
@ -675,30 +683,59 @@ void NodeItem::set_input(const std::string &in_name, const NodeItem &item)
else if (item.node) {
node->setConnectedNode(in_name, item.node);
}
else if (item.input) {
node->setAttribute("interfacename", item.input->getName());
}
else if (item.output) {
node->setConnectedOutput(in_name, item.output);
}
else {
CLOG_WARN(LOG_MATERIALX_SHADER, "Empty item to input: %s", in_name.c_str());
}
}
void NodeItem::set_input_output(const std::string &in_name,
const NodeItem &item,
const std::string &out_name)
NodeItem NodeItem::add_output(const std::string &out_name, Type out_type)
{
if (!item.node) {
NodeItem res = empty();
res.output = node->addOutput(out_name, type(out_type));
return res;
}
NodeItem NodeItem::create_input(const std::string &name, const NodeItem &item) const
{
NodeItem res = empty();
res.input = graph_->addInput(name);
Type item_type = item.type();
if (item.node) {
res.input->setConnectedNode(item.node);
}
else {
BLI_assert_unreachable();
}
node->setConnectedNode(in_name, item.node);
node->setConnectedOutput(in_name, item.node->getOutput(out_name));
res.input->setType(type(item_type));
return res;
}
void NodeItem::add_output(const std::string &name, Type out_type)
NodeItem NodeItem::create_output(const std::string &name, const NodeItem &item) const
{
node->addOutput(name, type(out_type));
}
NodeItem res = empty();
res.output = graph_->addOutput(name);
bool NodeItem::is_arithmetic(Type type)
{
return type >= Type::Float && type <= Type::Color4;
Type item_type = item.type();
if (item.node) {
res.output->setConnectedNode(item.node);
}
else if (item.input) {
res.output->setInterfaceName(item.input->getName());
}
else {
BLI_assert_unreachable();
}
res.output->setType(type(item_type));
return res;
}
NodeItem::Type NodeItem::cast_types(NodeItem &item1, NodeItem &item2)
@ -776,10 +813,16 @@ NodeItem NodeItem::arithmetic(const std::string &category, std::function<float(f
}
}
else {
/* TODO: Some of math functions (sin, cos, ...) doesn't work with Color types,
* we have to convert to Vector */
NodeItem v = *this;
if (ELEM(type, Type::Color3, Type::Color4) &&
ELEM(category, "sin", "cos", "tan", "asin", "acos", "atan2", "sqrt", "ln", "exp"))
{
/* These functions haven't implementation in MaterialX, converting to Vector types */
type = type == Type::Color3 ? Type::Vector3 : Type::Vector4;
v = v.convert(type);
}
res = create_node(category, type);
res.set_input("in", *this);
res.set_input("in", v);
}
return res;
}

View File

@ -43,6 +43,8 @@ class NodeItem {
public:
MaterialX::ValuePtr value;
MaterialX::NodePtr node;
MaterialX::InputPtr input;
MaterialX::OutputPtr output;
private:
MaterialX::GraphElement *graph_;
@ -102,15 +104,16 @@ class NodeItem {
NodeItem empty() const;
template<class T> NodeItem val(const T &data) const;
Type type() const;
NodeItem create_node(const std::string &category, NodeItem::Type type) const;
/* Functions to set input and output */
/* Node functions */
NodeItem create_node(const std::string &category, NodeItem::Type type) const;
template<class T> void set_input(const std::string &in_name, const T &value, Type in_type);
void set_input(const std::string &in_name, const NodeItem &item);
void set_input_output(const std::string &in_name,
const NodeItem &item,
const std::string &out_name);
void add_output(const std::string &in_name, Type out_type);
NodeItem add_output(const std::string &out_name, Type out_type);
/* Output functions */
NodeItem create_input(const std::string &name, const NodeItem &item) const;
NodeItem create_output(const std::string &name, const NodeItem &item) const;
private:
static Type cast_types(NodeItem &item1, NodeItem &item2);

View File

@ -4,6 +4,8 @@
#include "node_parser.h"
#include "group_nodes.h"
#include "BKE_node_runtime.hh"
namespace blender::nodes::materialx {
@ -17,13 +19,15 @@ NodeParser::NodeParser(MaterialX::GraphElement *graph,
const Material *material,
const bNode *node,
const bNodeSocket *socket_out,
NodeItem::Type to_type)
NodeItem::Type to_type,
GroupNodeParser *group_parser)
: graph_(graph),
depsgraph_(depsgraph),
material_(material),
node_(node),
socket_out_(socket_out),
to_type_(to_type)
to_type_(to_type),
group_parser_(group_parser)
{
}
@ -52,7 +56,7 @@ NodeItem NodeParser::compute_full()
return res;
}
std::string NodeParser::node_name()
std::string NodeParser::node_name() const
{
std::string name = node_->name;
if (node_->output_sockets().size() > 1) {
@ -61,7 +65,18 @@ std::string NodeParser::node_name()
if (ELEM(to_type_, NodeItem::Type::BSDF, NodeItem::Type::EDF)) {
name += "_" + NodeItem::type(to_type_);
}
#ifdef USE_MATERIALX_NODEGRAPH
return MaterialX::createValidName(name);
#else
std::string prefix;
GroupNodeParser *gr = group_parser_;
while (gr) {
const bNodeTree *ngroup = reinterpret_cast<const bNodeTree *>(gr->node_->id);
prefix = MaterialX::createValidName(ngroup->id.name) + "_" + prefix;
gr = gr->group_parser_;
}
return prefix + MaterialX::createValidName(name);
#endif
}
NodeItem NodeParser::create_node(const std::string &category, NodeItem::Type type)
@ -69,6 +84,16 @@ NodeItem NodeParser::create_node(const std::string &category, NodeItem::Type typ
return empty().create_node(category, type);
}
NodeItem NodeParser::create_input(const std::string &name, const NodeItem &item)
{
return empty().create_input(name, item);
}
NodeItem NodeParser::create_output(const std::string &name, const NodeItem &item)
{
return empty().create_output(name, item);
}
NodeItem NodeParser::get_input_default(const std::string &name, NodeItem::Type to_type)
{
return get_default(node_->input_by_identifier(name), to_type);
@ -129,6 +154,9 @@ NodeItem NodeParser::get_default(const bNodeSocket &socket, NodeItem::Type to_ty
{
NodeItem res = empty();
switch (socket.type) {
case SOCK_CUSTOM:
/* Return empty */
break;
case SOCK_FLOAT: {
float v = socket.default_value_typed<bNodeSocketValueFloat>()->value;
res.value = MaterialX::Value::createValue<float>(v);
@ -163,7 +191,7 @@ NodeItem NodeParser::get_input_link(const bNodeSocket &socket, NodeItem::Type to
const bNode *from_node = link->fromnode;
/* Passing NODE_REROUTE nodes */
while (from_node->type == NODE_REROUTE) {
while (from_node->is_reroute()) {
link = from_node->input_socket(0).link;
if (!(link && link->is_used())) {
return empty();
@ -171,6 +199,17 @@ NodeItem NodeParser::get_input_link(const bNodeSocket &socket, NodeItem::Type to
from_node = link->fromnode;
}
if (from_node->is_group()) {
return GroupNodeParser(
graph_, depsgraph_, material_, from_node, link->fromsock, to_type, group_parser_)
.compute_full();
}
if (from_node->is_group_input()) {
return GroupInputNodeParser(
graph_, depsgraph_, material_, from_node, link->fromsock, to_type, group_parser_)
.compute_full();
}
if (!from_node->typeinfo->materialx_fn) {
CLOG_WARN(LOG_MATERIALX_SHADER,
"Unsupported node: %s [%d]",
@ -179,7 +218,7 @@ NodeItem NodeParser::get_input_link(const bNodeSocket &socket, NodeItem::Type to
return empty();
}
NodeParserData data = {graph_, depsgraph_, material_, to_type, empty()};
NodeParserData data = {graph_, depsgraph_, material_, to_type, group_parser_, empty()};
from_node->typeinfo->materialx_fn(&data, const_cast<bNode *>(from_node), link->fromsock);
return data.result;
}

View File

@ -16,6 +16,8 @@ namespace blender::nodes::materialx {
extern struct CLG_LogRef *LOG_MATERIALX_SHADER;
class GroupNodeParser;
class NodeParser {
protected:
MaterialX::GraphElement *graph_;
@ -24,6 +26,7 @@ class NodeParser {
const bNode *node_;
const bNodeSocket *socket_out_;
NodeItem::Type to_type_;
GroupNodeParser *group_parser_;
public:
NodeParser(MaterialX::GraphElement *graph,
@ -31,15 +34,18 @@ class NodeParser {
const Material *material,
const bNode *node,
const bNodeSocket *socket_out,
NodeItem::Type to_type);
NodeItem::Type to_type,
GroupNodeParser *group_parser);
virtual ~NodeParser() = default;
virtual NodeItem compute() = 0;
virtual NodeItem compute_full();
protected:
std::string node_name();
std::string node_name() const;
NodeItem create_node(const std::string &category, NodeItem::Type type);
NodeItem create_input(const std::string &name, const NodeItem &item);
NodeItem create_output(const std::string &name, const NodeItem &item);
NodeItem get_input_default(const std::string &name, NodeItem::Type to_type);
NodeItem get_input_default(int index, NodeItem::Type to_type);
NodeItem get_output_default(const std::string &name, NodeItem::Type to_type);
@ -71,6 +77,7 @@ struct NodeParserData {
const Depsgraph *depsgraph;
const Material *material;
NodeItem::Type to_type;
GroupNodeParser *group_parser;
NodeItem result;
};
@ -91,7 +98,8 @@ struct NodeParserData {
void node_shader_materialx(void *data, struct bNode *node, struct bNodeSocket *out) \
{ \
materialx::NodeParserData *d = reinterpret_cast<materialx::NodeParserData *>(data); \
d->result = MaterialXNodeParser(d->graph, d->depsgraph, d->material, node, out, d->to_type) \
d->result = MaterialXNodeParser( \
d->graph, d->depsgraph, d->material, node, out, d->to_type, d->group_parser) \
.compute_full(); \
}

View File

@ -64,17 +64,17 @@ NODE_SHADER_MATERIALX_BEGIN
dielectric.set_input("scatter_mode", val(std::string("RT")));
NodeItem artistic_ior = create_node("artistic_ior", NodeItem::Type::Multioutput);
artistic_ior.add_output("ior", NodeItem::Type::Color3);
artistic_ior.add_output("extinction", NodeItem::Type::Color3);
artistic_ior.set_input("reflectivity", color);
artistic_ior.set_input("edge_color", color);
NodeItem ior_out = artistic_ior.add_output("ior", NodeItem::Type::Color3);
NodeItem extinction_out = artistic_ior.add_output("extinction", NodeItem::Type::Color3);
NodeItem conductor = create_node("conductor_bsdf", NodeItem::Type::BSDF);
if (normal) {
conductor.set_input("normal", normal);
}
conductor.set_input_output("ior", artistic_ior, "ior");
conductor.set_input_output("extinction", artistic_ior, "extinction");
conductor.set_input("ior", ior_out);
conductor.set_input("extinction", extinction_out);
conductor.set_input("roughness", roughness);
NodeItem res = create_node("mix", NodeItem::Type::BSDF);
@ -82,7 +82,7 @@ NODE_SHADER_MATERIALX_BEGIN
res.set_input("bg", conductor);
res.set_input("mix", val(0.5f));
return res ;
return res;
}
#endif
NODE_SHADER_MATERIALX_END

View File

@ -70,10 +70,10 @@ NODE_SHADER_MATERIALX_BEGIN
NodeItem tangent = get_input_link("Tangent", NodeItem::Type::Vector3);
NodeItem artistic_ior = create_node("artistic_ior", NodeItem::Type::Multioutput);
artistic_ior.add_output("ior", NodeItem::Type::Color3);
artistic_ior.add_output("extinction", NodeItem::Type::Color3);
artistic_ior.set_input("reflectivity", color);
artistic_ior.set_input("edge_color", color);
NodeItem ior_out = artistic_ior.add_output("ior", NodeItem::Type::Color3);
NodeItem extinction_out = artistic_ior.add_output("extinction", NodeItem::Type::Color3);
NodeItem res = create_node("conductor_bsdf", NodeItem::Type::BSDF);
if (normal) {
@ -82,8 +82,8 @@ NODE_SHADER_MATERIALX_BEGIN
if (tangent) {
res.set_input("tangent", tangent);
}
res.set_input_output("ior", artistic_ior, "ior");
res.set_input_output("extinction", artistic_ior, "extinction");
res.set_input("ior", ior_out);
res.set_input("extinction", extinction_out);
res.set_input("roughness", roughness);
return res;

View File

@ -60,7 +60,7 @@ NODE_SHADER_MATERIALX_BEGIN
res.set_input("roughness", roughness);
res.set_input("ior", ior);
res.set_input("scatter_mode", val(std::string("T")));
return res;
}
#endif

View File

@ -261,7 +261,7 @@ NODE_SHADER_MATERIALX_BEGIN
#ifdef WITH_MATERIALX
{
NodeItem scale = get_input_value("Scale", NodeItem::Type::Float);
NodeItem detail = get_input_value("Detail", NodeItem::Type::Float);
NodeItem detail = get_input_default("Detail", NodeItem::Type::Float);
NodeItem lacunarity = get_input_value("Lacunarity", NodeItem::Type::Float);
NodeItem position = create_node("position", NodeItem::Type::Vector3);
@ -269,9 +269,7 @@ NODE_SHADER_MATERIALX_BEGIN
NodeItem res = create_node("fractal3d", NodeItem::Type::Color3);
res.set_input("position", position);
if (detail.value) {
res.set_input("octaves", val(int(detail.value->asA<float>())));
}
res.set_input("octaves", val(int(detail.value->asA<float>())));
res.set_input("lacunarity", lacunarity);
return res;
}