USD export: Fix incorrect texture alpha mapping. #107022

Merged
Michael Kowalski merged 8 commits from makowalski/blender:usd_preview_surface_alpha_bug into main 2023-05-03 16:13:00 +02:00

View File

@ -55,6 +55,7 @@ static const pxr::TfToken rgb("rgb", pxr::TfToken::Immortal);
static const pxr::TfToken r("r", pxr::TfToken::Immortal); static const pxr::TfToken r("r", pxr::TfToken::Immortal);
static const pxr::TfToken g("g", pxr::TfToken::Immortal); static const pxr::TfToken g("g", pxr::TfToken::Immortal);
static const pxr::TfToken b("b", pxr::TfToken::Immortal); static const pxr::TfToken b("b", pxr::TfToken::Immortal);
static const pxr::TfToken a("a", pxr::TfToken::Immortal);
static const pxr::TfToken st("st", pxr::TfToken::Immortal); static const pxr::TfToken st("st", pxr::TfToken::Immortal);
static const pxr::TfToken result("result", pxr::TfToken::Immortal); static const pxr::TfToken result("result", pxr::TfToken::Immortal);
static const pxr::TfToken varname("varname", pxr::TfToken::Immortal); static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
@ -81,7 +82,6 @@ namespace blender::io::usd {
struct InputSpec { struct InputSpec {
pxr::TfToken input_name; pxr::TfToken input_name;
pxr::SdfValueTypeName input_type; pxr::SdfValueTypeName input_type;
pxr::TfToken source_name;
/* Whether a default value should be set /* Whether a default value should be set
* if the node socket has not input. Usually * if the node socket has not input. Usually
* false for the Normal input. */ * false for the Normal input. */
@ -113,7 +113,7 @@ static std::string get_tex_image_asset_path(bNode *node,
const pxr::UsdStageRefPtr stage, const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params); const USDExportParams &export_params);
static InputSpecMap &preview_surface_input_map(); static InputSpecMap &preview_surface_input_map();
static bNode *traverse_channel(bNodeSocket *input, short target_type); static bNodeLink *traverse_channel(bNodeSocket *input, short target_type);
template<typename T1, typename T2> template<typename T1, typename T2>
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value); void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value);
@ -145,8 +145,6 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
const InputSpecMap &input_map = preview_surface_input_map(); const InputSpecMap &input_map = preview_surface_input_map();
bool has_opacity = false;
/* Set the preview surface inputs. */ /* Set the preview surface inputs. */
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
@ -157,22 +155,54 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
continue; continue;
} }
pxr::UsdShadeShader created_shader;
bNode *input_node = traverse_channel(sock, SH_NODE_TEX_IMAGE);
const InputSpec &input_spec = it->second; const InputSpec &input_spec = it->second;
if (input_node) { if (bNodeLink *input_link = traverse_channel(sock, SH_NODE_TEX_IMAGE)) {
/* Create connection. */ /* Convert the texture image node connected to this input. */
created_shader = create_usd_preview_shader(usd_export_context, usd_material, input_node); bNode *input_node = input_link->fromnode;
pxr::UsdShadeShader usd_shader = create_usd_preview_shader(
usd_export_context, usd_material, input_node);
/* Determine the name of the USD texture node attribute that should be
* connected to this input. */
pxr::TfToken source_name;
if (input_spec.input_type == pxr::SdfValueTypeNames->Float) {
/* If the input is a float, we connect it to either the texture alpha or red channels. */
source_name = strcmp(input_link->fromsock->identifier, "Alpha") == 0 ? usdtokens::a :
usdtokens::r;
}
else {
source_name = usdtokens::rgb;
}
/* Create the preview surface input and connect it to the shader. */
pxr::UsdShadeConnectionSourceInfo source_info(
usd_shader.ConnectableAPI(), source_name, pxr::UsdShadeAttributeType::Output);
preview_surface.CreateInput(input_spec.input_name, input_spec.input_type) preview_surface.CreateInput(input_spec.input_name, input_spec.input_type)
.ConnectToSource(created_shader.ConnectableAPI(), input_spec.source_name); .ConnectToSource(source_info);
set_normal_texture_range(created_shader, input_spec);
if (input_spec.input_name == usdtokens::opacity) { set_normal_texture_range(usd_shader, input_spec);
has_opacity = true;
/* Export the texture, if necessary. */
if (usd_export_context.export_params.export_textures) {
export_texture(input_node,
usd_export_context.stage,
usd_export_context.export_params.overwrite_textures);
}
/* Look for a connected uv node. */
create_uvmap_shader(
usd_export_context, input_node, usd_material, usd_shader, default_uv_sampler);
set_normal_texture_range(usd_shader, input_spec);
/* Set opacityThreshold if an alpha cutout is used. */
if ((input_spec.input_name == usdtokens::opacity) &&
(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));
} }
} }
else if (input_spec.set_default_value) { else if (input_spec.set_default_value) {
@ -194,30 +224,6 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
break; break;
} }
} }
/* If any input texture node has been found, export the texture, if necessary,
* and look for a connected uv node. */
if (!(created_shader && input_node && input_node->type == SH_NODE_TEX_IMAGE)) {
continue;
}
if (usd_export_context.export_params.export_textures) {
export_texture(input_node,
usd_export_context.stage,
usd_export_context.export_params.overwrite_textures);
}
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));
}
} }
} }
@ -282,19 +288,18 @@ void create_usd_viewport_material(const USDExporterContext &usd_export_context,
static InputSpecMap &preview_surface_input_map() static InputSpecMap &preview_surface_input_map()
{ {
static InputSpecMap input_map = { static InputSpecMap input_map = {
{"Base Color", {"Base Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, true}},
{usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}}, {"Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, true}},
{"Color", {usdtokens::diffuse_color, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, true}}, {"Roughness", {usdtokens::roughness, pxr::SdfValueTypeNames->Float, true}},
{"Roughness", {usdtokens::roughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}}, {"Metallic", {usdtokens::metallic, pxr::SdfValueTypeNames->Float, true}},
{"Metallic", {usdtokens::metallic, pxr::SdfValueTypeNames->Float, usdtokens::r, true}}, {"Specular", {usdtokens::specular, pxr::SdfValueTypeNames->Float, true}},
{"Specular", {usdtokens::specular, pxr::SdfValueTypeNames->Float, usdtokens::r, true}}, {"Alpha", {usdtokens::opacity, pxr::SdfValueTypeNames->Float, true}},
{"Alpha", {usdtokens::opacity, pxr::SdfValueTypeNames->Float, usdtokens::r, true}}, {"IOR", {usdtokens::ior, pxr::SdfValueTypeNames->Float, true}},
{"IOR", {usdtokens::ior, pxr::SdfValueTypeNames->Float, usdtokens::r, true}},
/* Note that for the Normal input set_default_value is false. */ /* Note that for the Normal input set_default_value is false. */
{"Normal", {usdtokens::normal, pxr::SdfValueTypeNames->Float3, usdtokens::rgb, false}}, {"Normal", {usdtokens::normal, pxr::SdfValueTypeNames->Float3, false}},
{"Clearcoat", {usdtokens::clearcoat, pxr::SdfValueTypeNames->Float, usdtokens::r, true}}, {"Clearcoat", {usdtokens::clearcoat, pxr::SdfValueTypeNames->Float, true}},
{"Clearcoat Roughness", {"Clearcoat Roughness",
{usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float, usdtokens::r, true}}, {usdtokens::clearcoatRoughness, pxr::SdfValueTypeNames->Float, true}},
}; };
return input_map; return input_map;
@ -330,13 +335,13 @@ static void create_uvmap_shader(const USDExporterContext &usd_export_context,
continue; continue;
} }
bNode *uv_node = traverse_channel(tex_node_sock, SH_NODE_UVMAP); bNodeLink *uv_node_link = traverse_channel(tex_node_sock, SH_NODE_UVMAP);
if (uv_node == nullptr) { if (uv_node_link == nullptr) {
continue; continue;
} }
pxr::UsdShadeShader uv_shader = create_usd_preview_shader( pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
usd_export_context, usd_material, uv_node); usd_export_context, usd_material, uv_node_link->fromnode);
if (!uv_shader.GetPrim().IsValid()) { if (!uv_shader.GetPrim().IsValid()) {
continue; continue;
@ -344,7 +349,9 @@ static void create_uvmap_shader(const USDExporterContext &usd_export_context,
found_uv_node = true; found_uv_node = true;
if (NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(uv_node->storage)) { if (NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(
uv_node_link->fromnode->storage))
{
/* We need to make valid here because actual uv primvar has been. */ /* We need to make valid here because actual uv primvar has been. */
std::string uv_set = pxr::TfMakeValidIdentifier(shader_uv_map->uv_map); std::string uv_set = pxr::TfMakeValidIdentifier(shader_uv_map->uv_map);
@ -484,24 +491,25 @@ static pxr::TfToken get_node_tex_image_color_space(bNode *node)
return pxr::TfToken(); return pxr::TfToken();
} }
/* Search the upstream nodes connected to the given socket and return the first occurrence /* Search the upstream node links connected to the given socket and return the first occurrence
* of the node of the given type. Return null if no node of this type was found. */ * of the link connected to the node of the given type. Return null if no such link was found.
static bNode *traverse_channel(bNodeSocket *input, const short target_type) * The 'fromnode' and 'fromsock' members of the returned link are guaranteed to be not null. */
static bNodeLink *traverse_channel(bNodeSocket *input, const short target_type)
{ {
if (!input->link) { if (!(input->link && input->link->fromnode && input->link->fromsock)) {
return nullptr; return nullptr;
} }
bNode *linked_node = input->link->fromnode; bNode *linked_node = input->link->fromnode;
if (linked_node->type == target_type) { if (linked_node->type == target_type) {
/* Return match. */ /* Return match. */
return linked_node; return input->link;
} }
/* Recursively traverse the linked node's sockets. */ /* Recursively traverse the linked node's sockets. */
LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) { LISTBASE_FOREACH (bNodeSocket *, sock, &linked_node->inputs) {
if (bNode *found_node = traverse_channel(sock, target_type)) { if (bNodeLink *found_link = traverse_channel(sock, target_type)) {
return found_node; return found_link;
} }
} }