Fix: USD: Traverse through UsdShadeNodeGraph nodes during material import

Follow connections through `UsdShadeNodeGraph` nodes when reading
materials rather than looking only for `UsdShadeShader` nodes.

Found while investigating the Intel Sponza research sample:
https://www.intel.com/content/www/us/en/developer/topic-technology/graphics-research/samples.html

Pull Request: https://projects.blender.org/blender/blender/pulls/140565
This commit is contained in:
Jesse Yurkovich
2025-06-18 20:25:29 +02:00
committed by Jesse Yurkovich
parent a7f9ad5af6
commit bcd5af34f9
3 changed files with 142 additions and 5 deletions

View File

@@ -918,6 +918,33 @@ static void configure_displacement(const pxr::UsdShadeShader &usd_shader, bNode
((bNodeSocketValueFloat *)sock_scale->default_value)->value = scale_avg;
}
static pxr::UsdShadeShader node_graph_output_source(const pxr::UsdShadeNodeGraph &node_graph,
const pxr::TfToken &output_name)
{
// Check that we have a legit output
pxr::UsdShadeOutput output = node_graph.GetOutput(output_name);
if (!output) {
return pxr::UsdShadeShader();
}
pxr::UsdShadeAttributeVector attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(output);
if (attrs.empty()) {
return pxr::UsdShadeShader();
}
pxr::UsdAttribute attr = attrs[0];
std::pair<pxr::TfToken, pxr::UsdShadeAttributeType> name_and_type =
pxr::UsdShadeUtils::GetBaseNameAndType(attr.GetName());
pxr::UsdShadeShader shader(attr.GetPrim());
if (name_and_type.second != pxr::UsdShadeAttributeType::Output || !shader) {
return pxr::UsdShadeShader();
}
return shader;
}
bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
bNode *dest_node,
const StringRefNull dest_socket_name,
@@ -936,11 +963,19 @@ bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
usd_input.GetConnectedSource(&source, &source_name, &source_type);
if (!(source && source.GetPrim().IsA<pxr::UsdShadeShader>())) {
if (!source) {
return false;
}
pxr::UsdShadeShader source_shader(source.GetPrim());
const pxr::UsdPrim source_prim = source.GetPrim();
pxr::UsdShadeShader source_shader;
if (source_prim.IsA<pxr::UsdShadeShader>()) {
source_shader = pxr::UsdShadeShader(source_prim);
}
else if (source_prim.IsA<pxr::UsdShadeNodeGraph>()) {
pxr::UsdShadeNodeGraph node_graph(source_prim);
source_shader = node_graph_output_source(node_graph, source_name);
}
if (!source_shader) {
return false;
@@ -948,9 +983,9 @@ bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
pxr::TfToken shader_id;
if (!source_shader.GetShaderId(&shader_id)) {
CLOG_ERROR(&LOG,
"Couldn't get shader id for source shader %s",
source_shader.GetPath().GetAsString().c_str());
CLOG_WARN(&LOG,
"Couldn't get shader id for source shader %s",
source_shader.GetPath().GetAsString().c_str());
return false;
}

View File

@@ -0,0 +1,91 @@
#usda 1.0
(
defaultPrim = "root"
doc = "Blender v4.5.0 Beta"
metersPerUnit = 1
upAxis = "Z"
)
def Xform "root" (
customData = {
dictionary Blender = {
bool generated = 1
}
}
)
{
def Xform "Cube"
{
def Mesh "Cube" (
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, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1]
rel material:binding = </root/_materials/Material>
normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (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, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] (
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.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5)] (
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "none"
}
}
def Scope "_materials"
{
def Material "Material"
{
token outputs:surface.connect = </root/_materials/Material/Principled_BSDF.outputs:surface>
def Shader "Principled_BSDF"
{
uniform token info:id = "UsdPreviewSurface"
float inputs:clearcoat = 0
float inputs:clearcoatRoughness = 0.03
color3f inputs:diffuseColor.connect = </root/_materials/Material/node_graph_BaseColor.outputs:rgb>
float inputs:ior = 1.45
float inputs:metallic = 0
float inputs:opacity = 1
float inputs:roughness = 0.5
float inputs:specular = 0.5
token outputs:surface
}
def NodeGraph "node_graph_BaseColor" (
prepend references = </root/_materials/node_graph_BaseColor>
)
{
}
}
def NodeGraph "node_graph_BaseColor"
{
string inputs:frame:st = "st"
float3 outputs:rgb.connect = </root/_materials/node_graph_BaseColor/node_graph_BaseColor.outputs:rgb>
def Shader "node_graph_BaseColor"
{
uniform token info:id = "UsdUVTexture"
asset inputs:file = @./textures/test_single.png@
float2 inputs:st.connect = </root/_materials/node_graph_BaseColor/PrimvarReader_st.outputs:result>
token inputs:wrapS = "repeat"
token inputs:wrapT = "repeat"
float3 outputs:rgb
}
def Shader "PrimvarReader_st"
{
uniform token info:id = "UsdPrimvarReader_float2"
string inputs:varname.connect = </root/_materials/node_graph_BaseColor.inputs:frame:st>
float2 outputs:result
}
}
}
}

View File

@@ -533,6 +533,17 @@ class USDImportTest(AbstractUSDTest):
assert_attribute(mat, "f_vec", "Vector", "Normal")
assert_attribute(mat, "f_float", "Fac", "Roughness")
def test_import_material_node_graph(self):
"""Verify we can follow connections through NodeGraph defs."""
testfile = str(self.testdir / "usd_materials_node_graph.usda")
res = bpy.ops.wm.usd_import(filepath=testfile)
self.assertEqual({'FINISHED'}, res)
# If NodeGraph traversal is missing or broken, the Image Texture and UV Map nodes will be missing
mat = bpy.data.materials["Material"]
self.assert_all_nodes_present(mat, ["Principled BSDF", "Image Texture", "UV Map", "Material Output"])
def test_import_shader_varname_with_connection(self):
"""Test importing USD shader where uv primvar is a connection"""