Fix #107062: support opacityThreshold when exporting USD
This PR addresses issue “USD export does not respect opacity threshold for clip alpha blend mode #107062” This commit extends the USD Preview Surface material support to author the opacityThreshold attribute of materials on export, when the Alpha Clip blend mode is selected. When authoring alpha cutouts in Blender, one sets the Blend Mode to "Alpha Clip", and the Clip Threshold to some value greater than zero. When this case is detected on export, we now author the opacityThreshold attribute to match the specified clip threshold. Note that opacityThreshold is already handled correctly on import, so this change allows the feature to be fully round-tripped. Co-authored-by: Matt McLin <mmclin@apple.com> Pull Request: https://projects.blender.org/blender/blender/pulls/107149
This commit is contained in:
parent
f59fdc40ec
commit
3c74575dac
|
@ -47,6 +47,7 @@ static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken
|
|||
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken opacity("opacity", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken opacityThreshold("opacityThreshold", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken perspective("perspective", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken orthographic("orthographic", pxr::TfToken::Immortal);
|
||||
|
@ -144,6 +145,8 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
|
|||
|
||||
const InputSpecMap &input_map = preview_surface_input_map();
|
||||
|
||||
bool has_opacity = false;
|
||||
|
||||
/* Set the preview surface inputs. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
|
||||
|
@ -167,6 +170,10 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
|
|||
preview_surface.CreateInput(input_spec.input_name, input_spec.input_type)
|
||||
.ConnectToSource(created_shader.ConnectableAPI(), input_spec.source_name);
|
||||
set_normal_texture_range(created_shader, input_spec);
|
||||
|
||||
if (input_spec.input_name == usdtokens::opacity) {
|
||||
has_opacity = true;
|
||||
}
|
||||
}
|
||||
else if (input_spec.set_default_value) {
|
||||
/* Set hardcoded value. */
|
||||
|
@ -203,6 +210,14 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
|
|||
create_uvmap_shader(
|
||||
usd_export_context, input_node, usd_material, created_shader, default_uv_sampler);
|
||||
}
|
||||
|
||||
/* Set opacityThreshold if an alpha cutout is used. */
|
||||
if (has_opacity) {
|
||||
if ((material->blend_method == MA_BM_CLIP) && (material->alpha_threshold > 0.0)) {
|
||||
pxr::UsdShadeInput opacity_threshold_input = preview_surface.CreateInput(usdtokens::opacityThreshold, pxr::SdfValueTypeNames->Float);
|
||||
opacity_threshold_input.GetAttr().Set(pxr::VtValue(material->alpha_threshold));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &input_spec)
|
||||
|
|
|
@ -8,6 +8,7 @@ import unittest
|
|||
from pxr import Usd
|
||||
from pxr import UsdUtils
|
||||
from pxr import UsdGeom
|
||||
from pxr import UsdShade
|
||||
from pxr import Gf
|
||||
|
||||
import bpy
|
||||
|
@ -124,6 +125,74 @@ class USDExportTest(AbstractUSDTest):
|
|||
Gf.Vec3d(extent[1]), Gf.Vec3d(0.7515701, 0.5500924, 0.9027928)
|
||||
)
|
||||
|
||||
def test_opacity_threshold(self):
|
||||
# Note that the scene file used here is shared with a different test.
|
||||
# Here we assume that it has a Principled BSDF material with
|
||||
# a texture connected to its Base Color input.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_materials_export.blend"))
|
||||
|
||||
export_path = self.tempdir / "opaque_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 opaque blend case.
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF")
|
||||
shader = UsdShade.Shader(shader_prim)
|
||||
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")
|
||||
|
||||
# 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("/_materials/Material/Principled_BSDF")
|
||||
shader = UsdShade.Shader(shader_prim)
|
||||
opacity_input = shader.GetInput('opacity')
|
||||
opacity_thres_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")
|
||||
|
||||
|
||||
# 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("/_materials/Material/Principled_BSDF")
|
||||
shader = UsdShade.Shader(shader_prim)
|
||||
opacity_input = shader.GetInput('opacity')
|
||||
opacity_thres_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")
|
||||
|
||||
def main():
|
||||
global args
|
||||
|
|
Loading…
Reference in New Issue