2023-11-07 15:42:52 +11:00
|
|
|
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
|
2023-09-25 18:36:42 +02:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
|
|
|
|
|
|
#include "node_item.h"
|
2025-01-20 11:37:08 +01:00
|
|
|
#include "node_graph.h"
|
2023-09-25 18:36:42 +02:00
|
|
|
#include "node_parser.h"
|
|
|
|
|
|
|
|
|
|
#include "BLI_assert.h"
|
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
|
|
|
|
|
|
namespace blender::nodes::materialx {
|
|
|
|
|
|
|
|
|
|
NodeItem::NodeItem(MaterialX::GraphElement *graph) : graph_(graph) {}
|
|
|
|
|
|
|
|
|
|
NodeItem::Type NodeItem::type(const std::string &type_str)
|
|
|
|
|
{
|
|
|
|
|
/* Converting only MaterialX supported types */
|
|
|
|
|
if (type_str == "multioutput") {
|
|
|
|
|
return Type::Multioutput;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "string") {
|
|
|
|
|
return Type::String;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "filename") {
|
|
|
|
|
return Type::Filename;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "boolean") {
|
|
|
|
|
return Type::Boolean;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "integer") {
|
|
|
|
|
return Type::Integer;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "float") {
|
|
|
|
|
return Type::Float;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "vector2") {
|
|
|
|
|
return Type::Vector2;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "vector3") {
|
|
|
|
|
return Type::Vector3;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "vector4") {
|
|
|
|
|
return Type::Vector4;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "color3") {
|
|
|
|
|
return Type::Color3;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "color4") {
|
|
|
|
|
return Type::Color4;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "BSDF") {
|
|
|
|
|
return Type::BSDF;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "EDF") {
|
|
|
|
|
return Type::EDF;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "displacementshader") {
|
|
|
|
|
return Type::DisplacementShader;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "surfaceshader") {
|
|
|
|
|
return Type::SurfaceShader;
|
|
|
|
|
}
|
|
|
|
|
if (type_str == "material") {
|
|
|
|
|
return Type::Material;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
return Type::Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string NodeItem::type(Type type)
|
|
|
|
|
{
|
|
|
|
|
switch (type) {
|
|
|
|
|
case Type::Any:
|
|
|
|
|
return "any";
|
|
|
|
|
case Type::Multioutput:
|
|
|
|
|
return "multioutput";
|
|
|
|
|
case Type::String:
|
|
|
|
|
return "string";
|
|
|
|
|
case Type::Filename:
|
|
|
|
|
return "filename";
|
|
|
|
|
case Type::Boolean:
|
|
|
|
|
return "boolean";
|
|
|
|
|
case Type::Integer:
|
|
|
|
|
return "integer";
|
|
|
|
|
case Type::Float:
|
|
|
|
|
return "float";
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
return "vector2";
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
return "vector3";
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
return "vector4";
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
return "color3";
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
return "color4";
|
|
|
|
|
case Type::BSDF:
|
|
|
|
|
return "BSDF";
|
|
|
|
|
case Type::EDF:
|
|
|
|
|
return "EDF";
|
|
|
|
|
case Type::DisplacementShader:
|
|
|
|
|
return "displacementshader";
|
|
|
|
|
case Type::SurfaceShader:
|
|
|
|
|
return "surfaceshader";
|
|
|
|
|
case Type::Material:
|
|
|
|
|
return "material";
|
|
|
|
|
case Type::SurfaceOpacity:
|
|
|
|
|
return "opacity";
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NodeItem::is_arithmetic(Type type)
|
|
|
|
|
{
|
|
|
|
|
return type >= Type::Float && type <= Type::Color4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem::operator bool() const
|
|
|
|
|
{
|
|
|
|
|
return value || node || input || output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator+(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
Type type = this->type();
|
|
|
|
|
if (ELEM(type, Type::BSDF, Type::EDF)) {
|
|
|
|
|
/* Special case: add BSDF/EDF shaders */
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
if (other.type() == type) {
|
|
|
|
|
res = create_node("add", type, {{"in1", *this}, {"in2", other}});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return arithmetic(other, "add", [](float a, float b) { return a + b; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator-(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic(other, "subtract", [](float a, float b) { return a - b; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator-() const
|
|
|
|
|
{
|
|
|
|
|
return val(0.0f) - *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator*(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
Type type = this->type();
|
|
|
|
|
if (ELEM(type, Type::BSDF, Type::EDF)) {
|
|
|
|
|
/* Special case: multiple BSDF/EDF shader by Float or Color3 */
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
Type other_type = other.type();
|
|
|
|
|
if (ELEM(other_type, Type::Float, Type::Color3)) {
|
|
|
|
|
res = create_node("multiply", type, {{"in1", *this}, {"in2", other}});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return arithmetic(other, "multiply", [](float a, float b) { return a * b; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator/(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic(other, "divide", [](float a, float b) { return b == 0.0f ? 0.0f : a / b; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator%(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic(
|
|
|
|
|
other, "modulo", [](float a, float b) { return b == 0.0f ? 0.0f : std::fmod(a, b); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator^(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic(other, "power", [](float a, float b) { return std::pow(a, b); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::operator[](int index) const
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(is_arithmetic(type()));
|
|
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
|
float v = 0.0f;
|
|
|
|
|
switch (type()) {
|
|
|
|
|
case Type::Float:
|
|
|
|
|
v = value->asA<float>();
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
v = value->asA<MaterialX::Vector2>()[index];
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
v = value->asA<MaterialX::Vector3>()[index];
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
v = value->asA<MaterialX::Vector4>()[index];
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
v = value->asA<MaterialX::Color3>()[index];
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
v = value->asA<MaterialX::Color4>()[index];
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
return val(v);
|
|
|
|
|
}
|
|
|
|
|
return create_node("extract", Type::Float, {{"in", *this}, {"index", val(index)}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NodeItem::operator==(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
if (!*this) {
|
|
|
|
|
return !other;
|
|
|
|
|
}
|
|
|
|
|
if (!other) {
|
|
|
|
|
return !*this;
|
|
|
|
|
}
|
|
|
|
|
if (node && node == other.node) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if ((node && other.value) || (value && other.node)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem item1 = *this;
|
|
|
|
|
NodeItem item2 = other;
|
|
|
|
|
Type to_type = cast_types(item1, item2);
|
|
|
|
|
if (to_type == Type::Empty) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return item1.value->getValueString() == item2.value->getValueString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NodeItem::operator!=(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return !(*this == other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::abs() const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic("absval", [](float a) { return std::abs(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::floor() const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic("floor", [](float a) { return std::floor(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::ceil() const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic("ceil", [](float a) { return std::ceil(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::length() const
|
|
|
|
|
{
|
|
|
|
|
if (value) {
|
|
|
|
|
return dotproduct(*this).sqrt();
|
|
|
|
|
}
|
|
|
|
|
return create_node("magnitude", Type::Float, {{"in", to_vector()}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::normalize() const
|
|
|
|
|
{
|
|
|
|
|
if (value) {
|
|
|
|
|
return *this / length();
|
|
|
|
|
}
|
|
|
|
|
return create_node("normalize", Type::Vector3, {{"in", to_vector()}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::min(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic(other, "min", [](float a, float b) { return std::min(a, b); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::max(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic(other, "max", [](float a, float b) { return std::max(a, b); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::dotproduct(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
if (value && other.value) {
|
|
|
|
|
NodeItem d = *this * other;
|
|
|
|
|
float f = 0.0f;
|
|
|
|
|
switch (d.type()) {
|
|
|
|
|
case Type::Float: {
|
|
|
|
|
f = d.value->asA<float>();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector2: {
|
|
|
|
|
auto v = d.value->asA<MaterialX::Vector2>();
|
|
|
|
|
f = v[0] + v[1];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector3: {
|
|
|
|
|
auto v = d.value->asA<MaterialX::Vector3>();
|
|
|
|
|
f = v[0] + v[1] + v[2];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector4: {
|
|
|
|
|
auto v = d.value->asA<MaterialX::Vector4>();
|
|
|
|
|
f = v[0] + v[1] + v[2] + v[3];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color3: {
|
|
|
|
|
auto v = d.value->asA<MaterialX::Color3>();
|
|
|
|
|
f = v[0] + v[1] + v[2];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color4: {
|
|
|
|
|
auto v = d.value->asA<MaterialX::Color4>();
|
|
|
|
|
f = v[0] + v[1] + v[2] + v[3];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
return val(f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem item1 = to_vector();
|
|
|
|
|
NodeItem item2 = other.to_vector();
|
|
|
|
|
cast_types(item1, item2);
|
|
|
|
|
return create_node("dotproduct", Type::Float, {{"in1", item1}, {"in2", item2}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::mix(const NodeItem &val1, const NodeItem &val2) const
|
|
|
|
|
{
|
|
|
|
|
if ((value && val1.value && val2.value) || type() != Type::Float) {
|
|
|
|
|
return (val(1.0f) - *this) * val1 + *this * val2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Type type1 = val1.type();
|
|
|
|
|
if (ELEM(type1, Type::BSDF, Type::EDF)) {
|
|
|
|
|
BLI_assert(val2.type() == type1);
|
|
|
|
|
|
|
|
|
|
/* Special case: mix BSDF/EDF shaders */
|
|
|
|
|
return create_node("mix", type1, {{"bg", val1}, {"fg", val2}, {"mix", *this}});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
NodeItem item1 = val1;
|
|
|
|
|
NodeItem item2 = val2;
|
|
|
|
|
Type to_type = cast_types(item1, item2);
|
|
|
|
|
return create_node("mix", to_type, {{"bg", item1}, {"fg", item2}, {"mix", *this}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::clamp(const NodeItem &min_val, const NodeItem &max_val) const
|
|
|
|
|
{
|
|
|
|
|
if (value && min_val.value && max_val.value) {
|
|
|
|
|
return min(max_val).max(min_val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (min_val.type() == Type::Float && max_val.type() == Type::Float) {
|
|
|
|
|
return create_node("clamp", type(), {{"in", *this}, {"low", min_val}, {"high", max_val}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Type type = this->type();
|
|
|
|
|
return create_node(
|
|
|
|
|
"clamp",
|
|
|
|
|
type,
|
|
|
|
|
{{"in", *this}, {"low", min_val.convert(type)}, {"high", max_val.convert(type)}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::clamp(float min_val, float max_val) const
|
|
|
|
|
{
|
|
|
|
|
return clamp(val(min_val), val(max_val));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::rotate(const NodeItem &angle, const NodeItem &axis)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(type() == Type::Vector3);
|
|
|
|
|
BLI_assert(angle.type() == Type::Float);
|
|
|
|
|
BLI_assert(axis.type() == Type::Vector3);
|
|
|
|
|
|
|
|
|
|
return create_node(
|
|
|
|
|
"rotate3d", NodeItem::Type::Vector3, {{"in", *this}, {"amount", angle}, {"axis", axis}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::rotate(const NodeItem &angle_xyz, bool invert)
|
|
|
|
|
{
|
|
|
|
|
NodeItem x = angle_xyz[0];
|
|
|
|
|
NodeItem y = angle_xyz[1];
|
|
|
|
|
NodeItem z = angle_xyz[2];
|
|
|
|
|
|
|
|
|
|
NodeItem x_axis = val(MaterialX::Vector3(1.0f, 0.0f, 0.0f));
|
|
|
|
|
NodeItem y_axis = val(MaterialX::Vector3(0.0f, 1.0f, 0.0f));
|
|
|
|
|
NodeItem z_axis = val(MaterialX::Vector3(0.0f, 0.0f, 1.0f));
|
|
|
|
|
|
|
|
|
|
if (invert) {
|
|
|
|
|
return rotate(z, z_axis).rotate(y, y_axis).rotate(x, x_axis);
|
|
|
|
|
}
|
|
|
|
|
return rotate(x, x_axis).rotate(y, y_axis).rotate(z, z_axis);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::sin() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("sin", [](float a) { return std::sin(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::cos() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("cos", [](float a) { return std::cos(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::tan() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("tan", [](float a) { return std::tan(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::asin() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("asin", [](float a) { return std::asin(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::acos() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("acos", [](float a) { return std::acos(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::atan() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("atan", [](float a) { return std::atan(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::atan2(const NodeItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic(other, "atan2", [](float a, float b) { return std::atan2(a, b); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::sinh() const
|
|
|
|
|
{
|
|
|
|
|
NodeItem v = to_vector();
|
|
|
|
|
return (v.exp() - (-v).exp()) / val(2.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::cosh() const
|
|
|
|
|
{
|
|
|
|
|
NodeItem v = to_vector();
|
|
|
|
|
return (v.exp() + (-v).exp()) / val(2.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::tanh() const
|
|
|
|
|
{
|
|
|
|
|
NodeItem v = to_vector();
|
|
|
|
|
NodeItem a = v.exp();
|
|
|
|
|
NodeItem b = (-v).exp();
|
|
|
|
|
return (a - b) / (a + b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::ln() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("ln", [](float a) { return std::log(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::sqrt() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("sqrt", [](float a) { return std::sqrt(a); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::sign() const
|
|
|
|
|
{
|
|
|
|
|
return arithmetic("sign", [](float a) { return a < 0.0f ? -1.0f : (a == 0.0f ? 0.0f : 1.0f); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::exp() const
|
|
|
|
|
{
|
|
|
|
|
return to_vector().arithmetic("exp", [](float a) { return std::exp(a); });
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-08 11:39:54 +01:00
|
|
|
bool NodeItem::is_convertible(eNodeSocketDatatype from_type, Type to_type)
|
|
|
|
|
{
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Any:
|
|
|
|
|
return true;
|
|
|
|
|
case Type::Empty:
|
|
|
|
|
case Type::Multioutput:
|
|
|
|
|
return false;
|
|
|
|
|
case Type::String:
|
|
|
|
|
case Type::Filename:
|
|
|
|
|
return from_type == SOCK_STRING;
|
|
|
|
|
case Type::Boolean:
|
|
|
|
|
return from_type == SOCK_BOOLEAN;
|
|
|
|
|
case Type::Integer:
|
|
|
|
|
return from_type == SOCK_INT;
|
|
|
|
|
case Type::Float:
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
case Type::DisplacementShader:
|
|
|
|
|
return ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
|
|
|
|
|
case Type::EDF:
|
|
|
|
|
return ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER);
|
|
|
|
|
case Type::BSDF:
|
|
|
|
|
case Type::SurfaceShader:
|
|
|
|
|
case Type::Material:
|
|
|
|
|
case Type::SurfaceOpacity:
|
|
|
|
|
return from_type == SOCK_SHADER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-25 18:36:42 +02:00
|
|
|
NodeItem NodeItem::convert(Type to_type) const
|
|
|
|
|
{
|
|
|
|
|
Type from_type = type();
|
|
|
|
|
if (from_type == Type::Empty || from_type == to_type || to_type == Type::Any) {
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
2025-01-08 11:39:54 +01:00
|
|
|
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
/* Link arithmetic types to shader as EDF. */
|
|
|
|
|
case Type::EDF:
|
|
|
|
|
if (is_arithmetic(from_type)) {
|
|
|
|
|
return create_node("uniform_edf", NodeItem::Type::EDF, {{"color", convert(Type::Color3)}});
|
|
|
|
|
}
|
|
|
|
|
return empty();
|
|
|
|
|
/* Displacement shader from arithmetic types, when not using (Vector) Displacement node. */
|
|
|
|
|
case Type::DisplacementShader:
|
|
|
|
|
if (is_arithmetic(from_type)) {
|
|
|
|
|
return create_node("displacement",
|
|
|
|
|
NodeItem::Type::DisplacementShader,
|
|
|
|
|
{{"displacement", convert(Type::Vector3)}});
|
|
|
|
|
}
|
|
|
|
|
return empty();
|
|
|
|
|
/* Surface opacity is just a float. */
|
|
|
|
|
case Type::SurfaceOpacity:
|
|
|
|
|
to_type = Type::Float;
|
|
|
|
|
if (from_type == to_type || to_type == Type::Any) {
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
/* Material output will evaluate graph multiple times for different components,
|
|
|
|
|
* when linking arithmetic types we want to leave those empty. */
|
|
|
|
|
case Type::BSDF:
|
|
|
|
|
case Type::SurfaceShader:
|
|
|
|
|
case Type::Material:
|
|
|
|
|
return empty();
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-25 18:36:42 +02:00
|
|
|
if (!is_arithmetic(from_type) || !is_arithmetic(to_type)) {
|
|
|
|
|
CLOG_WARN(LOG_MATERIALX_SHADER,
|
|
|
|
|
"Cannot convert: %s -> %s",
|
|
|
|
|
type(from_type).c_str(),
|
|
|
|
|
type(to_type).c_str());
|
|
|
|
|
return empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (to_type == Type::Float) {
|
|
|
|
|
return (*this)[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Converting types which requires > 1 iteration */
|
|
|
|
|
switch (from_type) {
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Vector4);
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Color3);
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Color3).convert(Type::Color4);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
return convert(Type::Color3).convert(Type::Color4);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Vector2);
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Color3);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Vector2);
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
return convert(Type::Vector3).convert(Type::Vector4);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
return convert(Type::Vector4).convert(Type::Vector3).convert(Type::Vector2);
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
return convert(Type::Vector4).convert(Type::Vector3);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Converting 1 iteration types */
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
if (value) {
|
|
|
|
|
switch (from_type) {
|
|
|
|
|
case Type::Float: {
|
|
|
|
|
float v = value->asA<float>();
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector2>({v, v});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector3>({v, v, v});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector4>({v, v, v, 1.0f});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color3>({v, v, v});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color4>({v, v, v, 1.0f});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector2: {
|
|
|
|
|
auto v = value->asA<MaterialX::Vector2>();
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector3>({v[0], v[1], 0.0f});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector3: {
|
|
|
|
|
auto v = value->asA<MaterialX::Vector3>();
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector2>({v[0], v[1]});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector4>(
|
|
|
|
|
{v[0], v[1], v[2], 0.0f});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color3>({v[0], v[1], v[2]});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector4: {
|
|
|
|
|
auto v = value->asA<MaterialX::Vector4>();
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector3>({v[0], v[1], v[2]});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color4>({v[0], v[1], v[2], v[3]});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color3: {
|
|
|
|
|
auto v = value->asA<MaterialX::Color3>();
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector3>({v[0], v[1], v[2]});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color4>({v[0], v[1], v[2], 1.0f});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color4: {
|
|
|
|
|
auto v = value->asA<MaterialX::Color4>();
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector4>(
|
|
|
|
|
{v[0], v[1], v[2], v[3]});
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color3>({v[0], v[1], v[2]});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
res = create_node("convert", to_type, {{"in", *this}});
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::to_vector() const
|
|
|
|
|
{
|
|
|
|
|
switch (type()) {
|
|
|
|
|
case Type::Float:
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
return convert(Type::Vector3);
|
|
|
|
|
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
return convert(Type::Vector4);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
return empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::if_else(CompareOp op,
|
|
|
|
|
const NodeItem &other,
|
|
|
|
|
const NodeItem &if_val,
|
|
|
|
|
const NodeItem &else_val) const
|
|
|
|
|
{
|
|
|
|
|
switch (op) {
|
|
|
|
|
case CompareOp::Less:
|
2024-02-05 11:39:51 +01:00
|
|
|
return if_else(CompareOp::GreaterEq, other, else_val, if_val);
|
2023-09-25 18:36:42 +02:00
|
|
|
case CompareOp::LessEq:
|
2024-02-05 11:39:51 +01:00
|
|
|
return if_else(CompareOp::Greater, other, else_val, if_val);
|
2023-09-25 18:36:42 +02:00
|
|
|
case CompareOp::NotEq:
|
|
|
|
|
return if_else(CompareOp::Eq, other, else_val, if_val);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
if (type() != Type::Float || other.type() != Type::Float) {
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto item1 = if_val;
|
|
|
|
|
auto item2 = else_val;
|
|
|
|
|
Type to_type = cast_types(item1, item2);
|
|
|
|
|
if (to_type == Type::Empty) {
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::function<bool(float, float)> func = nullptr;
|
|
|
|
|
std::string category;
|
|
|
|
|
switch (op) {
|
|
|
|
|
case CompareOp::Greater:
|
|
|
|
|
category = "ifgreater";
|
|
|
|
|
func = [](float a, float b) { return a > b; };
|
|
|
|
|
break;
|
|
|
|
|
case CompareOp::GreaterEq:
|
|
|
|
|
category = "ifgreatereq";
|
|
|
|
|
func = [](float a, float b) { return a >= b; };
|
|
|
|
|
break;
|
|
|
|
|
case CompareOp::Eq:
|
|
|
|
|
category = "ifequal";
|
|
|
|
|
func = [](float a, float b) { return a == b; };
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value && other.value) {
|
|
|
|
|
res = func(value->asA<float>(), other.value->asA<float>()) ? item1 : item2;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
res = create_node(
|
|
|
|
|
category, to_type, {{"value1", *this}, {"value2", other}, {"in1", item1}, {"in2", item2}});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::empty() const
|
|
|
|
|
{
|
|
|
|
|
return NodeItem(graph_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem::Type NodeItem::type() const
|
|
|
|
|
{
|
|
|
|
|
if (value) {
|
|
|
|
|
return type(value->getTypeString());
|
|
|
|
|
}
|
|
|
|
|
if (node) {
|
|
|
|
|
return type(node->getType());
|
|
|
|
|
}
|
|
|
|
|
if (output) {
|
|
|
|
|
return type(output->getType());
|
|
|
|
|
}
|
|
|
|
|
return Type::Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::create_node(const std::string &category, Type type) const
|
|
|
|
|
{
|
2025-01-20 11:37:08 +01:00
|
|
|
const std::string name = NodeGraph::unique_anonymous_node_name(graph_);
|
|
|
|
|
const std::string type_str = NodeItem::type(type);
|
2023-09-25 18:36:42 +02:00
|
|
|
CLOG_INFO(LOG_MATERIALX_SHADER, 2, "<%s type=%s>", category.c_str(), type_str.c_str());
|
|
|
|
|
NodeItem res = empty();
|
2024-06-06 09:43:48 +10:00
|
|
|
/* Surface-shader nodes and materials are added directly to the document,
|
|
|
|
|
* otherwise to the node-graph. */
|
2024-06-11 20:45:00 +10:00
|
|
|
if (ELEM(type, Type::SurfaceShader, Type::Material)) {
|
2025-01-20 11:37:08 +01:00
|
|
|
res.node = graph_->getDocument()->addNode(category, name, type_str);
|
USD: Add MaterialX shader export
This change adds the ability to export MaterialX networks into the resulting
USD layer.
Details:
A new export option has been added to the USD export to enable MaterialX
export. It is off by default currently due to reasons in the caveats
section.
When enabled, it exports the MaterialX shading network alongside the
UsdPreviewSurface network, on the same USD Material. This allows the same
material to be used by renderers that don't support MaterialX, using the
USDPreviewSurface as a fallback. This is similar to setups in other DCC
packages, and matches the format we've used in our Reality Composer Pro
asset library.
It uses the existing MaterialX framework used to generate MaterialX
documents for rendering, to act as the basis for the USD graph. In this
process it also re-uses the existing texture export code as well if provided
and necessary.
Once the MaterialX document is created, use usdMtlx to generate a USD
shading network. Unfortunately, usdMtlx generates a graph that is unlike
what other DCCs that support MaterialX-embedded-in-USD generates. It
generates several extra prim hierarchies, and externalizes all shader
inputs, making them difficult to edit in other MaterialX graph editors.
To workaround this, generate the MaterialX shading network onto a
temporary stage, where we then run various pre-processing steps to prevent
prim collisions and to reflow the paths once they're converted.
The PrimSpecs are then copied over to their new path. The resulting prim
hierarchy matches what many artists we've worked with prefer to work with.
Caveats:
The Export MaterialX check is off by default. When using the Principled
BSDF, the resulting graph is very usable. However, when using some of the
other BSDFs, the shading networks generated by the existing MaterialX
framework in Blender generate some shading graphs that are difficult for
usdview and other DCC's to understand. The graph is still correct, but
because we're trying to prioritize compatibility, the default is off.
In future PRs we can aim to make the graphs for those other BSDFs play
better with other DCCs.
Other Implementation Details:
As part of this commit we've also done the following:
* Place some of the materialx graphs inside a passthrough nodegraph to
avoid node conflicts.
* Better handle some shader output types , and better handle some
conflict cases.
* Moved the ExportTextureFunction to materials.h due to some difficult
to resolve header ordering issues. This has no effect on any runtime code.
* There is a test for the MaterialX export that does some basic checking to
make sure we get an export out the other end that matches our expectations
Authored by Apple: Dhruv Govil
This PR is based on an earlier implementation by Brecht van Lommel , as well
as Brian Savery and his teams' work at AMD to implement the general
MaterialX framework within Blender.
Pull Request: https://projects.blender.org/blender/blender/pulls/122575
2024-06-05 20:43:44 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-01-20 11:37:08 +01:00
|
|
|
res.node = graph_->addNode(category, name, type_str);
|
USD: Add MaterialX shader export
This change adds the ability to export MaterialX networks into the resulting
USD layer.
Details:
A new export option has been added to the USD export to enable MaterialX
export. It is off by default currently due to reasons in the caveats
section.
When enabled, it exports the MaterialX shading network alongside the
UsdPreviewSurface network, on the same USD Material. This allows the same
material to be used by renderers that don't support MaterialX, using the
USDPreviewSurface as a fallback. This is similar to setups in other DCC
packages, and matches the format we've used in our Reality Composer Pro
asset library.
It uses the existing MaterialX framework used to generate MaterialX
documents for rendering, to act as the basis for the USD graph. In this
process it also re-uses the existing texture export code as well if provided
and necessary.
Once the MaterialX document is created, use usdMtlx to generate a USD
shading network. Unfortunately, usdMtlx generates a graph that is unlike
what other DCCs that support MaterialX-embedded-in-USD generates. It
generates several extra prim hierarchies, and externalizes all shader
inputs, making them difficult to edit in other MaterialX graph editors.
To workaround this, generate the MaterialX shading network onto a
temporary stage, where we then run various pre-processing steps to prevent
prim collisions and to reflow the paths once they're converted.
The PrimSpecs are then copied over to their new path. The resulting prim
hierarchy matches what many artists we've worked with prefer to work with.
Caveats:
The Export MaterialX check is off by default. When using the Principled
BSDF, the resulting graph is very usable. However, when using some of the
other BSDFs, the shading networks generated by the existing MaterialX
framework in Blender generate some shading graphs that are difficult for
usdview and other DCC's to understand. The graph is still correct, but
because we're trying to prioritize compatibility, the default is off.
In future PRs we can aim to make the graphs for those other BSDFs play
better with other DCCs.
Other Implementation Details:
As part of this commit we've also done the following:
* Place some of the materialx graphs inside a passthrough nodegraph to
avoid node conflicts.
* Better handle some shader output types , and better handle some
conflict cases.
* Moved the ExportTextureFunction to materials.h due to some difficult
to resolve header ordering issues. This has no effect on any runtime code.
* There is a test for the MaterialX export that does some basic checking to
make sure we get an export out the other end that matches our expectations
Authored by Apple: Dhruv Govil
This PR is based on an earlier implementation by Brecht van Lommel , as well
as Brian Savery and his teams' work at AMD to implement the general
MaterialX framework within Blender.
Pull Request: https://projects.blender.org/blender/blender/pulls/122575
2024-06-05 20:43:44 +02:00
|
|
|
}
|
2023-09-25 18:36:42 +02:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::create_node(const std::string &category, Type type, const Inputs &inputs) const
|
|
|
|
|
{
|
|
|
|
|
NodeItem res = create_node(category, type);
|
2025-01-20 11:37:08 +01:00
|
|
|
for (const auto &it : inputs) {
|
2023-09-25 18:36:42 +02:00
|
|
|
if (it.second) {
|
|
|
|
|
res.set_input(it.first, it.second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NodeItem::set_input(const std::string &in_name, const NodeItem &item)
|
|
|
|
|
{
|
|
|
|
|
if (item.value) {
|
|
|
|
|
Type item_type = item.type();
|
|
|
|
|
switch (item_type) {
|
|
|
|
|
case Type::String:
|
|
|
|
|
set_input(in_name, item.value->asA<std::string>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Boolean:
|
|
|
|
|
set_input(in_name, item.value->asA<bool>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Integer:
|
|
|
|
|
set_input(in_name, item.value->asA<int>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Float:
|
|
|
|
|
set_input(in_name, item.value->asA<float>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector2:
|
|
|
|
|
set_input(in_name, item.value->asA<MaterialX::Vector2>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector3:
|
|
|
|
|
set_input(in_name, item.value->asA<MaterialX::Vector3>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Vector4:
|
|
|
|
|
set_input(in_name, item.value->asA<MaterialX::Vector4>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color3:
|
|
|
|
|
set_input(in_name, item.value->asA<MaterialX::Color3>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
case Type::Color4:
|
|
|
|
|
set_input(in_name, item.value->asA<MaterialX::Color4>(), item_type);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (item.node) {
|
USD: Add MaterialX shader export
This change adds the ability to export MaterialX networks into the resulting
USD layer.
Details:
A new export option has been added to the USD export to enable MaterialX
export. It is off by default currently due to reasons in the caveats
section.
When enabled, it exports the MaterialX shading network alongside the
UsdPreviewSurface network, on the same USD Material. This allows the same
material to be used by renderers that don't support MaterialX, using the
USDPreviewSurface as a fallback. This is similar to setups in other DCC
packages, and matches the format we've used in our Reality Composer Pro
asset library.
It uses the existing MaterialX framework used to generate MaterialX
documents for rendering, to act as the basis for the USD graph. In this
process it also re-uses the existing texture export code as well if provided
and necessary.
Once the MaterialX document is created, use usdMtlx to generate a USD
shading network. Unfortunately, usdMtlx generates a graph that is unlike
what other DCCs that support MaterialX-embedded-in-USD generates. It
generates several extra prim hierarchies, and externalizes all shader
inputs, making them difficult to edit in other MaterialX graph editors.
To workaround this, generate the MaterialX shading network onto a
temporary stage, where we then run various pre-processing steps to prevent
prim collisions and to reflow the paths once they're converted.
The PrimSpecs are then copied over to their new path. The resulting prim
hierarchy matches what many artists we've worked with prefer to work with.
Caveats:
The Export MaterialX check is off by default. When using the Principled
BSDF, the resulting graph is very usable. However, when using some of the
other BSDFs, the shading networks generated by the existing MaterialX
framework in Blender generate some shading graphs that are difficult for
usdview and other DCC's to understand. The graph is still correct, but
because we're trying to prioritize compatibility, the default is off.
In future PRs we can aim to make the graphs for those other BSDFs play
better with other DCCs.
Other Implementation Details:
As part of this commit we've also done the following:
* Place some of the materialx graphs inside a passthrough nodegraph to
avoid node conflicts.
* Better handle some shader output types , and better handle some
conflict cases.
* Moved the ExportTextureFunction to materials.h due to some difficult
to resolve header ordering issues. This has no effect on any runtime code.
* There is a test for the MaterialX export that does some basic checking to
make sure we get an export out the other end that matches our expectations
Authored by Apple: Dhruv Govil
This PR is based on an earlier implementation by Brecht van Lommel , as well
as Brian Savery and his teams' work at AMD to implement the general
MaterialX framework within Blender.
Pull Request: https://projects.blender.org/blender/blender/pulls/122575
2024-06-05 20:43:44 +02:00
|
|
|
if (type() == Type::SurfaceShader) {
|
|
|
|
|
auto output_name = item.node->getName() + "_out";
|
|
|
|
|
|
|
|
|
|
auto output = graph_->getOutput(output_name);
|
|
|
|
|
if (!output) {
|
2024-12-31 23:39:20 +01:00
|
|
|
output = graph_->addOutput(output_name, item.node->getType());
|
USD: Add MaterialX shader export
This change adds the ability to export MaterialX networks into the resulting
USD layer.
Details:
A new export option has been added to the USD export to enable MaterialX
export. It is off by default currently due to reasons in the caveats
section.
When enabled, it exports the MaterialX shading network alongside the
UsdPreviewSurface network, on the same USD Material. This allows the same
material to be used by renderers that don't support MaterialX, using the
USDPreviewSurface as a fallback. This is similar to setups in other DCC
packages, and matches the format we've used in our Reality Composer Pro
asset library.
It uses the existing MaterialX framework used to generate MaterialX
documents for rendering, to act as the basis for the USD graph. In this
process it also re-uses the existing texture export code as well if provided
and necessary.
Once the MaterialX document is created, use usdMtlx to generate a USD
shading network. Unfortunately, usdMtlx generates a graph that is unlike
what other DCCs that support MaterialX-embedded-in-USD generates. It
generates several extra prim hierarchies, and externalizes all shader
inputs, making them difficult to edit in other MaterialX graph editors.
To workaround this, generate the MaterialX shading network onto a
temporary stage, where we then run various pre-processing steps to prevent
prim collisions and to reflow the paths once they're converted.
The PrimSpecs are then copied over to their new path. The resulting prim
hierarchy matches what many artists we've worked with prefer to work with.
Caveats:
The Export MaterialX check is off by default. When using the Principled
BSDF, the resulting graph is very usable. However, when using some of the
other BSDFs, the shading networks generated by the existing MaterialX
framework in Blender generate some shading graphs that are difficult for
usdview and other DCC's to understand. The graph is still correct, but
because we're trying to prioritize compatibility, the default is off.
In future PRs we can aim to make the graphs for those other BSDFs play
better with other DCCs.
Other Implementation Details:
As part of this commit we've also done the following:
* Place some of the materialx graphs inside a passthrough nodegraph to
avoid node conflicts.
* Better handle some shader output types , and better handle some
conflict cases.
* Moved the ExportTextureFunction to materials.h due to some difficult
to resolve header ordering issues. This has no effect on any runtime code.
* There is a test for the MaterialX export that does some basic checking to
make sure we get an export out the other end that matches our expectations
Authored by Apple: Dhruv Govil
This PR is based on an earlier implementation by Brecht van Lommel , as well
as Brian Savery and his teams' work at AMD to implement the general
MaterialX framework within Blender.
Pull Request: https://projects.blender.org/blender/blender/pulls/122575
2024-06-05 20:43:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output->setConnectedNode(item.node);
|
|
|
|
|
node->setConnectedOutput(in_name, output);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
node->setConnectedNode(in_name, item.node);
|
|
|
|
|
}
|
2023-09-25 18:36:42 +02:00
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::add_output(const std::string &out_name, Type out_type)
|
|
|
|
|
{
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
res.input->setType(type(item_type));
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::create_output(const std::string &name, const NodeItem &item) const
|
|
|
|
|
{
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
res.output = graph_->addOutput(name);
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
Type t1 = item1.type();
|
|
|
|
|
Type t2 = item2.type();
|
|
|
|
|
if (t1 == t2) {
|
|
|
|
|
return t1;
|
|
|
|
|
}
|
|
|
|
|
if (!is_arithmetic(t1) || !is_arithmetic(t2)) {
|
|
|
|
|
CLOG_WARN(
|
|
|
|
|
LOG_MATERIALX_SHADER, "Can't adjust types: %s <-> %s", type(t1).c_str(), type(t2).c_str());
|
|
|
|
|
return Type::Empty;
|
|
|
|
|
}
|
|
|
|
|
if (t1 < t2) {
|
|
|
|
|
item1 = item1.convert(t2);
|
|
|
|
|
return t2;
|
|
|
|
|
}
|
2025-01-20 11:37:08 +01:00
|
|
|
item2 = item2.convert(t1);
|
|
|
|
|
return t1;
|
2023-09-25 18:36:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NodeItem::is_arithmetic() const
|
|
|
|
|
{
|
|
|
|
|
return is_arithmetic(type());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::arithmetic(const std::string &category, std::function<float(float)> func) const
|
|
|
|
|
{
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
Type type = this->type();
|
|
|
|
|
BLI_assert(is_arithmetic(type));
|
|
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case Type::Float: {
|
|
|
|
|
float v = value->asA<float>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<float>(func(v));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color3: {
|
|
|
|
|
auto v = value->asA<MaterialX::Color3>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color3>(
|
|
|
|
|
{func(v[0]), func(v[1]), func(v[2])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color4: {
|
|
|
|
|
auto v = value->asA<MaterialX::Color4>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color4>(
|
|
|
|
|
{func(v[0]), func(v[1]), func(v[2]), func(v[3])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector2: {
|
|
|
|
|
auto v = value->asA<MaterialX::Vector2>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector2>({func(v[0]), func(v[1])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector3: {
|
|
|
|
|
auto v = value->asA<MaterialX::Vector3>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector3>(
|
|
|
|
|
{func(v[0]), func(v[1]), func(v[2])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector4: {
|
|
|
|
|
auto v = value->asA<MaterialX::Vector4>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector4>(
|
|
|
|
|
{func(v[0]), func(v[1]), func(v[2]), func(v[3])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
res = create_node(category, type, {{"in", *this}});
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NodeItem NodeItem::arithmetic(const NodeItem &other,
|
|
|
|
|
const std::string &category,
|
|
|
|
|
std::function<float(float, float)> func,
|
|
|
|
|
Type to_type) const
|
|
|
|
|
{
|
|
|
|
|
NodeItem res = empty();
|
|
|
|
|
NodeItem item1 = *this;
|
|
|
|
|
NodeItem item2 = other;
|
|
|
|
|
to_type = (to_type == Type::Any) ? cast_types(item1, item2) : to_type;
|
|
|
|
|
if (to_type == Type::Empty) {
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value && other.value) {
|
|
|
|
|
switch (to_type) {
|
|
|
|
|
case Type::Float: {
|
|
|
|
|
float v1 = item1.value->asA<float>();
|
|
|
|
|
float v2 = item2.value->asA<float>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<float>(func(v1, v2));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color3: {
|
|
|
|
|
auto v1 = item1.value->asA<MaterialX::Color3>();
|
|
|
|
|
auto v2 = item2.value->asA<MaterialX::Color3>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color3>(
|
|
|
|
|
{func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Color4: {
|
|
|
|
|
auto v1 = item1.value->asA<MaterialX::Color4>();
|
|
|
|
|
auto v2 = item2.value->asA<MaterialX::Color4>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Color4>(
|
|
|
|
|
{func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2]), func(v1[3], v2[3])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector2: {
|
|
|
|
|
auto v1 = item1.value->asA<MaterialX::Vector2>();
|
|
|
|
|
auto v2 = item2.value->asA<MaterialX::Vector2>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector2>(
|
|
|
|
|
{func(v1[0], v2[0]), func(v1[1], v2[1])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector3: {
|
|
|
|
|
auto v1 = item1.value->asA<MaterialX::Vector3>();
|
|
|
|
|
auto v2 = item2.value->asA<MaterialX::Vector3>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector3>(
|
|
|
|
|
{func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Type::Vector4: {
|
|
|
|
|
auto v1 = item1.value->asA<MaterialX::Vector4>();
|
|
|
|
|
auto v2 = item2.value->asA<MaterialX::Vector4>();
|
|
|
|
|
res.value = MaterialX::Value::createValue<MaterialX::Vector4>(
|
|
|
|
|
{func(v1[0], v2[0]), func(v1[1], v2[1]), func(v1[2], v2[2]), func(v1[3], v2[3])});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-02-11 17:16:54 +01:00
|
|
|
#if !(MATERIALX_MAJOR_VERSION <= 1 && MATERIALX_MINOR_VERSION <= 38)
|
|
|
|
|
if (category == "atan2") {
|
|
|
|
|
res = create_node(category, to_type, {{"iny", item1}, {"inx", item2}});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
res = create_node(category, to_type, {{"in1", item1}, {"in2", item2}});
|
|
|
|
|
}
|
2023-09-25 18:36:42 +02:00
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace blender::nodes::materialx
|