Export material to MaterialX for Hydra render #111765

Manually merged
Brecht Van Lommel merged 48 commits from DagerD/blender:matx-export-material into main 2023-09-26 18:56:40 +02:00
22 changed files with 550 additions and 344 deletions
Showing only changes of commit c5b48921bf - Show all commits

View File

@ -209,6 +209,10 @@ if(WITH_OPENVDB)
)
endif()
if(WITH_MATERIALX)
list(APPEND LIB MaterialXCore)
BogdanNagirniak marked this conversation as resolved Outdated

Add add_definitions(-DWITH_MATERIALX)

Add `add_definitions(-DWITH_MATERIALX)`
endif()
blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.h

View File

@ -52,7 +52,9 @@ class HydraSceneDelegate : public pxr::HdSceneDelegate {
const View3D *view3d = nullptr;
Main *bmain = nullptr;
Scene *scene = nullptr;
ShadingSettings shading_settings;
bool use_materialx = true;
private:
ObjectDataMap objects_;

View File

@ -12,6 +12,9 @@
#include <pxr/imaging/hd/tokens.h>
#include <pxr/usdImaging/usdImaging/materialParamUtils.h>
#include <pxr/usd/usdMtlx/reader.h>
BogdanNagirniak marked this conversation as resolved Outdated

Add #ifdef WITH_MATERIALX here and other places in this file, to make it possible to build without materialx.

Add `#ifdef WITH_MATERIALX` here and other places in this file, to make it possible to build without materialx.
#include <pxr/usd/usdMtlx/utils.h>
#include "MEM_guardedalloc.h"
#include "BKE_lib_id.h"
@ -30,6 +33,7 @@
#include "intern/usd_exporter_context.h"
#include "intern/usd_writer_material.h"
#include "shader/materialx/material.h"
namespace blender::io::hydra {
@ -66,10 +70,22 @@ void MaterialData::init()
time,
export_params,
image_cache_file_path()};
/* Create USD material. */
pxr::UsdShadeMaterial usd_material = usd::create_usd_material(
export_context, material_path, (Material *)id, "st");
pxr::UsdShadeMaterial usd_material;
if (scene_delegate_->use_materialx) {
MaterialX::DocumentPtr doc = blender::nodes::materialx::export_to_materialx(
scene_delegate_->depsgraph, (Material *)id);
pxr::UsdMtlxRead(doc, stage);
if (pxr::UsdPrim materials = stage->GetPrimAtPath(pxr::SdfPath("/MaterialX/Materials"))) {
pxr::UsdPrimSiblingRange children = materials.GetChildren();
if (!children.empty()) {
usd_material = pxr::UsdShadeMaterial(*children.begin());
}
}
}
BogdanNagirniak marked this conversation as resolved

for not to -> to not

for not to -> to not
else {
usd_material = usd::create_usd_material(export_context, material_path, (Material *)id, "st");
}
/* Convert USD material to Hydra material network map, adapted for render delegate. */
const pxr::HdRenderDelegate *render_delegate =

View File

@ -145,18 +145,19 @@ set(LIB
if(WITH_MATERIALX)
list(APPEND LIB MaterialXCore)
list(APPEND LIB MaterialXFormat)
list(APPEND SRC
materialx/material.cc
materialx/nodes/node.cc
materialx/nodes/material_output.cc
materialx/nodes/principled_bsdf.cc
materialx/nodes/image.cc
materialx/nodes/bsdf_principled.cc
materialx/nodes/node_parser.cc
materialx/nodes/output_material.cc
materialx/nodes/tex_image.cc
materialx/material.h
materialx/nodes/node.h
materialx/nodes/material_output.h
materialx/nodes/principled_bsdf.h
materialx/nodes/image.h
materialx/nodes/bsdf_principled.h
materialx/nodes/node_parser.h
materialx/nodes/output_material.h
materialx/nodes/tex_image.h
)
endif()

View File

@ -3,34 +3,37 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "material.h"
#include "nodes/material_output.h"
#include "nodes/output_material.h"
#include <MaterialXCore/Node.h>
#include <MaterialXFormat/XmlIo.h>
#include "NOD_shader.h"
namespace blender::nodes::materialx {
static void export_nodegraph(MaterialX::DocumentPtr doc, Depsgraph *depsgraph, Material *material)
static void export_nodegraph(MaterialX::GraphElement *graph,
Depsgraph *depsgraph,
Material *material)
{
material->nodetree->ensure_topology_cache();
bNode *output_node = ntreeShaderOutputNode(material->nodetree, SHD_OUTPUT_ALL);
MaterialXMaterialOutputNode material_node(doc, depsgraph, material, output_node);
material_node.convert();
OutputMaterialNodeParser parser(graph, depsgraph, material, output_node);
parser.compute();
}
static void create_standard_surface(MaterialX::DocumentPtr doc, Material *material)
static void create_standard_surface(MaterialX::GraphElement *graph, Material *material)
BogdanNagirniak marked this conversation as resolved Outdated

This should create a temporary copy of the node tree with ntreeLocalize, similar as is done for generating the GLSL shader. That will resolve any muting and reroute nodes, as handling them as part of the export process is not easy.

Node groups might ideally be preserved in export, but it would be easiest to start with exporting them expanded. For this it would be easiest to use the functions also called from ntreeGPUMaterialNodes:

  ntree_shader_groups_remove_muted_links(localtree);
  ntree_shader_groups_expand_inputs(localtree);
  ntree_shader_groups_flatten(localtree);
This should create a temporary copy of the node tree with `ntreeLocalize`, similar as is done for generating the GLSL shader. That will resolve any muting and reroute nodes, as handling them as part of the export process is not easy. Node groups might ideally be preserved in export, but it would be easiest to start with exporting them expanded. For this it would be easiest to use the functions also called from `ntreeGPUMaterialNodes`: ``` ntree_shader_groups_remove_muted_links(localtree); ntree_shader_groups_expand_inputs(localtree); ntree_shader_groups_flatten(localtree); ```

Exporting of group nodes is implemented in materialx/group_nodes.cc/.h

Exporting of group nodes is implemented in `materialx/group_nodes.cc/.h`
{
MaterialX::NodePtr surfacematerial = doc->addNode(
"surfacematerial", MaterialX::EMPTY_STRING, "material");
MaterialX::NodePtr standard_surface = doc->addNode(
MaterialX::NodePtr standard_surface = graph->addNode(
"standard_surface", MaterialX::EMPTY_STRING, "surfaceshader");
standard_surface->addInput("base", "float")->setValue(1.0);
standard_surface->addInput("base_color", "color3")
->setValue(MaterialX::Color3(material->r, material->g, material->b));
MaterialX::NodePtr surfacematerial = graph->addNode(
"surfacematerial", MaterialX::EMPTY_STRING, "material");
surfacematerial->addInput(standard_surface->getType(), standard_surface->getType())
->setNodeName(standard_surface->getName());
}
@ -39,11 +42,13 @@ MaterialX::DocumentPtr export_to_materialx(Depsgraph *depsgraph, Material *mater
{
MaterialX::DocumentPtr doc = MaterialX::createDocument();
if (material->use_nodes) {
export_nodegraph(doc, depsgraph, material);
export_nodegraph(doc.get(), depsgraph, material);
}
else {
create_standard_surface(doc, material);
create_standard_surface(doc.get(), material);
}
std::string str = MaterialX::writeToXmlString(doc);
printf("\nMaterial: %s\n%s\n", material->id.name, str.c_str());
return doc;
}

View File

@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "bsdf_principled.h"
#include <BKE_node_runtime.hh>
namespace blender::nodes::materialx {
NodeItem BSDFPrincipledNodeParser::compute()
{
auto enabled = [](NodeItem &val) -> bool {
if (val.node) {
return true;
}
if (!val.value) {
return false;
}
if (val.value->isA<float>()) {
BogdanNagirniak marked this conversation as resolved Outdated

I would argue here.... for completeness that we should export the full node tree, even if some parts of it are "inactive".

E.G. Here we only convert the part of the tree with Subsurface Color input if subsurface is turned on. One could imagine that someone wants to take that exported materialx and then turn on the subsurface, then the part for the subsurface color is missing.

It's up to the MaterialX code generation and compiler to prune parts of the node tree that aren't needed. Not here.

I would argue here.... for completeness that we should export the full node tree, even if some parts of it are "inactive". E.G. Here we only convert the part of the tree with Subsurface Color input if subsurface is turned on. One could imagine that someone wants to take that exported materialx and then turn on the subsurface, then the part for the subsurface color is missing. It's up to the MaterialX code generation and compiler to prune parts of the node tree that aren't needed. Not here.

This is done

This is done
return val.value->asA<float>() != 0.0f;
}
if (val.value->isA<MaterialX::Color4>()) {
auto c = val.value->asA<MaterialX::Color4>();
return c[0] != 0.0f || c[1] != 0.0f || c[2] != 0.0f;
}
return true;
};
/* Getting required inputs
* Note: if some inputs are not needed they won't be taken */
NodeItem base_color = get_input_value("Base Color");
NodeItem subsurface = get_input_value("Subsurface");
NodeItem subsurface_radius = empty_value();
NodeItem subsurface_color = empty_value();
if (enabled(subsurface)) {
subsurface_radius = get_input_value("Subsurface Radius");
subsurface_color = get_input_value("Subsurface Color");
}
NodeItem metallic = get_input_value("Metallic");
NodeItem specular = get_input_value("Specular");
// NodeItem specular_tint = get_input_value("Specular Tint");
NodeItem roughness = get_input_value("Roughness");
NodeItem anisotropic = empty_value();
NodeItem anisotropic_rotation = empty_value();
if (enabled(metallic)) {
/* TODO: use Specular Tint input */
anisotropic = get_input_value("Anisotropic");
if (enabled(anisotropic)) {
anisotropic_rotation = get_input_value("Anisotropic Rotation");
// anisotropic_rotation = 0.5 - (anisotropic_rotation % 1.0)
}
}
NodeItem sheen = get_input_value("Sheen");
// sheen_tint = empty_value();
// if enabled(sheen):
// sheen_tint = get_input_value("Sheen Tint");
NodeItem clearcoat = get_input_value("Clearcoat");
NodeItem clearcoat_roughness = empty_value();
if (enabled(clearcoat)) {
clearcoat_roughness = get_input_value("Clearcoat Roughness");
}
NodeItem ior = get_input_value("IOR");
NodeItem transmission = get_input_value("Transmission");
NodeItem transmission_roughness = empty_value();
if (enabled(transmission)) {
transmission_roughness = get_input_value("Transmission Roughness");
}
NodeItem emission = get_input_value("Emission");
NodeItem emission_strength = get_input_value("Emission Strength");
NodeItem alpha = get_input_value("Alpha");
// transparency = 1.0 - alpha
NodeItem normal = get_input_link("Normal");
NodeItem clearcoat_normal = get_input_link("Clearcoat Normal");
NodeItem tangent = get_input_link("Tangent");
/* Creating standard_surface */
NodeItem res = create_node("standard_surface", "surfaceshader");
res.set_input("base", 1.0, "float");
res.set_input("base_color", base_color.to_color3());
res.set_input("diffuse_roughness", roughness);
res.set_input("normal", normal);
res.set_input("tangent", tangent);
if (enabled(metallic)) {
res.set_input("metalness", metallic);
}
if (enabled(specular)) {
res.set_input("specular", specular);
res.set_input("specular_color", base_color.to_color3());
res.set_input("specular_roughness", roughness);
res.set_input("specular_IOR", ior);
res.set_input("specular_anisotropy", anisotropic);
res.set_input("specular_rotation", anisotropic_rotation);
}
if (enabled(transmission)) {
res.set_input("transmission", transmission);
res.set_input("transmission_color", base_color.to_color3());
res.set_input("transmission_extra_roughness", transmission_roughness);
}
if (enabled(subsurface)) {
res.set_input("subsurface", subsurface);
res.set_input("subsurface_color", subsurface_color);
res.set_input("subsurface_radius", subsurface_radius);
res.set_input("subsurface_anisotropy", anisotropic);
}
if (enabled(sheen)) {
res.set_input("sheen", sheen);
res.set_input("sheen_color", base_color.to_color3());
res.set_input("sheen_roughness", roughness);
}
if (enabled(clearcoat)) {
res.set_input("coat", clearcoat);
res.set_input("coat_color", base_color.to_color3());
res.set_input("coat_roughness", clearcoat_roughness);
res.set_input("coat_IOR", ior);
res.set_input("coat_anisotropy", anisotropic);
res.set_input("coat_rotation", anisotropic_rotation);
res.set_input("coat_normal", clearcoat_normal);
}
if (enabled(emission)) {
res.set_input("emission", emission_strength);
res.set_input("emission_color", emission);
}
return res;
}
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node_parser.h"
namespace blender::nodes::materialx {
class BSDFPrincipledNodeParser : public NodeParser {
public:
using NodeParser::NodeParser;
NodeItem compute() override;
};
} // namespace blender::nodes::materialx

View File

@ -1,45 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node.h"
#include "image.h"
#include "hydra/image.h"
#include "DEG_depsgraph_query.h"
namespace blender::nodes::materialx {
const MaterialX::Color3 MaterialXTexImageNode::texture_error_color_{1.0, 0.0, 1.0};
MaterialXTexImageNode::MaterialXTexImageNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node)
: MaterialXNode(doc, depsgraph, material, node)
{
matx_node = doc->addNode("image", MaterialX::createValidName(node->name), "color3");
}
MaterialX::NodePtr MaterialXTexImageNode::convert()
{
Image *image = (Image *)node->id;
NodeTexImage *tex = static_cast<NodeTexImage *>(node->storage);
Scene *scene = DEG_get_input_scene(depsgraph);
Main *bmain = DEG_get_bmain(depsgraph);
std::string image_path;
/* TODO: What if Blender built without Hydra? Also io::hydra::cache_or_get_image_file contain
* pretty general code, so could be moved from bf_usd project. */
#ifdef WITH_HYDRA
image_path = io::hydra::cache_or_get_image_file(bmain, scene, image, &tex->iuser);
#endif
MaterialX::NodePtr uv_node = doc->addNode("texcoord", MaterialX::EMPTY_STRING, "vector2");
matx_node->addInput("file", "filename")->setValue(image_path);
matx_node->addInput("texcoord", "vector2")->setNodeName(uv_node->getName());
return matx_node;
}
} // namespace blender::nodes::materialx

View File

@ -1,24 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node.h"
namespace blender::nodes::materialx {
class MaterialXTexImageNode : public MaterialXNode {
protected:
/* Following Cycles color for wrong Texture nodes. */
static const MaterialX::Color3 texture_error_color_;
public:
MaterialXTexImageNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node);
MaterialX::NodePtr convert() override;
};
} // namespace blender::nodes::materialx

View File

@ -1,35 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "material_output.h"
#include "principled_bsdf.h"
namespace blender::nodes::materialx {
MaterialXMaterialOutputNode::MaterialXMaterialOutputNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node)
: MaterialXNode(doc, depsgraph, material, node)
{
matx_node = doc->addNode("surfacematerial", MaterialX::createValidName(node->name), "material");
}
MaterialX::NodePtr MaterialXMaterialOutputNode::convert()
{
LISTBASE_FOREACH (const bNodeSocket *, sock, &node->inputs) {
if (!sock->link) {
continue;
}
if (STREQ(sock->name, "Surface")) {
const bNode *inode = sock->link->fromnode;
MaterialXPrincipledBSDFNode surface_node(doc, depsgraph, material, inode);
surface_node.convert();
matx_node->addInput("surfaceshader", "surfaceshader")->setNodeName(inode->name);
}
}
return matx_node;
}
} // namespace blender::nodes::materialx

View File

@ -1,20 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node.h"
namespace blender::nodes::materialx {
class MaterialXMaterialOutputNode : public MaterialXNode {
public:
MaterialXMaterialOutputNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node);
MaterialX::NodePtr convert() override;
};
} // namespace blender::nodes::materialx

View File

@ -1,17 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node.h"
namespace blender::nodes::materialx {
MaterialXNode::MaterialXNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node)
: depsgraph(depsgraph), material(material), node(node), doc(doc)
{
}
} // namespace blender::nodes::materialx

View File

@ -1,33 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <MaterialXCore/Document.h>
#include "DEG_depsgraph.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
namespace blender::nodes::materialx {
class MaterialXNode {
public:
const Depsgraph *depsgraph = nullptr;
const Material *material = nullptr;
const bNode *node = nullptr;
MaterialX::NodePtr matx_node;
MaterialX::DocumentPtr doc;
public:
MaterialXNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node);
virtual ~MaterialXNode() = default;
virtual MaterialX::NodePtr convert() = 0;
};
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,180 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_parser.h"
#include "bsdf_principled.h"
#include "tex_image.h"
#include <BKE_node_runtime.hh>
namespace blender::nodes::materialx {
NodeItem::NodeItem(MaterialX::GraphElement *graph) : graph_(graph) {}
void NodeItem::set_input(const std::string &name, const NodeItem &item)
{
if (item.value) {
set_input(name, item.value);
}
else if (item.node) {
set_input(name, item.node);
}
}
void NodeItem::set_input(const std::string &name, const MaterialX::ValuePtr value)
{
if (value->isA<float>()) {
set_input(name, value->asA<float>(), "float");
}
else if (value->isA<MaterialX::Vector3>()) {
set_input(name, value->asA<MaterialX::Vector3>(), "vector3");
}
else if (value->isA<MaterialX::Vector4>()) {
set_input(name, value->asA<MaterialX::Vector4>(), "vector4");
}
else if (value->isA<MaterialX::Color3>()) {
set_input(name, value->asA<MaterialX::Color3>(), "color3");
}
else if (value->isA<MaterialX::Color4>()) {
set_input(name, value->asA<MaterialX::Color4>(), "color4");
}
else {
BLI_assert_unreachable();
}
}
void NodeItem::set_input(const std::string &name, const MaterialX::NodePtr node)
{
this->node->setConnectedNode(name, node);
}
NodeItem::operator bool() const
{
return value || node;
}
NodeItem NodeItem::to_color3()
{
NodeItem res(graph_);
if (value) {
if (value->isA<float>()) {
float v = value->asA<float>();
res.value = MaterialX::Value::createValue<MaterialX::Color3>(MaterialX::Color3(v, v, v));
}
else if (value->isA<MaterialX::Color3>()) {
res.value = value;
}
else if (value->isA<MaterialX::Color4>()) {
auto c = value->asA<MaterialX::Color4>();
res.value = MaterialX::Value::createValue<MaterialX::Color3>(
MaterialX::Color3(c[0], c[1], c[2]));
}
}
else if (node) {
res.node = node;
}
return res;
}
NodeParser::NodeParser(MaterialX::GraphElement *graph,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node)
: graph(graph), depsgraph(depsgraph), material(material), node(node)
{
}
NodeItem NodeParser::create_node(const std::string &mx_category,
const std::string &mx_type,
bool accessory)
{
NodeItem res = empty_value();
res.node = graph->addNode(mx_category,
accessory ? MaterialX::EMPTY_STRING :
MaterialX::createValidName(node->name),
mx_type);
return res;
}
NodeItem NodeParser::get_input_default(const std::string &name)
{
NodeItem res = empty_value();
const bNodeSocket &socket = node->input_by_identifier(name);
switch (socket.type) {
case SOCK_FLOAT: {
float v = socket.default_value_typed<bNodeSocketValueFloat>()->value;
res.value = MaterialX::Value::createValue<float>(v);
} break;
case SOCK_VECTOR: {
const float *v = socket.default_value_typed<bNodeSocketValueVector>()->value;
res.value = MaterialX::Value::createValue<MaterialX::Vector3>(
MaterialX::Vector3(v[0], v[1], v[2]));
} break;
case SOCK_RGBA: {
const float *v = socket.default_value_typed<bNodeSocketValueRGBA>()->value;
res.value = MaterialX::Value::createValue<MaterialX::Color4>(
MaterialX::Color4(v[0], v[1], v[2], v[3]));
} break;
default: {
// TODO log warn
}
}
return res;
}
NodeItem NodeParser::get_input_link(const std::string &name)
{
NodeItem res = empty_value();
const bNodeLink *link = node->input_by_identifier(name).link;
if (!(link && link->is_used())) {
return res;
}
const bNode *in_node = link->fromnode;
/* Passing NODE_REROUTE nodes */
BogdanNagirniak marked this conversation as resolved Outdated

If there is a node type callback, this switch statement will not be needed anymore.

If there is a node type callback, this switch statement will not be needed anymore.
while (in_node->type == NODE_REROUTE) {
link = in_node->input_socket(0).link;
if (!(link && link->is_used())) {
return res;
}
in_node = link->fromnode;
}
/* Getting required NodeParser object */
std::unique_ptr<NodeParser> parser;
switch (in_node->type) {
case SH_NODE_BSDF_PRINCIPLED:
parser = std::make_unique<BSDFPrincipledNodeParser>(graph, depsgraph, material, in_node);
break;
case SH_NODE_TEX_IMAGE:
parser = std::make_unique<TexImageNodeParser>(graph, depsgraph, material, in_node);
break;
default:
// TODO: warning log
return res;
}
res = parser->compute();
return res;
}
NodeItem NodeParser::get_input_value(const std::string &name)
{
NodeItem res = get_input_link(name);
if (!res) {
res = get_input_default(name);
}
return res;
}
NodeItem NodeParser::empty_value()
{
return NodeItem(graph);
}
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <MaterialXCore/Document.h>
#include "DEG_depsgraph.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
namespace blender::nodes::materialx {
class NodeItem {
public:
MaterialX::ValuePtr value;
MaterialX::NodePtr node;
private:
MaterialX::GraphElement *graph_;
public:
NodeItem(MaterialX::GraphElement *graph);
~NodeItem() = default;
template<class T>
void set_input(const std::string &name, const T &value, const std::string &mx_type);
void set_input(const std::string &name, const NodeItem &item);
void set_input(const std::string &name, const MaterialX::ValuePtr value);
void set_input(const std::string &name, const MaterialX::NodePtr node);
operator bool() const;
NodeItem to_color3();
};
template<class T>
void NodeItem::set_input(const std::string &name, const T &value, const std::string &mx_type)
{
node->setInputValue(name, value, mx_type);
}
class NodeParser {
public:
MaterialX::GraphElement *graph;
const Depsgraph *depsgraph;
const Material *material;
const bNode *node;
public:
NodeParser(MaterialX::GraphElement *graph,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node);
virtual ~NodeParser() = default;
virtual NodeItem compute() = 0;
protected:
NodeItem create_node(const std::string &mx_category,
const std::string &mx_type,
bool accessory = false);
NodeItem get_input_default(const std::string &name);
NodeItem get_input_link(const std::string &name);
NodeItem get_input_value(const std::string &name);
NodeItem empty_value();
};
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "output_material.h"
namespace blender::nodes::materialx {
NodeItem OutputMaterialNodeParser::compute()
{
NodeItem node = empty_value();
NodeItem surface = get_input_link("Surface");
if (surface) {
node = create_node("surfacematerial", "material");
node.set_input("surfaceshader", surface);
}
return node;
}
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node_parser.h"
namespace blender::nodes::materialx {
class OutputMaterialNodeParser : public NodeParser {
public:
using NodeParser::NodeParser;
NodeItem compute() override;
};
} // namespace blender::nodes::materialx

View File

@ -1,126 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "principled_bsdf.h"
#include <BKE_node_runtime.hh>
namespace blender::nodes::materialx {
const MaterialX::Color3 MaterialXPrincipledBSDFNode::default_white_color_{1.0, 1.0, 1.0};
MaterialXPrincipledBSDFNode::MaterialXPrincipledBSDFNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node)
: MaterialXNode(doc, depsgraph, material, node)
{
matx_node = doc->addNode(
"standard_surface", MaterialX::createValidName(node->name), "surfaceshader");
}
MaterialX::NodePtr MaterialXPrincipledBSDFNode::convert()
{
#pragma region get inputs
const float *base_color =
node->input_by_identifier("Base Color").default_value_typed<bNodeSocketValueRGBA>()->value;
const float subsurface =
node->input_by_identifier("Subsurface").default_value_typed<bNodeSocketValueFloat>()->value;
const float *subsurface_radius = node->input_by_identifier("Subsurface Radius")
.default_value_typed<bNodeSocketValueVector>()
->value;
const float *subsurface_color = node->input_by_identifier("Subsurface Color")
.default_value_typed<bNodeSocketValueRGBA>()
->value;
const float metallic =
node->input_by_identifier("Metallic").default_value_typed<bNodeSocketValueFloat>()->value;
const float specular =
node->input_by_identifier("Specular").default_value_typed<bNodeSocketValueFloat>()->value;
const float roughness =
node->input_by_identifier("Roughness").default_value_typed<bNodeSocketValueFloat>()->value;
const float anisotropic =
node->input_by_identifier("Anisotropic").default_value_typed<bNodeSocketValueFloat>()->value;
const float anisotropic_rot = node->input_by_identifier("Anisotropic Rotation")
.default_value_typed<bNodeSocketValueFloat>()
->value;
const float sheen =
node->input_by_identifier("Sheen").default_value_typed<bNodeSocketValueFloat>()->value;
const float clearcoat =
node->input_by_identifier("Clearcoat").default_value_typed<bNodeSocketValueFloat>()->value;
const float clearcoat_roughness = node->input_by_identifier("Clearcoat Roughness")
.default_value_typed<bNodeSocketValueFloat>()
->value;
const float IOR =
node->input_by_identifier("IOR").default_value_typed<bNodeSocketValueFloat>()->value;
const float transmission = node->input_by_identifier("Transmission")
.default_value_typed<bNodeSocketValueFloat>()
->value;
const float *emission =
node->input_by_identifier("Emission").default_value_typed<bNodeSocketValueRGBA>()->value;
const float emission_str = node->input_by_identifier("Emission Strength")
.default_value_typed<bNodeSocketValueFloat>()
->value;
const float *normal =
node->input_by_identifier("Normal").default_value_typed<bNodeSocketValueVector>()->value;
const float *clearcoat_normal = node->input_by_identifier("Clearcoat Normal")
.default_value_typed<bNodeSocketValueVector>()
->value;
const float *tangent =
node->input_by_identifier("Tangent").default_value_typed<bNodeSocketValueVector>()->value;
#pragma endregion get inputs
#pragma region set inputs
matx_node->addInput("base", "float")->setValue(1.0);
matx_node->addInput("base_color", "color3")
->setValue(MaterialX::Color3(base_color[0], base_color[1], base_color[2]));
matx_node->addInput("diffuse_roughness", "float")->setValue(roughness);
matx_node->addInput("normal", "vector3")
->setValue(MaterialX::Vector3(normal[0], normal[1], normal[2]));
matx_node->addInput("tangent", "vector3")
->setValue(MaterialX::Vector3(tangent[0], tangent[1], tangent[2]));
matx_node->addInput("metalness", "float")->setValue(metallic);
matx_node->addInput("specular", "float")->setValue(specular);
matx_node->addInput("specular_color", "color3")->setValue(default_white_color_);
matx_node->addInput("specular_roughness", "float")->setValue(roughness);
matx_node->addInput("specular_IOR", "float")->setValue(IOR);
matx_node->addInput("specular_anisotropy", "float")->setValue(anisotropic);
matx_node->addInput("specular_rotation", "float")->setValue(anisotropic_rot);
matx_node->addInput("transmission", "float")->setValue(transmission);
matx_node->addInput("transmission_color", "color3")->setValue(default_white_color_);
matx_node->addInput("transmission_extra_roughness", "float")->setValue(roughness);
matx_node->addInput("subsurface", "float")->setValue(subsurface);
matx_node->addInput("subsurface_color", "color3")
->setValue(MaterialX::Color3(subsurface_color[0], subsurface_color[1], subsurface_color[2]));
matx_node->addInput("subsurface_radius", "color3")
->setValue(
MaterialX::Color3(subsurface_radius[0], subsurface_radius[1], subsurface_radius[2]));
matx_node->addInput("subsurface_anisotropy", "float")->setValue(anisotropic);
matx_node->addInput("sheen", "float")->setValue(sheen);
matx_node->addInput("sheen_color", "color3")->setValue(default_white_color_);
matx_node->addInput("sheen_roughness", "float")->setValue(roughness);
matx_node->addInput("coat", "float")->setValue(clearcoat);
matx_node->addInput("coat_color", "color3")->setValue(default_white_color_);
matx_node->addInput("coat_roughness", "float")->setValue(clearcoat_roughness);
matx_node->addInput("coat_IOR", "float")->setValue(IOR);
matx_node->addInput("coat_anisotropy", "float")->setValue(anisotropic);
matx_node->addInput("coat_rotation", "float")->setValue(anisotropic_rot);
matx_node->addInput("coat_normal", "vector3")
->setValue(
MaterialX::Vector3(clearcoat_normal[0], clearcoat_normal[1], clearcoat_normal[2]));
matx_node->addInput("emission", "float")->setValue(emission_str);
matx_node->addInput("emission_color", "color3")
->setValue(MaterialX::Color3(emission[0], emission[1], emission[2]));
#pragma endregion set inputs
return matx_node;
}
} // namespace blender::nodes::materialx

View File

@ -1,23 +0,0 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node.h"
namespace blender::nodes::materialx {
class MaterialXPrincipledBSDFNode : public MaterialXNode {
protected:
static const MaterialX::Color3 default_white_color_;
public:
MaterialXPrincipledBSDFNode(MaterialX::DocumentPtr doc,
const Depsgraph *depsgraph,
const Material *material,
const bNode *node);
MaterialX::NodePtr convert() override;
};
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "tex_image.h"
#include "node_parser.h"
#include "hydra/image.h"
#include "DEG_depsgraph_query.h"
namespace blender::nodes::materialx {
NodeItem TexImageNodeParser::compute()
{
Image *image = (Image *)node->id;
NodeTexImage *tex = static_cast<NodeTexImage *>(node->storage);
Scene *scene = DEG_get_input_scene(depsgraph);
Main *bmain = DEG_get_bmain(depsgraph);
std::string image_path;
/* TODO: What if Blender built without Hydra? Also io::hydra::cache_or_get_image_file contain
* pretty general code, so could be moved from bf_usd project. */
#ifdef WITH_HYDRA
BogdanNagirniak marked this conversation as resolved Outdated

Can thiscallback function somehow that can be provided by the code that calls the MaterialX conversion?

That would also avoid the dependency of the shader nodes module on the USD module.

Can thiscallback function somehow that can be provided by the code that calls the MaterialX conversion? That would also avoid the dependency of the shader nodes module on the USD module.
image_path = io::hydra::cache_or_get_image_file(bmain, scene, image, &tex->iuser);
#endif
NodeItem texcoord = create_node("texcoord", "vector2", true);
NodeItem res = create_node("image", "color3");
res.set_input("file", image_path, "filename");
res.set_input("texcoord", texcoord);
return res;
}
} // namespace blender::nodes::materialx

View File

@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "node_parser.h"
namespace blender::nodes::materialx {
class TexImageNodeParser : public NodeParser {
public:
using NodeParser::NodeParser;
NodeItem compute() override;
};
} // namespace blender::nodes::materialx

View File

@ -89,6 +89,7 @@ void Engine::sync(Depsgraph *depsgraph, bContext *context)
pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("scene");
hydra_scene_delegate_ = std::make_unique<io::hydra::HydraSceneDelegate>(render_index_.get(),
scene_path);
hydra_scene_delegate_->use_materialx = bl_engine_->type->flag & RE_USE_MATERIALX;
}
hydra_scene_delegate_->populate(depsgraph, context ? CTX_wm_view3d(context) : nullptr);
}