USD: enable material displacement support
This enables material displacement for UsdPreviewSurface import and export. Scenarios are limited by what's supported by the preview surface itself. Namely only Object Space displacement can be used (no vector displacement)[1] and the Midlevel and Scale parameters are maintained by adjusting the scale-bias on the image texture controlling the Height (this means that Midlevel and Scale must be constants). Hydra/MaterialX support is more complicated. First, there is a bug which prevents scalar displacment from working correctly and that needs USD 2408+ for the fix[2]. Second, is that there's an open question about which coordinate system to use for MaterialX's vector displacement maps. Lastly, Hydra GL does not render displacement, making verification using only Blender impossible[3]. As a result, this PR only makes MaterialX "ready" for support, but stops short of actually connecting the final piece of the node graph until more of the above can be sorted out. Tests are added which cover: - Variations of Midlevel and Scale values - A constant Height setup - Negative scenarios checking that only Object space is supported and that midlevel and scale need to be constants [1] https://openusd.org/release/spec_usdpreviewsurface.html [2] https://github.com/PixarAnimationStudios/OpenUSD/issues/3325 [3] https://forum.aousd.org/t/materialx-displacement-hydra-storm/1098/2 Pull Request: https://projects.blender.org/blender/blender/pulls/128909
This commit is contained in:
committed by
Jesse Yurkovich
parent
0598db079d
commit
b4c2feea38
@@ -251,6 +251,53 @@ class USDExportTest(AbstractUSDTest):
|
||||
geom_subsets = UsdGeom.Subset.GetGeomSubsets(dynamic_mesh_prim)
|
||||
self.assertEqual(len(geom_subsets), 0)
|
||||
|
||||
def test_export_material_displacement(self):
|
||||
"""Validate correct export of Displacement information for the UsdPreviewSurface"""
|
||||
|
||||
# Use the common materials .blend file
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_materials_displace.blend"))
|
||||
export_path = self.tempdir / "material_displace.usda"
|
||||
self.export_and_validate(filepath=str(export_path), export_materials=True)
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
|
||||
# Verify "constant" displacement
|
||||
shader_surface = UsdShade.Shader(stage.GetPrimAtPath("/root/_materials/constant/Principled_BSDF"))
|
||||
self.assertEqual(shader_surface.GetIdAttr().Get(), "UsdPreviewSurface")
|
||||
input_displacement = shader_surface.GetInput('displacement')
|
||||
self.assertEqual(input_displacement.HasConnectedSource(), False, "Displacement input should not be connected")
|
||||
self.assertAlmostEqual(input_displacement.Get(), 0.45, 5)
|
||||
|
||||
# Validate various Midlevel and Scale scenarios
|
||||
def validate_displacement(mat_name, expected_scale, expected_bias):
|
||||
shader_surface = UsdShade.Shader(stage.GetPrimAtPath(f"/root/_materials/{mat_name}/Principled_BSDF"))
|
||||
shader_image = UsdShade.Shader(stage.GetPrimAtPath(f"/root/_materials/{mat_name}/Image_Texture"))
|
||||
self.assertEqual(shader_surface.GetIdAttr().Get(), "UsdPreviewSurface")
|
||||
self.assertEqual(shader_image.GetIdAttr().Get(), "UsdUVTexture")
|
||||
input_displacement = shader_surface.GetInput('displacement')
|
||||
input_colorspace = shader_image.GetInput('sourceColorSpace')
|
||||
input_scale = shader_image.GetInput('scale')
|
||||
input_bias = shader_image.GetInput('bias')
|
||||
self.assertEqual(input_displacement.HasConnectedSource(), True, "Displacement input should be connected")
|
||||
self.assertEqual(input_colorspace.Get(), 'raw')
|
||||
self.assertEqual(self.round_vector(input_scale.Get()), expected_scale)
|
||||
self.assertEqual(self.round_vector(input_bias.Get()), expected_bias)
|
||||
|
||||
validate_displacement("mid_0_0", [1.0, 1.0, 1.0, 1.0], [0, 0, 0, 0])
|
||||
validate_displacement("mid_0_5", [1.0, 1.0, 1.0, 1.0], [-0.5, -0.5, -0.5, 0])
|
||||
validate_displacement("mid_1_0", [1.0, 1.0, 1.0, 1.0], [-1, -1, -1, 0])
|
||||
validate_displacement("mid_0_0_scale_0_3", [0.3, 0.3, 0.3, 1.0], [0, 0, 0, 0])
|
||||
validate_displacement("mid_0_5_scale_0_3", [0.3, 0.3, 0.3, 1.0], [-0.15, -0.15, -0.15, 0])
|
||||
validate_displacement("mid_1_0_scale_0_3", [0.3, 0.3, 0.3, 1.0], [-0.3, -0.3, -0.3, 0])
|
||||
|
||||
# Validate that no displacement occurs for scenarios USD doesn't support
|
||||
shader_surface = UsdShade.Shader(stage.GetPrimAtPath(f"/root/_materials/bad_wrong_space/Principled_BSDF"))
|
||||
input_displacement = shader_surface.GetInput('displacement')
|
||||
self.assertTrue(input_displacement.Get() is None)
|
||||
shader_surface = UsdShade.Shader(stage.GetPrimAtPath(f"/root/_materials/bad_non_const/Principled_BSDF"))
|
||||
input_displacement = shader_surface.GetInput('displacement')
|
||||
self.assertTrue(input_displacement.Get() is None)
|
||||
|
||||
def check_primvar(self, prim, pv_name, pv_typeName, pv_interp, elements_len):
|
||||
pv = UsdGeom.PrimvarsAPI(prim).GetPrimvar(pv_name)
|
||||
self.assertTrue(pv.HasValue())
|
||||
|
||||
@@ -295,6 +295,54 @@ class USDImportTest(AbstractUSDTest):
|
||||
face_indices = [i for i, d in enumerate(material_index_attr.data) if d.value == mat_index]
|
||||
self.assertEqual(len(face_indices), 4, f"Incorrect number of faces with material index {mat_index}")
|
||||
|
||||
def test_import_material_displacement(self):
|
||||
"""Validate correct import of Displacement information for the UsdPreviewSurface"""
|
||||
|
||||
# Use the existing materials test file to create the USD file
|
||||
# for import. It is validated as part of the bl_usd_export test.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_materials_displace.blend"))
|
||||
testfile = str(self.tempdir / "temp_material_displace.usda")
|
||||
res = bpy.ops.wm.usd_export(filepath=str(testfile), export_materials=True)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to export to {testfile}")
|
||||
|
||||
# Reload the empty file and import back in
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
|
||||
res = bpy.ops.wm.usd_import(filepath=testfile)
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {testfile}")
|
||||
|
||||
# Most shader graph validation should occur through the Hydra render test suite. Here we
|
||||
# will only check some high-level criteria for each expected node graph.
|
||||
|
||||
def assert_displacement(mat, height, midlevel, scale):
|
||||
nodes = mat.node_tree.nodes
|
||||
node_displace_index = nodes.find("Displacement")
|
||||
self.assertTrue(node_displace_index >= 0)
|
||||
|
||||
node_displace = nodes[node_displace_index]
|
||||
if height is not None:
|
||||
self.assertAlmostEqual(node_displace.inputs[0].default_value, height)
|
||||
else:
|
||||
self.assertEqual(len(node_displace.inputs[0].links), 1)
|
||||
self.assertAlmostEqual(node_displace.inputs[1].default_value, midlevel)
|
||||
self.assertAlmostEqual(node_displace.inputs[2].default_value, scale)
|
||||
|
||||
mat = bpy.data.materials["constant"]
|
||||
assert_displacement(mat, 0.95, 0.5, 1.0)
|
||||
|
||||
mat = bpy.data.materials["mid_1_0"]
|
||||
assert_displacement(mat, None, 1.0, 1.0)
|
||||
mat = bpy.data.materials["mid_0_5"]
|
||||
assert_displacement(mat, None, 0.5, 1.0)
|
||||
mat = bpy.data.materials["mid_0_0"]
|
||||
assert_displacement(mat, None, 0.0, 1.0)
|
||||
|
||||
mat = bpy.data.materials["mid_1_0_scale_0_3"]
|
||||
assert_displacement(mat, None, 1.0, 0.3)
|
||||
mat = bpy.data.materials["mid_0_5_scale_0_3"]
|
||||
assert_displacement(mat, None, 0.5, 0.3)
|
||||
mat = bpy.data.materials["mid_0_0_scale_0_3"]
|
||||
assert_displacement(mat, None, 0.0, 0.3)
|
||||
|
||||
def test_import_shader_varname_with_connection(self):
|
||||
"""Test importing USD shader where uv primvar is a connection"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user