Add Blender v4.5 MaterialX OpenPBR shader support

Implement support for ND_open_pbr_surface_surfaceshader exported by Blender v4.5
when using MaterialX. This enables proper parsing and conversion of OpenPBR
materials from Blender-exported USD files.

Key changes:
- Add MtlxOpenPBRSurface struct with all Blender v4.5 parameters in usdMtlx.hh
- Implement ReconstructShader specialization for MtlxOpenPBRSurface in prim-reconstruct.cc
- Add MaterialXConfigAPI detection and OpenPBR shader attachment in render-data.cc
- Implement DumpOpenPBRSurface for material output in render-scene-dump.cc
- Add pretty-print support for MtlxOpenPBRSurface in pprinter.cc

When MaterialXConfigAPI is present on a Material, the system now:
1. Attempts to parse outputs:mtlx:surface connection
2. Falls back to searching for child Shader prims with OpenPBR info:id
3. Converts MtlxOpenPBRSurface to OpenPBRSurface for rendering

Tested with models/cube-materialx.usda and models/suzanne-materialx.usda

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2025-11-05 10:43:04 +09:00
parent 82ec2b3c84
commit 9798cf142c
8 changed files with 958 additions and 11 deletions

174
models/cube-materialx.usda Executable file
View File

@@ -0,0 +1,174 @@
#usda 1.0
(
defaultPrim = "root"
doc = "Blender v4.5.4 LTS"
metersPerUnit = 1
upAxis = "Z"
)
def Xform "root" (
customData = {
dictionary Blender = {
bool generated = 1
}
}
)
{
def Xform "Cube"
{
custom string userProperties:blender:object_name = "Cube"
def Mesh "Cube_001" (
active = true
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
uniform bool doubleSided = 1
float3[] extent = [(-1, -1, -1), (1, 1, 1)]
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 7, 6, 6, 7, 5, 4, 4, 5, 1, 0, 2, 6, 4, 0, 7, 3, 1, 5]
rel material:binding = </root/_materials/Material_003>
normal3f[] normals = [(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] (
interpolation = "faceVarying"
)
point3f[] points = [(-1, -1, -1), (-1, -1, 1), (-1, 1, -1), (-1, 1, 1), (1, -1, -1), (1, -1, 1), (1, 1, -1), (1, 1, 1)]
texCoord2f[] primvars:st = [(0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75)] (
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "none"
custom string userProperties:blender:data_name = "Cube.001"
}
}
def Scope "_materials"
{
def Material "Material_003" (
prepend apiSchemas = ["MaterialXConfigAPI"]
)
{
string config:mtlx:version = "1.39"
token outputs:mtlx:surface.connect = </root/_materials/Material_003/Principled_BSDF_mtlx1.outputs:surface>
token outputs:surface.connect = </root/_materials/Material_003/Principled_BSDF.outputs:surface>
custom string userProperties:blender:data_name = "Material.003"
def Shader "Principled_BSDF"
{
uniform token info:id = "UsdPreviewSurface"
float inputs:clearcoat = 0
float inputs:clearcoatRoughness = 0.03
color3f inputs:diffuseColor = (0.8, 0.8, 0.8)
float inputs:ior = 1.5
float inputs:metallic = 0
float inputs:opacity = 1
float inputs:roughness = 0.5
float inputs:specular = 0.5
token outputs:surface
}
def Shader "Principled_BSDF_mtlx1"
{
uniform token info:id = "ND_open_pbr_surface_surfaceshader"
color3f inputs:base_color = (0.8, 0.8, 0.8)
float inputs:base_diffuse_roughness = 0
float inputs:base_metalness = 0
float inputs:base_weight = 1
color3f inputs:coat_color = (1, 1, 1)
float inputs:coat_darkening
float inputs:coat_ior = 1.5
float inputs:coat_roughness = 0.03
float inputs:coat_roughness_anisotropy
float inputs:coat_weight = 0
color3f inputs:emission_color = (1, 1, 1)
float inputs:emission_luminance = 0
color3f inputs:fuzz_color = (1, 1, 1)
float inputs:fuzz_roughness = 0.5
float inputs:fuzz_weight = 0
float3 inputs:geometry_coat_normal
float3 inputs:geometry_coat_tangent
float3 inputs:geometry_normal
float inputs:geometry_opacity = 1
float3 inputs:geometry_tangent.connect = </root/_materials/Material_003/NodeGraphs.outputs:node_003_out>
bool inputs:geometry_thin_walled
color3f inputs:specular_color = (1, 1, 1)
float inputs:specular_ior = 1.5
float inputs:specular_roughness = 0.5
float inputs:specular_roughness_anisotropy = 0
float inputs:specular_weight = 1
color3f inputs:subsurface_color = (0.8, 0.8, 0.8)
float inputs:subsurface_radius = 0.05
color3f inputs:subsurface_radius_scale = (1, 0.2, 0.1)
float inputs:subsurface_scatter_anisotropy = 0
float inputs:subsurface_weight = 0
float inputs:thin_film_ior = 1.33
float inputs:thin_film_thickness = 0
float inputs:thin_film_weight = 0
color3f inputs:transmission_color = (0.8, 0.8, 0.8)
float inputs:transmission_depth
float inputs:transmission_dispersion_abbe_number
float inputs:transmission_dispersion_scale
color3f inputs:transmission_scatter
float inputs:transmission_scatter_anisotropy
float inputs:transmission_weight = 0
token outputs:surface
}
def NodeGraph "NodeGraphs"
{
float3 outputs:node_003_out.connect = </root/_materials/Material_003/NodeGraphs/node_003.outputs:out>
def Shader "node"
{
uniform token info:id = "ND_normal_vector3"
string inputs:space = "world"
float3 outputs:out
}
def Shader "node_001"
{
uniform token info:id = "ND_normalize_vector3"
float3 inputs:in.connect = </root/_materials/Material_003/NodeGraphs/node.outputs:out>
float3 outputs:out
}
def Shader "node_002"
{
uniform token info:id = "ND_tangent_vector3"
string inputs:space = "world"
float3 outputs:out
}
def Shader "node_003"
{
uniform token info:id = "ND_normalize_vector3"
float3 inputs:in.connect = </root/_materials/Material_003/NodeGraphs/node_002.outputs:out>
float3 outputs:out
}
def Shader "node_004"
{
uniform token info:id = "ND_rotate3d_vector3"
float inputs:amount = -90
float3 inputs:axis.connect = </root/_materials/Material_003/NodeGraphs/node_001.outputs:out>
float3 inputs:in.connect = </root/_materials/Material_003/NodeGraphs/node_003.outputs:out>
float3 outputs:out
}
def Shader "node_005"
{
uniform token info:id = "ND_normalize_vector3"
float3 inputs:in.connect = </root/_materials/Material_003/NodeGraphs/node_004.outputs:out>
float3 outputs:out
}
}
}
}
def DomeLight "env_light"
{
float inputs:intensity = 1
asset inputs:texture:file = @.\textures\color_121212.hdr@
float3 xformOp:rotateXYZ = (90, 1.2722219e-14, 90)
uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
}
}

174
models/suzanne-materialx.usda Executable file

File diff suppressed because one or more lines are too long

BIN
models/suzanne-materialx.usdc Executable file

Binary file not shown.

View File

@@ -14,6 +14,7 @@
#include "str-util.hh"
#include "tiny-format.hh"
#include "usdShade.hh"
#include "usdMtlx.hh"
#include "value-pprint.hh"
#include "timesamples-pprint.hh"
//
@@ -4108,6 +4109,9 @@ std::string to_string(const Shader &shader, const uint32_t indent,
ss << print_shader_params(pvtx2d.value(), indent + 1);
} else if (auto pvs = shader.value.get_value<UsdPreviewSurface>()) {
ss << print_shader_params(pvs.value(), indent + 1);
} else if (auto mtlx_opbr = shader.value.get_value<MtlxOpenPBRSurface>()) {
// Blender v4.5 MaterialX OpenPBR Surface
ss << print_common_shader_params(mtlx_opbr.value(), indent + 1);
} else if (auto pvsn = shader.value.get_value<ShaderNode>()) {
// Generic ShaderNode
ss << print_common_shader_params(pvsn.value(), indent + 1);

View File

@@ -5529,6 +5529,153 @@ bool ReconstructShader<MtlxAutodeskStandardSurface>(
return true;
}
template <>
bool ReconstructShader<MtlxOpenPBRSurface>(
const Specifier &spec,
PropertyMap &properties,
const ReferenceList &references,
MtlxOpenPBRSurface *surface,
std::string *warn,
std::string *err,
const PrimReconstructOptions &options) {
(void)spec;
(void)references;
(void)options;
(void)warn;
std::set<std::string> table;
table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim<Shader>
for (auto &prop : properties) {
// Base properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:base_weight", MtlxOpenPBRSurface,
surface->base_weight)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:base_color", MtlxOpenPBRSurface,
surface->base_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:base_metalness", MtlxOpenPBRSurface,
surface->base_metalness)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:base_diffuse_roughness", MtlxOpenPBRSurface,
surface->base_diffuse_roughness)
// Specular properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_weight", MtlxOpenPBRSurface,
surface->specular_weight)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_color", MtlxOpenPBRSurface,
surface->specular_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_roughness", MtlxOpenPBRSurface,
surface->specular_roughness)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_ior", MtlxOpenPBRSurface,
surface->specular_ior)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_anisotropy", MtlxOpenPBRSurface,
surface->specular_anisotropy)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_rotation", MtlxOpenPBRSurface,
surface->specular_rotation)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular_roughness_anisotropy", MtlxOpenPBRSurface,
surface->specular_roughness_anisotropy)
// Transmission properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_weight", MtlxOpenPBRSurface,
surface->transmission_weight)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_color", MtlxOpenPBRSurface,
surface->transmission_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_depth", MtlxOpenPBRSurface,
surface->transmission_depth)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_scatter", MtlxOpenPBRSurface,
surface->transmission_scatter)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_scatter_anisotropy", MtlxOpenPBRSurface,
surface->transmission_scatter_anisotropy)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_dispersion", MtlxOpenPBRSurface,
surface->transmission_dispersion)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_dispersion_abbe_number", MtlxOpenPBRSurface,
surface->transmission_dispersion_abbe_number)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:transmission_dispersion_scale", MtlxOpenPBRSurface,
surface->transmission_dispersion_scale)
// Subsurface properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_weight", MtlxOpenPBRSurface,
surface->subsurface_weight)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_color", MtlxOpenPBRSurface,
surface->subsurface_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_radius", MtlxOpenPBRSurface,
surface->subsurface_radius)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_radius_scale", MtlxOpenPBRSurface,
surface->subsurface_radius_scale)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_scale", MtlxOpenPBRSurface,
surface->subsurface_scale)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_anisotropy", MtlxOpenPBRSurface,
surface->subsurface_anisotropy)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:subsurface_scatter_anisotropy", MtlxOpenPBRSurface,
surface->subsurface_scatter_anisotropy)
// Coat properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_weight", MtlxOpenPBRSurface,
surface->coat_weight)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_color", MtlxOpenPBRSurface,
surface->coat_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_roughness", MtlxOpenPBRSurface,
surface->coat_roughness)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_anisotropy", MtlxOpenPBRSurface,
surface->coat_anisotropy)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_rotation", MtlxOpenPBRSurface,
surface->coat_rotation)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_roughness_anisotropy", MtlxOpenPBRSurface,
surface->coat_roughness_anisotropy)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_ior", MtlxOpenPBRSurface,
surface->coat_ior)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_darkening", MtlxOpenPBRSurface,
surface->coat_darkening)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_affect_color", MtlxOpenPBRSurface,
surface->coat_affect_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:coat_affect_roughness", MtlxOpenPBRSurface,
surface->coat_affect_roughness)
// Fuzz properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fuzz_weight", MtlxOpenPBRSurface,
surface->fuzz_weight)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fuzz_color", MtlxOpenPBRSurface,
surface->fuzz_color)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fuzz_roughness", MtlxOpenPBRSurface,
surface->fuzz_roughness)
// Thin film properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:thin_film_thickness", MtlxOpenPBRSurface,
surface->thin_film_thickness)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:thin_film_ior", MtlxOpenPBRSurface,
surface->thin_film_ior)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:thin_film_weight", MtlxOpenPBRSurface,
surface->thin_film_weight)
// Emission properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:emission_luminance", MtlxOpenPBRSurface,
surface->emission_luminance)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:emission_color", MtlxOpenPBRSurface,
surface->emission_color)
// Geometry properties
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:geometry_opacity", MtlxOpenPBRSurface,
surface->geometry_opacity)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:geometry_thin_walled", MtlxOpenPBRSurface,
surface->geometry_thin_walled)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:geometry_normal", MtlxOpenPBRSurface,
surface->geometry_normal)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:geometry_tangent", MtlxOpenPBRSurface,
surface->geometry_tangent)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:geometry_coat_normal", MtlxOpenPBRSurface,
surface->geometry_coat_normal)
PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:geometry_coat_tangent", MtlxOpenPBRSurface,
surface->geometry_coat_tangent)
// Output
PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:surface", MtlxOpenPBRSurface,
surface->surface)
ADD_PROPERTY(table, prop, MtlxOpenPBRSurface, surface->props)
PARSE_PROPERTY_END_MAKE_WARN(table, prop)
}
return true;
}
template <>
bool ReconstructShader<OpenPBRSurface>(
const Specifier &spec,
@@ -5848,6 +5995,15 @@ bool ReconstructPrim<Shader>(
}
shader->info_id = kMtlxAutodeskStandardSurface;
shader->value = surface;
} else if (shader_type.compare(kNdOpenPbrSurfaceSurfaceshader) == 0) {
// Blender v4.5 MaterialX OpenPBR Surface export
MtlxOpenPBRSurface surface;
if (!ReconstructShader<MtlxOpenPBRSurface>(spec, properties, references,
&surface, warn, err, options)) {
PUSH_ERROR_AND_RETURN("Failed to Reconstruct " << kNdOpenPbrSurfaceSurfaceshader);
}
shader->info_id = kNdOpenPbrSurfaceSurfaceshader;
shader->value = surface;
} else {
// Reconstruct as generic ShaderNode
ShaderNode surface;

View File

@@ -43,6 +43,7 @@
#include "tinyusdz.hh"
#include "usdGeom.hh"
#include "usdShade.hh"
#include "usdMtlx.hh"
#include "value-pprint.hh"
#include "logger.hh"
#include "bone-util.hh"
@@ -6182,6 +6183,7 @@ bool RenderSceneConverter::ConvertMaterial(const RenderSceneConverterEnv &env,
//
// surface shader
// First try outputs:surface (standard USD), then outputs:mtlx:surface (MaterialX)
{
if (material.surface.authored()) {
auto paths = material.surface.get_connections();
@@ -6233,10 +6235,11 @@ bool RenderSceneConverter::ConvertMaterial(const RenderSceneConverterEnv &env,
shaderPrim->prim_type_name()));
}
// Check for UsdPreviewSurface or OpenPBRSurface
// Check for UsdPreviewSurface, OpenPBRSurface, or MtlxOpenPBRSurface (Blender v4.5+ export)
const UsdPreviewSurface *psurface = shader->value.as<UsdPreviewSurface>();
const OpenPBRSurface *openpbr = shader->value.as<OpenPBRSurface>();
const MtlxOpenPBRSurface *mtlx_openpbr = shader->value.as<MtlxOpenPBRSurface>();
// prop part must be `outputs:surface` for now.
if (surfacePath.prop_part() != "outputs:surface") {
PUSH_ERROR_AND_RETURN(
@@ -6244,7 +6247,7 @@ bool RenderSceneConverter::ConvertMaterial(const RenderSceneConverterEnv &env,
"`outputs:surface`, but got `{}`",
mat_abs_path.full_path_name(), surfacePath.prop_part()));
}
if (psurface) {
// Convert UsdPreviewSurface
PreviewSurfaceShader pss;
@@ -6254,7 +6257,7 @@ bool RenderSceneConverter::ConvertMaterial(const RenderSceneConverterEnv &env,
}
rmat.surfaceShader = pss;
}
if (openpbr) {
// Convert OpenPBRSurface
OpenPBRSurfaceShader openpbr_shader;
@@ -6264,14 +6267,278 @@ bool RenderSceneConverter::ConvertMaterial(const RenderSceneConverterEnv &env,
}
rmat.openPBRShader = openpbr_shader;
}
if (!psurface && !openpbr) {
if (mtlx_openpbr) {
// Convert MtlxOpenPBRSurface (Blender v4.5+ MaterialX export with ND_open_pbr_surface_surfaceshader)
// For now, convert it to OpenPBRSurface format by copying compatible parameters
OpenPBRSurface converted_openpbr;
// Copy base layer properties
converted_openpbr.base_weight = mtlx_openpbr->base_weight;
converted_openpbr.base_color = mtlx_openpbr->base_color;
converted_openpbr.base_roughness = mtlx_openpbr->base_diffuse_roughness;
converted_openpbr.base_metalness = mtlx_openpbr->base_metalness;
// Copy specular properties
converted_openpbr.specular_weight = mtlx_openpbr->specular_weight;
converted_openpbr.specular_color = mtlx_openpbr->specular_color;
converted_openpbr.specular_roughness = mtlx_openpbr->specular_roughness;
converted_openpbr.specular_ior = mtlx_openpbr->specular_ior;
converted_openpbr.specular_anisotropy = mtlx_openpbr->specular_anisotropy;
converted_openpbr.specular_rotation = mtlx_openpbr->specular_rotation;
// Copy transmission properties
converted_openpbr.transmission_weight = mtlx_openpbr->transmission_weight;
converted_openpbr.transmission_color = mtlx_openpbr->transmission_color;
converted_openpbr.transmission_depth = mtlx_openpbr->transmission_depth;
converted_openpbr.transmission_scatter = mtlx_openpbr->transmission_scatter;
converted_openpbr.transmission_scatter_anisotropy = mtlx_openpbr->transmission_scatter_anisotropy;
converted_openpbr.transmission_dispersion = mtlx_openpbr->transmission_dispersion;
// Copy subsurface properties
converted_openpbr.subsurface_weight = mtlx_openpbr->subsurface_weight;
converted_openpbr.subsurface_color = mtlx_openpbr->subsurface_color;
converted_openpbr.subsurface_scale = mtlx_openpbr->subsurface_scale;
converted_openpbr.subsurface_anisotropy = mtlx_openpbr->subsurface_anisotropy;
// Copy coat properties
converted_openpbr.coat_weight = mtlx_openpbr->coat_weight;
converted_openpbr.coat_color = mtlx_openpbr->coat_color;
converted_openpbr.coat_roughness = mtlx_openpbr->coat_roughness;
converted_openpbr.coat_anisotropy = mtlx_openpbr->coat_anisotropy;
converted_openpbr.coat_rotation = mtlx_openpbr->coat_rotation;
converted_openpbr.coat_ior = mtlx_openpbr->coat_ior;
// Note: MtlxOpenPBRSurface has float coat_affect_color, OpenPBRSurface has color3f
// Just skip coat_affect_color conversion for now since types don't match easily
// TODO: Proper type conversion if needed
converted_openpbr.coat_affect_roughness = mtlx_openpbr->coat_affect_roughness;
// Copy emission properties
converted_openpbr.emission_luminance = mtlx_openpbr->emission_luminance;
converted_openpbr.emission_color = mtlx_openpbr->emission_color;
// Copy geometry properties
converted_openpbr.opacity = mtlx_openpbr->geometry_opacity;
// Copy normal and tangent if they have values (TypedAttribute -> TypedAttributeWithFallback)
if (mtlx_openpbr->geometry_normal.has_value()) {
auto normal_val = mtlx_openpbr->geometry_normal.get_value();
if (normal_val) {
converted_openpbr.normal = normal_val.value();
}
}
if (mtlx_openpbr->geometry_tangent.has_value()) {
auto tangent_val = mtlx_openpbr->geometry_tangent.get_value();
if (tangent_val) {
converted_openpbr.tangent = tangent_val.value();
}
}
// Convert to OpenPBRSurfaceShader
OpenPBRSurfaceShader openpbr_shader;
if (!ConvertOpenPBRSurfaceShader(env, surfacePath, converted_openpbr, &openpbr_shader)) {
PUSH_ERROR_AND_RETURN(fmt::format(
"Failed to convert MtlxOpenPBRSurface : {}", surfacePath.prim_part()));
}
rmat.openPBRShader = openpbr_shader;
}
if (!psurface && !openpbr && !mtlx_openpbr) {
PUSH_ERROR_AND_RETURN(
fmt::format("Shader's info:id must be UsdPreviewSurface or OpenPBRSurface, but got {}",
fmt::format("Shader's info:id must be UsdPreviewSurface, OpenPBRSurface, or ND_open_pbr_surface_surfaceshader, but got {}",
shader->info_id));
}
}
//
// Process MaterialX-specific surface shader when MaterialXConfigAPI is present
// When MaterialXConfigAPI is authored, we look for MaterialX shaders
{
// Check if MaterialXConfigAPI is applied (via materialXConfig field)
// For now, we only check the materialXConfig field as apiSchemas checking would need
// proper MaterialXConfigAPI enum support in APISchemas::APIName
bool has_materialx_api = material.materialXConfig.has_value();
PUSH_WARN(fmt::format("Material {}: materialXConfig.has_value = {}",
mat_abs_path.full_path_name(), has_materialx_api));
if (has_materialx_api) {
DCOUT("Material has MaterialXConfigAPI, looking for MaterialX shaders");
PUSH_WARN("Material has MaterialXConfigAPI, looking for MaterialX shaders");
// First try to parse outputs:mtlx:surface connection
Path mtlxSurfacePath;
bool has_mtlx_surface = false;
// Try to find the connection in various forms
for (const auto& prop_name : {"outputs:mtlx:surface.connect", "outputs:mtlx:surface"}) {
auto it = material.props.find(prop_name);
if (it != material.props.end()) {
if (it->second.is_relationship()) {
auto targets = it->second.get_relationTargets();
if (!targets.empty()) {
mtlxSurfacePath = targets[0];
has_mtlx_surface = true;
DCOUT("Found MaterialX surface connection via relationship: " << mtlxSurfacePath);
break;
}
} else if (it->second.is_attribute()) {
// Try to extract path from attribute
auto attr = it->second.get_attribute();
if (auto token_val = attr.get_value<value::token>()) {
std::string path_str = token_val.value().str();
if (!path_str.empty()) {
// Remove brackets if present
if (path_str.front() == '<' && path_str.back() == '>') {
path_str = path_str.substr(1, path_str.size() - 2);
}
// Parse the path
size_t pos = path_str.find(".outputs:");
if (pos != std::string::npos) {
std::string prim_path = path_str.substr(0, pos);
mtlxSurfacePath = Path(prim_path, "");
has_mtlx_surface = true;
DCOUT("Found MaterialX surface connection via token: " << mtlxSurfacePath);
break;
}
}
}
}
}
}
// If direct connection parsing failed, look for child Shader prims with OpenPBR info:id
if (!has_mtlx_surface) {
DCOUT("Direct connection not found, searching for child shaders with OpenPBR info:id");
PUSH_WARN("Direct connection not found, searching for child shaders with OpenPBR info:id");
// Get the material prim from the stage to access its children
const Prim* mat_prim = nullptr;
if (env.stage.find_prim_at_path(mat_abs_path, mat_prim, &err)) {
if (mat_prim) {
// Iterate through children to find OpenPBR shader
for (const auto& child : mat_prim->children()) {
const Shader* shader = child.as<Shader>();
if (shader) {
// Check if this is an OpenPBR shader by its info:id
if (shader->info_id == kNdOpenPbrSurfaceSurfaceshader ||
shader->info_id == "ND_open_pbr_surface_surfaceshader") {
Path child_path = mat_abs_path;
child_path = child_path.append_element(child.element_name());
mtlxSurfacePath = child_path;
has_mtlx_surface = true;
DCOUT("Found OpenPBR shader child: " << child_path);
PUSH_WARN(fmt::format("Found OpenPBR shader child: {}", child_path.full_path_name()));
break;
}
}
}
}
}
}
// Process the found MaterialX shader
if (has_mtlx_surface) {
const Prim *mtlxShaderPrim{nullptr};
if (!env.stage.find_prim_at_path(
Path(mtlxSurfacePath.prim_part(), /* prop part */ ""), mtlxShaderPrim,
&err)) {
PUSH_WARN(fmt::format(
"MaterialX shader path {} not found in stage",
mtlxSurfacePath.full_path_name()));
} else if (mtlxShaderPrim) {
const Shader *mtlxShader = mtlxShaderPrim->as<Shader>();
if (mtlxShader) {
// Check if it's an OpenPBR shader
const MtlxOpenPBRSurface *mtlx_openpbr = mtlxShader->value.as<MtlxOpenPBRSurface>();
if (mtlx_openpbr) {
DCOUT("Converting MtlxOpenPBRSurface to RenderMaterial");
// Convert MtlxOpenPBRSurface to OpenPBRSurface
OpenPBRSurface converted_openpbr;
// Copy base layer properties
converted_openpbr.base_weight = mtlx_openpbr->base_weight;
converted_openpbr.base_color = mtlx_openpbr->base_color;
converted_openpbr.base_roughness = mtlx_openpbr->base_diffuse_roughness;
converted_openpbr.base_metalness = mtlx_openpbr->base_metalness;
// Copy specular properties
converted_openpbr.specular_weight = mtlx_openpbr->specular_weight;
converted_openpbr.specular_color = mtlx_openpbr->specular_color;
converted_openpbr.specular_roughness = mtlx_openpbr->specular_roughness;
converted_openpbr.specular_ior = mtlx_openpbr->specular_ior;
converted_openpbr.specular_anisotropy = mtlx_openpbr->specular_anisotropy;
converted_openpbr.specular_rotation = mtlx_openpbr->specular_rotation;
// Copy transmission properties
converted_openpbr.transmission_weight = mtlx_openpbr->transmission_weight;
converted_openpbr.transmission_color = mtlx_openpbr->transmission_color;
converted_openpbr.transmission_depth = mtlx_openpbr->transmission_depth;
converted_openpbr.transmission_scatter = mtlx_openpbr->transmission_scatter;
converted_openpbr.transmission_scatter_anisotropy = mtlx_openpbr->transmission_scatter_anisotropy;
converted_openpbr.transmission_dispersion = mtlx_openpbr->transmission_dispersion;
// Copy subsurface properties
converted_openpbr.subsurface_weight = mtlx_openpbr->subsurface_weight;
converted_openpbr.subsurface_color = mtlx_openpbr->subsurface_color;
converted_openpbr.subsurface_scale = mtlx_openpbr->subsurface_scale;
converted_openpbr.subsurface_anisotropy = mtlx_openpbr->subsurface_anisotropy;
// Copy coat properties
converted_openpbr.coat_weight = mtlx_openpbr->coat_weight;
converted_openpbr.coat_color = mtlx_openpbr->coat_color;
converted_openpbr.coat_roughness = mtlx_openpbr->coat_roughness;
converted_openpbr.coat_anisotropy = mtlx_openpbr->coat_anisotropy;
converted_openpbr.coat_rotation = mtlx_openpbr->coat_rotation;
converted_openpbr.coat_ior = mtlx_openpbr->coat_ior;
converted_openpbr.coat_affect_roughness = mtlx_openpbr->coat_affect_roughness;
// Copy emission properties
converted_openpbr.emission_luminance = mtlx_openpbr->emission_luminance;
converted_openpbr.emission_color = mtlx_openpbr->emission_color;
// Copy geometry properties
converted_openpbr.opacity = mtlx_openpbr->geometry_opacity;
// Copy normal and tangent if they have values
if (mtlx_openpbr->geometry_normal.has_value()) {
auto normal_val = mtlx_openpbr->geometry_normal.get_value();
if (normal_val) {
converted_openpbr.normal = normal_val.value();
}
}
if (mtlx_openpbr->geometry_tangent.has_value()) {
auto tangent_val = mtlx_openpbr->geometry_tangent.get_value();
if (tangent_val) {
converted_openpbr.tangent = tangent_val.value();
}
}
// Convert to OpenPBRSurfaceShader
OpenPBRSurfaceShader openpbr_shader;
if (!ConvertOpenPBRSurfaceShader(env, mtlxSurfacePath, converted_openpbr, &openpbr_shader)) {
PUSH_WARN(fmt::format(
"Failed to convert MtlxOpenPBRSurface : {}", mtlxSurfacePath.prim_part()));
} else {
rmat.openPBRShader = openpbr_shader;
DCOUT("Successfully attached MaterialX OpenPBR shader to RenderMaterial");
PUSH_WARN(fmt::format("Successfully attached MaterialX OpenPBR shader to RenderMaterial: {}",
mtlxSurfacePath.full_path_name()));
}
} else {
PUSH_WARN(fmt::format(
"Found shader {} but it's not ND_open_pbr_surface_surfaceshader (got {})",
mtlxSurfacePath.prim_part(), mtlxShader->info_id));
}
}
}
} else {
DCOUT("No MaterialX OpenPBR shader found for material with MaterialXConfigAPI");
}
}
}
DCOUT("Converted Material: " << mat_abs_path);
(*rmat_out) = rmat;

View File

@@ -437,6 +437,151 @@ std::string DumpCamera(const RenderCamera &camera, uint32_t indent) {
return ss.str();
}
std::string DumpOpenPBRSurface(const OpenPBRSurfaceShader &shader,
uint32_t indent) {
std::stringstream ss;
ss << "OpenPBRSurfaceShader {\n";
// Base layer
ss << pprint::Indent(indent + 1) << "base_weight = ";
if (shader.base_weight.is_texture()) {
ss << "texture_id[" << shader.base_weight.texture_id << "]";
} else {
ss << shader.base_weight.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "base_color = ";
if (shader.base_color.is_texture()) {
ss << "texture_id[" << shader.base_color.texture_id << "]";
} else {
ss << shader.base_color.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "base_roughness = ";
if (shader.base_roughness.is_texture()) {
ss << "texture_id[" << shader.base_roughness.texture_id << "]";
} else {
ss << shader.base_roughness.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "base_metalness = ";
if (shader.base_metalness.is_texture()) {
ss << "texture_id[" << shader.base_metalness.texture_id << "]";
} else {
ss << shader.base_metalness.value;
}
ss << "\n";
// Specular layer
ss << pprint::Indent(indent + 1) << "specular_weight = ";
if (shader.specular_weight.is_texture()) {
ss << "texture_id[" << shader.specular_weight.texture_id << "]";
} else {
ss << shader.specular_weight.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "specular_color = ";
if (shader.specular_color.is_texture()) {
ss << "texture_id[" << shader.specular_color.texture_id << "]";
} else {
ss << shader.specular_color.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "specular_roughness = ";
if (shader.specular_roughness.is_texture()) {
ss << "texture_id[" << shader.specular_roughness.texture_id << "]";
} else {
ss << shader.specular_roughness.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "specular_ior = ";
if (shader.specular_ior.is_texture()) {
ss << "texture_id[" << shader.specular_ior.texture_id << "]";
} else {
ss << shader.specular_ior.value;
}
ss << "\n";
// Coat layer
ss << pprint::Indent(indent + 1) << "coat_weight = ";
if (shader.coat_weight.is_texture()) {
ss << "texture_id[" << shader.coat_weight.texture_id << "]";
} else {
ss << shader.coat_weight.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "coat_color = ";
if (shader.coat_color.is_texture()) {
ss << "texture_id[" << shader.coat_color.texture_id << "]";
} else {
ss << shader.coat_color.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "coat_roughness = ";
if (shader.coat_roughness.is_texture()) {
ss << "texture_id[" << shader.coat_roughness.texture_id << "]";
} else {
ss << shader.coat_roughness.value;
}
ss << "\n";
// Emission
ss << pprint::Indent(indent + 1) << "emission_luminance = ";
if (shader.emission_luminance.is_texture()) {
ss << "texture_id[" << shader.emission_luminance.texture_id << "]";
} else {
ss << shader.emission_luminance.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "emission_color = ";
if (shader.emission_color.is_texture()) {
ss << "texture_id[" << shader.emission_color.texture_id << "]";
} else {
ss << shader.emission_color.value;
}
ss << "\n";
// Transmission
ss << pprint::Indent(indent + 1) << "transmission_weight = ";
if (shader.transmission_weight.is_texture()) {
ss << "texture_id[" << shader.transmission_weight.texture_id << "]";
} else {
ss << shader.transmission_weight.value;
}
ss << "\n";
// Subsurface
ss << pprint::Indent(indent + 1) << "subsurface_weight = ";
if (shader.subsurface_weight.is_texture()) {
ss << "texture_id[" << shader.subsurface_weight.texture_id << "]";
} else {
ss << shader.subsurface_weight.value;
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "subsurface_color = ";
if (shader.subsurface_color.is_texture()) {
ss << "texture_id[" << shader.subsurface_color.texture_id << "]";
} else {
ss << shader.subsurface_color.value;
}
ss << "\n";
ss << pprint::Indent(indent) << "}";
return ss.str();
}
std::string DumpPreviewSurface(const PreviewSurfaceShader &shader,
uint32_t indent) {
std::stringstream ss;
@@ -535,7 +680,7 @@ std::string DumpPreviewSurface(const PreviewSurfaceShader &shader,
}
ss << "\n";
ss << pprint::Indent(indent) << "}\n";
ss << pprint::Indent(indent) << "}";
return ss.str();
}
@@ -559,6 +704,14 @@ std::string DumpMaterial(const RenderMaterial &material, uint32_t indent) {
}
ss << "\n";
ss << pprint::Indent(indent + 1) << "openPBRShader = ";
if (material.openPBRShader.has_value()) {
ss << DumpOpenPBRSurface(*material.openPBRShader, indent + 1);
} else {
ss << "null";
}
ss << "\n";
ss << pprint::Indent(indent) << "}\n";
return ss.str();

View File

@@ -29,6 +29,9 @@ constexpr auto kMtlxUsdPreviewSurface = "MtlxUsdPreviewSurface";
constexpr auto kMtlxAutodeskStandardSurface = "MtlxAutodeskStandardSurface";
constexpr auto kMtlxOpenPBRSurface = "MtlxOpenPBRSurface";
// MaterialX node definition IDs (as used in info:id attribute)
constexpr auto kNdOpenPbrSurfaceSurfaceshader = "ND_open_pbr_surface_surfaceshader";
namespace mtlx {
@@ -101,6 +104,7 @@ struct MtlxOpenPBRSurface : ShaderNode {
TypedAttributeWithFallback<Animatable<float>> specular_ior{1.5f};
TypedAttributeWithFallback<Animatable<float>> specular_anisotropy{0.0f};
TypedAttributeWithFallback<Animatable<float>> specular_rotation{0.0f};
TypedAttributeWithFallback<Animatable<float>> specular_roughness_anisotropy{0.0f};
// Transmission properties
TypedAttributeWithFallback<Animatable<float>> transmission_weight{0.0f};
@@ -111,15 +115,19 @@ struct MtlxOpenPBRSurface : ShaderNode {
value::color3f{0.0f, 0.0f, 0.0f}};
TypedAttributeWithFallback<Animatable<float>> transmission_scatter_anisotropy{0.0f};
TypedAttributeWithFallback<Animatable<float>> transmission_dispersion{0.0f};
TypedAttributeWithFallback<Animatable<float>> transmission_dispersion_abbe_number{0.0f};
TypedAttributeWithFallback<Animatable<float>> transmission_dispersion_scale{0.0f};
// Subsurface properties
TypedAttributeWithFallback<Animatable<float>> subsurface_weight{0.0f};
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_color{
value::color3f{0.8f, 0.8f, 0.8f}};
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_radius{
value::color3f{1.0f, 1.0f, 1.0f}};
TypedAttributeWithFallback<Animatable<float>> subsurface_radius{0.05f}; // Blender uses float, not color3f
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_radius_scale{
value::color3f{1.0f, 0.2f, 0.1f}};
TypedAttributeWithFallback<Animatable<float>> subsurface_scale{1.0f};
TypedAttributeWithFallback<Animatable<float>> subsurface_anisotropy{0.0f};
TypedAttributeWithFallback<Animatable<float>> subsurface_scatter_anisotropy{0.0f};
// Coat properties
TypedAttributeWithFallback<Animatable<float>> coat_weight{0.0f};
@@ -128,13 +136,22 @@ struct MtlxOpenPBRSurface : ShaderNode {
TypedAttributeWithFallback<Animatable<float>> coat_roughness{0.1f};
TypedAttributeWithFallback<Animatable<float>> coat_anisotropy{0.0f};
TypedAttributeWithFallback<Animatable<float>> coat_rotation{0.0f};
TypedAttributeWithFallback<Animatable<float>> coat_roughness_anisotropy{0.0f};
TypedAttributeWithFallback<Animatable<float>> coat_ior{1.6f};
TypedAttributeWithFallback<Animatable<float>> coat_darkening{0.0f};
TypedAttributeWithFallback<Animatable<float>> coat_affect_color{0.0f};
TypedAttributeWithFallback<Animatable<float>> coat_affect_roughness{0.0f};
// Fuzz properties (fabric/cloth layer)
TypedAttributeWithFallback<Animatable<float>> fuzz_weight{0.0f};
TypedAttributeWithFallback<Animatable<value::color3f>> fuzz_color{
value::color3f{1.0f, 1.0f, 1.0f}};
TypedAttributeWithFallback<Animatable<float>> fuzz_roughness{0.5f};
// Thin film properties
TypedAttributeWithFallback<Animatable<float>> thin_film_thickness{0.0f};
TypedAttributeWithFallback<Animatable<float>> thin_film_ior{1.5f};
TypedAttributeWithFallback<Animatable<float>> thin_film_weight{0.0f};
// Emission properties
TypedAttributeWithFallback<Animatable<float>> emission_luminance{0.0f};
@@ -148,9 +165,11 @@ struct MtlxOpenPBRSurface : ShaderNode {
// Normal and tangent
TypedAttribute<Animatable<value::normal3f>> geometry_normal;
TypedAttribute<Animatable<value::vector3f>> geometry_tangent;
TypedAttribute<Animatable<value::normal3f>> geometry_coat_normal;
TypedAttribute<Animatable<value::vector3f>> geometry_coat_tangent;
// Output
TypedTerminalAttribute<value::token> out; // 'out'
TypedTerminalAttribute<value::token> surface; // 'outputs:surface'
};
// https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx