Export material to MaterialX for Hydra render #111765
|
@ -209,6 +209,10 @@ if(WITH_OPENVDB)
|
|||
)
|
||||
endif()
|
||||
|
||||
if(WITH_MATERIALX)
|
||||
list(APPEND LIB MaterialXCore)
|
||||
BogdanNagirniak marked this conversation as resolved
Outdated
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
# RNA_prototypes.h
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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
Brecht Van Lommel
commented
Add 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
Brecht Van Lommel
commented
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 =
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
Brecht Van Lommel
commented
This should create a temporary copy of the node tree with 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
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);
```
Bogdan Nagirniak
commented
Exporting of group nodes is implemented in 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Brian Savery (AMD)
commented
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.
Bogdan Nagirniak
commented
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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Brecht Van Lommel
commented
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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Brecht Van Lommel
commented
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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
Add
add_definitions(-DWITH_MATERIALX)