USD: Use nodes for alpha-clip behavior instead of material properties

EEVEE-next has removed the MA_BM_CLIP / alpha_threshold material
properties in favor of using nodes for equivalent functionality. This
changes USD to build and traverse node graphs during import and export
accordingly. Indirectly this allows Cycles to correctly render such
materials now too.

A complicating factor is that the UsdPreviewSurface defines its opacity
threshold using greater-than-equals[1], which Blender does not support
(and for which was technically already incorrect as EEVEE-legacy only
used greater-than for its shaders). Due to this we actually need to use
2 nodes: A less-than, followed by a one-minus invert, to arrive at the
proper value. We'll translate UsdPreviewSurface to this form on Import.

For Export we will look for either this 2-node pattern or a Round
node plugged into Alpha. Looking for Round is a result of the glTF
documentation which recommended the use of this node for thresholds of
0.5[2]. It's a tiny addition that seems reasonable to accommodate.

[1] https://openusd.org/release/spec_usdpreviewsurface.html (search for "opacityThreshold")
[2] https://docs.blender.org/manual/en/4.2/addons/import_export/scene_gltf2.html#alpha-modes

See PR for example images

Pull Request: https://projects.blender.org/blender/blender/pulls/122025
This commit is contained in:
Jesse Yurkovich
2024-05-25 23:30:13 +02:00
committed by Jesse Yurkovich
parent 21db0daa4e
commit 9daded5d87
4 changed files with 138 additions and 80 deletions

View File

@@ -148,53 +148,23 @@ class USDExportTest(AbstractUSDTest):
opacity_input = shader.GetInput('opacity')
self.assertEqual(opacity_input.HasConnectedSource(), False,
"Opacity input should not be connected for opaque material")
self.assertAlmostEqual(opacity_input.Get(), 1.0, "Opacity input should be set to 1")
self.assertAlmostEqual(opacity_input.Get(), 1.0, 2, "Opacity input should be set to 1")
# The material already has a texture input to the Base Color.
# Now also link this texture to the Alpha input.
# Set an opacity threshold appropriate for alpha clipping.
mat = bpy.data.materials['Material']
bsdf = mat.node_tree.nodes['Principled BSDF']
tex_output = bsdf.inputs['Base Color'].links[0].from_node.outputs['Color']
alpha_input = bsdf.inputs['Alpha']
mat.node_tree.links.new(tex_output, alpha_input)
bpy.data.materials['Material'].blend_method = 'CLIP'
bpy.data.materials['Material'].alpha_threshold = 0.01
export_path = self.tempdir / "alphaclip_material.usda"
res = bpy.ops.wm.usd_export(
filepath=str(export_path),
export_materials=True,
evaluation_mode="RENDER",
)
self.assertEqual({'FINISHED'}, res, f"Unable to export to {export_path}")
# Inspect and validate the exported USD for the alpha clip case.
stage = Usd.Stage.Open(str(export_path))
shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF")
# Inspect and validate the exported USD for the alpha clip w/Round node case.
shader_prim = stage.GetPrimAtPath("/root/_materials/Clip_With_Round/Principled_BSDF")
shader = UsdShade.Shader(shader_prim)
opacity_input = shader.GetInput('opacity')
opacity_thres_input = shader.GetInput('opacityThreshold')
opacity_thresh_input = shader.GetInput('opacityThreshold')
self.assertEqual(opacity_input.HasConnectedSource(), True, "Alpha input should be connected")
self.assertGreater(opacity_thres_input.Get(), 0.0, "Opacity threshold input should be > 0")
self.assertAlmostEqual(opacity_thresh_input.Get(), 0.5, 2, "Opacity threshold input should be 0.5")
# Modify material again, this time with alpha blend.
bpy.data.materials['Material'].blend_method = 'BLEND'
export_path = self.tempdir / "alphablend_material.usda"
res = bpy.ops.wm.usd_export(
filepath=str(export_path),
export_materials=True,
evaluation_mode="RENDER",
)
self.assertEqual({'FINISHED'}, res, f"Unable to export to {export_path}")
# Inspect and validate the exported USD for the alpha blend case.
stage = Usd.Stage.Open(str(export_path))
shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF")
# Inspect and validate the exported USD for the alpha clip w/LessThan+Invert node case.
shader_prim = stage.GetPrimAtPath("/root/_materials/Clip_With_LessThanInvert/Principled_BSDF")
shader = UsdShade.Shader(shader_prim)
opacity_input = shader.GetInput('opacity')
opacity_thres_input = shader.GetInput('opacityThreshold')
opacity_thresh_input = shader.GetInput('opacityThreshold')
self.assertEqual(opacity_input.HasConnectedSource(), True, "Alpha input should be connected")
self.assertEqual(opacity_thres_input.Get(), None, "Opacity threshold should not be specified for alpha blend")
self.assertAlmostEqual(opacity_thresh_input.Get(), 0.2, 2, "Opacity threshold input should be 0.2")
def check_primvar(self, prim, pv_name, pv_typeName, pv_interp, elements_len):
pv = UsdGeom.PrimvarsAPI(prim).GetPrimvar(pv_name)