USD: Improved Texture Coordinate Translation (UsdTransform2d) #114821

Merged
Michael Kowalski merged 7 commits from CharlesWardlaw/blender:feature/port_mapping_to_main into main 2023-11-27 17:18:56 +01:00
6 changed files with 419 additions and 83 deletions

View File

@ -105,7 +105,8 @@ void MaterialData::init()
else
#endif
{
usd_material = usd::create_usd_material(export_context, material_path, (Material *)id, "st");
usd_material = usd::create_usd_material(
export_context, material_path, (Material *)id, "st", nullptr);
}
/* Convert USD material to Hydra material network map, adapted for render delegate. */

View File

@ -13,6 +13,7 @@
#include "BKE_material.h"
#include "BKE_node.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_report.h"
#include "BLI_fileops.h"
#include "BLI_math_vector.h"
@ -22,6 +23,8 @@
#include "DNA_material_types.h"
#include "WM_api.hh"
#include <pxr/base/gf/vec3f.h>
#include <pxr/usd/ar/packageUtils.h>
#include <pxr/usd/usdShade/material.h>
@ -42,6 +45,7 @@ static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal)
static const pxr::TfToken file("file", pxr::TfToken::Immortal);
static const pxr::TfToken g("g", pxr::TfToken::Immortal);
static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
static const pxr::TfToken in("in", pxr::TfToken::Immortal);
static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
static const pxr::TfToken occlusion("occlusion", pxr::TfToken::Immortal);
@ -61,11 +65,24 @@ static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
/* Wrap mode names. */
static const pxr::TfToken black("black", pxr::TfToken::Immortal);
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
CharlesWardlaw marked this conversation as resolved
Review

These wrap mode values don't seem to be used in the reader.

These wrap mode values don't seem to be used in the reader.
Review

Good catch-- on the port I missed a function. Thanks!

Good catch-- on the port I missed a function. Thanks!
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
/* Transform 2d names. */
static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal);
static const pxr::TfToken scale("scale", pxr::TfToken::Immortal);
static const pxr::TfToken translation("translation", pxr::TfToken::Immortal);
/* USD shader names. */
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
pxr::TfToken::Immortal);
static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal);
static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal);
} // namespace usdtokens
/* Temporary folder for saving imported textures prior to packing.
@ -265,6 +282,40 @@ static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader
return pxr::TfToken();
}
static int get_image_extension(const pxr::UsdShadeShader &usd_shader, const int default_value)
{
pxr::UsdShadeInput wrap_input = usd_shader.GetInput(usdtokens::wrapS);
if (!wrap_input) {
wrap_input = usd_shader.GetInput(usdtokens::wrapT);
}
if (!wrap_input) {
return default_value;
}
pxr::VtValue wrap_input_val;
if (!(wrap_input.Get(&wrap_input_val) && wrap_input_val.IsHolding<pxr::TfToken>())) {
return default_value;
}
pxr::TfToken wrap_val = wrap_input_val.Get<pxr::TfToken>();
if (wrap_val == usdtokens::repeat) {
return SHD_IMAGE_EXTENSION_REPEAT;
}
if (wrap_val == usdtokens::clamp) {
return SHD_IMAGE_EXTENSION_EXTEND;
}
if (wrap_val == usdtokens::black) {
return SHD_IMAGE_EXTENSION_CLIP;
}
return default_value;
}
/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source
* of the given material. Returns true if a UsdPreviewSurface source was found
* and returns false otherwise. */
@ -326,6 +377,42 @@ static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader
}
}
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken &input_name)
{
pxr::UsdShadeInput input = usd_shader.GetInput(input_name);
/* Check if the shader's input is connected to another source,
* and use that instead if so. */
if (input) {
for (const pxr::UsdShadeConnectionSourceInfo &source_info : input.GetConnectedSources()) {
pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
pxr::UsdShadeInput secondary_input = shader.GetInput(source_info.sourceName);
if (secondary_input) {
input = secondary_input;
break;
}
}
}
return input;
}
static bNodeSocket *get_input_socket(bNode *node, const char *identifier, ReportList *reports)
{
bNodeSocket *sock = nodeFindSocket(node, SOCK_IN, identifier);
if (!sock) {
BKE_reportf(reports,
RPT_ERROR,
"%s: Error: Couldn't get input socket %s for node %s",
__func__,
identifier,
node->idname);
}
return sock;
}
namespace blender::io::usd {
namespace {
@ -617,11 +704,9 @@ bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
return false;
}
/* For now, only convert UsdUVTexture and UsdPrimvarReader_float2 inputs. */
/* For now, only convert UsdUVTexture, UsdTransform2d and UsdPrimvarReader_float2 inputs. */
if (shader_id == usdtokens::UsdUVTexture) {
if (STREQ(dest_socket_name, "Normal")) {
/* The normal texture input requires creating a normal map node. */
float locx = 0.0f;
float locy = 0.0f;
@ -648,6 +733,10 @@ bool USDMaterialReader::follow_connection(const pxr::UsdShadeInput &usd_input,
convert_usd_primvar_reader_float2(
source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx);
}
else if (shader_id == usdtokens::UsdTransform2d) {
convert_usd_transform_2d(
source_shader, source_name, dest_node, dest_socket_name, ntree, column + 1, r_ctx);
}
return true;
}
@ -700,6 +789,85 @@ void USDMaterialReader::convert_usd_uv_texture(const pxr::UsdShadeShader &usd_sh
}
}
void USDMaterialReader::convert_usd_transform_2d(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken &usd_source_name,
bNode *dest_node,
const char *dest_socket_name,
CharlesWardlaw marked this conversation as resolved
Review

As someone new to the USD API, I've noticed there's like 5 different coding conventions when it comes to getting values:

if (Attr.Get(val) && val.IsHolding<T>()) { ... val.Get<T>() }
if (Attr.Get(val) && val.IsHolding<T>()) { ... val.UncheckedGet<T>() }
if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().Get<T>() }
if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().UncheckedGet<T>() }
if (Attr.Get(val)) { ... val.Get<T>() }

Is it possible to choose just 1 style? In what situations are the Casts necessary? Can UncheckedGet always be used since we verify in some form first?

As someone new to the USD API, I've noticed there's like 5 different coding conventions when it comes to getting values: ``` if (Attr.Get(val) && val.IsHolding<T>()) { ... val.Get<T>() } if (Attr.Get(val) && val.IsHolding<T>()) { ... val.UncheckedGet<T>() } if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().Get<T>() } if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().UncheckedGet<T>() } if (Attr.Get(val)) { ... val.Get<T>() } ``` Is it possible to choose just 1 style? In what situations are the Casts necessary? Can `UncheckedGet` always be used since we verify in some form first?
Review

God, casts are necessary all over the place. >_<;

One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast.

I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this.

God, casts are necessary all over the place. >_<; One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast. I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this.
Review

God, casts are necessary all over the place. >_<;

One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast.

I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this.

I agree casts are often necessary, because in practice one finds that attributes are not always consistently authored, or for backward compatibility when the USD specification has changed. That said, we can perhaps open a separate task to review the existing USD import code and decide on a more consistent convention throughout.

> God, casts are necessary all over the place. >_<; > > One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast. > > I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this. I agree casts are often necessary, because in practice one finds that attributes are not always consistently authored, or for backward compatibility when the USD specification has changed. That said, we can perhaps open a separate task to review the existing USD import code and decide on a more consistent convention throughout.
bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx) const
{
if (!usd_shader || !dest_node || !ntree || !dest_socket_name || !bmain_ || !r_ctx) {
return;
}
bNode *mapping = get_cached_node(r_ctx->node_cache, usd_shader);
if (mapping == nullptr) {
float locx = 0.0f;
float locy = 0.0f;
compute_node_loc(column, &locx, &locy, r_ctx);
/* Create the MAPPING node. */
mapping = add_node(nullptr, ntree, SH_NODE_MAPPING, locx, locy);
if (!mapping) {
BKE_reportf(reports(),
RPT_WARNING,
"%s: Couldn't create SH_NODE_MAPPING for node input %s",
__func__,
dest_socket_name);
return;
}
/* Cache newly created node. */
cache_node(r_ctx->node_cache, usd_shader, mapping);
mapping->custom1 = TEXMAP_TYPE_POINT;
if (bNodeSocket *scale_socket = get_input_socket(mapping, "Scale", reports())) {
if (pxr::UsdShadeInput scale_input = get_input(usd_shader, usdtokens::scale)) {
pxr::VtValue val;
if (scale_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
pxr::GfVec2f scale_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();
float scale[3] = {scale_val[0], scale_val[1], 1.0f};
copy_v3_v3(((bNodeSocketValueVector *)scale_socket->default_value)->value, scale);
}
}
}
if (bNodeSocket *loc_socket = get_input_socket(mapping, "Location", reports())) {
if (pxr::UsdShadeInput trans_input = get_input(usd_shader, usdtokens::translation)) {
pxr::VtValue val;
if (trans_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
pxr::GfVec2f trans_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();
float loc[3] = {trans_val[0], trans_val[1], 0.0f};
copy_v3_v3(((bNodeSocketValueVector *)loc_socket->default_value)->value, loc);
}
}
}
if (bNodeSocket *rot_socket = get_input_socket(mapping, "Rotation", reports())) {
if (pxr::UsdShadeInput rot_input = get_input(usd_shader, usdtokens::rotation)) {
pxr::VtValue val;
if (rot_input.Get(&val) && val.CanCast<float>()) {
float rot_val = val.Cast<float>().UncheckedGet<float>() * M_PI / 180.0f;
float rot[3] = {0.0f, 0.0f, rot_val};
copy_v3_v3(((bNodeSocketValueVector *)rot_socket->default_value)->value, rot);
}
}
}
}
/* Connect to destination node input. */
link_nodes(ntree, mapping, "Vector", dest_node, dest_socket_name);
/* Connect the mapping node "Vector" input. */
if (pxr::UsdShadeInput in_input = usd_shader.GetInput(usdtokens::in)) {
set_node_input(in_input, mapping, "Vector", ntree, column, r_ctx);
}
}
void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
bNode *tex_image) const
{
@ -800,6 +968,9 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
STRNCPY(image->colorspace_settings.name, "Raw");
}
NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
storage->extension = get_image_extension(usd_shader, storage->extension);
if (import_textures && params_.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image))
{

View File

@ -129,6 +129,14 @@ class USDMaterialReader {
int column,
NodePlacementContext *r_ctx) const;
void convert_usd_transform_2d(const pxr::UsdShadeShader &usd_shader,
const pxr::TfToken &usd_source_name,
bNode *dest_node,
const char *dest_socket_name,
bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx) const;
/**
* Load the texture image node's texture from the path given by the USD shader's
* file input value.

View File

@ -118,7 +118,7 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont
}
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
return create_usd_material(usd_export_context_, usd_path, material, active_uv);
return create_usd_material(usd_export_context_, usd_path, material, active_uv, reports());
}
void USDAbstractWriter::write_visibility(const HierarchyContext &context,

View File

@ -47,6 +47,7 @@ static const pxr::TfToken emissive_color("emissiveColor", pxr::TfToken::Immortal
static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken UsdTransform2d("UsdTransform2d", pxr::TfToken::Immortal);
static const pxr::TfToken uv_texture("UsdUVTexture", pxr::TfToken::Immortal);
static const pxr::TfToken primvar_float2("UsdPrimvarReader_float2", pxr::TfToken::Immortal);
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
@ -74,6 +75,14 @@ static const pxr::TfToken bias("bias", pxr::TfToken::Immortal);
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
static const pxr::TfToken Shader("Shader", pxr::TfToken::Immortal);
static const pxr::TfToken black("black", pxr::TfToken::Immortal);
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
static const pxr::TfToken in("in", pxr::TfToken::Immortal);
CharlesWardlaw marked this conversation as resolved
Review

There is another "emissiveColor" token name defined above already.

There is another "emissiveColor" token name defined above already.
static const pxr::TfToken translation("translation", pxr::TfToken::Immortal);
static const pxr::TfToken rotation("rotation", pxr::TfToken::Immortal);
} // namespace usdtokens
/* Cycles specific tokens. */
@ -104,11 +113,18 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &material,
bNode *node);
static void create_uv_input(const USDExporterContext &usd_export_context,
bNodeSocket *input_socket,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeInput &usd_input,
const pxr::TfToken &default_uv,
ReportList *reports);
static void create_uvmap_shader(const USDExporterContext &usd_export_context,
bNode *tex_node,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeShader &usd_tex_shader,
const pxr::TfToken &default_uv);
const pxr::TfToken &default_uv,
ReportList *reports);
static void export_texture(const USDExporterContext &usd_export_context, bNode *node);
static bNode *find_bsdf_node(Material *material);
static void get_absolute_path(Image *ima, char *r_path);
@ -117,14 +133,27 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
static InputSpecMap &preview_surface_input_map();
static bNodeLink *traverse_channel(bNodeSocket *input, short target_type);
template<typename T1, typename T2>
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value);
void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &input_spec);
/* Create an input on the given shader with name and type
* provided by the InputSpec and assign the given value to the
* input. Parameters T1 and T2 indicate the Blender and USD
* value types, respectively. */
template<typename T1, typename T2>
void create_input(pxr::UsdShadeShader &shader,
const InputSpec &spec,
const void *value,
float scale)
{
const T1 *cast_value = static_cast<const T1 *>(value);
shader.CreateInput(spec.input_name, spec.input_type).Set(scale * T2(cast_value->value));
}
static void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv)
const std::string &default_uv,
ReportList *reports)
{
if (!material) {
return;
@ -157,14 +186,23 @@ static void create_usd_preview_surface_material(const USDExporterContext &usd_ex
continue;
}
/* Allow scaling inputs. */
float scale = 1.0;
const InputSpec &input_spec = it->second;
bNodeLink *input_link = traverse_channel(sock, SH_NODE_TEX_IMAGE);
if (input_spec.input_name == usdtokens::emissive_color) {
/* Don't export emission color if strength is zero. */
bNodeSocket *emission_strength_sock = nodeFindSocket(node, SOCK_IN, "Emission Strength");
if (!input_link &&
((bNodeSocketValueFloat *)emission_strength_sock->default_value)->value == 0.0f) {
if (!emission_strength_sock) {
continue;
}
scale = ((bNodeSocketValueFloat *)emission_strength_sock->default_value)->value;
if (scale == 0.0f) {
continue;
}
CharlesWardlaw marked this conversation as resolved Outdated

This seems like another case where we'd, maybe, want to know we didn't find the socket? In general not being able to find a socket for one of our own nodes is totally an error on our part, nothing the user can do about it, but we don't have a "Retail assert" right now so tracing is the only option... I'm torn between a) silently continuing, b) tracing and continuing, or c) just use a Debug assert for folks running debug and crash with null-ref in retail.

This seems like another case where we'd, maybe, want to know we didn't find the socket? In general not being able to find a socket for one of our own nodes is totally an error on our part, nothing the user can do about it, but we don't have a "Retail assert" right now so tracing is the only option... I'm torn between a) silently continuing, b) tracing and continuing, or c) just use a Debug assert for folks running debug and crash with null-ref in retail.

This kind of missing socket stuff occurs either with broken .blend files or, sometimes, when loading stuff between versions and upgrades or downgrades don't work 100%. I don't think normal users who stick to official releases will hit something like this.

The version of Blender we publish through our launcher, however, is constantly running on top of main. We at times ingest bugs that are the result of refactors or other upstream changes, but are not always able to ingest the subsequent fixes as quickly.

I am 200% against allowing the application to get into a crash state knowingly, especially when a simple pointer check can avoid this. I feel it's better for the user to have bad behavior and recover from it than to suddenly be unable to open a file and lose all the data contained within.

I'm happy to add a report print here if you like.

This kind of missing socket stuff occurs either with broken .blend files or, sometimes, when loading stuff between versions and upgrades or downgrades don't work 100%. I don't think normal users who stick to official releases will hit something like this. The version of Blender we publish through our launcher, however, is constantly running on top of main. We at times ingest bugs that are the result of refactors or other upstream changes, but are not always able to ingest the subsequent fixes as quickly. I am 200% against allowing the application to get into a crash state knowingly, especially when a simple pointer check can avoid this. I feel it's better for the user to have bad behavior and recover from it than to suddenly be unable to open a file and lose all the data contained within. I'm happy to add a report print here if you like.
}
@ -201,9 +239,15 @@ static void create_usd_preview_surface_material(const USDExporterContext &usd_ex
export_texture(usd_export_context, input_node);
}
/* Look for a connected uv node. */
create_uvmap_shader(
usd_export_context, input_node, usd_material, usd_shader, default_uv_sampler);
/* Look for a connected uvmap node. */
if (bNodeSocket *socket = nodeFindSocket(input_node, SOCK_IN, "Vector")) {
if (pxr::UsdShadeInput st_input = usd_shader.CreateInput(usdtokens::st,
pxr::SdfValueTypeNames->Float2))
{
create_uv_input(
usd_export_context, socket, usd_material, st_input, default_uv_sampler, reports);
}
}
set_normal_texture_range(usd_shader, input_spec);
@ -218,22 +262,20 @@ static void create_usd_preview_surface_material(const USDExporterContext &usd_ex
}
else if (input_spec.set_default_value) {
/* Set hardcoded value. */
switch (sock->type) {
case SOCK_FLOAT: {
create_input<bNodeSocketValueFloat, float>(
preview_surface, input_spec, sock->default_value);
break;
}
preview_surface, input_spec, sock->default_value, scale);
} break;
case SOCK_VECTOR: {
create_input<bNodeSocketValueVector, pxr::GfVec3f>(
preview_surface, input_spec, sock->default_value);
break;
}
preview_surface, input_spec, sock->default_value, scale);
} break;
case SOCK_RGBA: {
create_input<bNodeSocketValueRGBA, pxr::GfVec3f>(
preview_surface, input_spec, sock->default_value);
break;
}
preview_surface, input_spec, sock->default_value, scale);
} break;
default:
break;
}
@ -320,82 +362,153 @@ static InputSpecMap &preview_surface_input_map()
return input_map;
}
/* Create an input on the given shader with name and type
* provided by the InputSpec and assign the given value to the
* input. Parameters T1 and T2 indicate the Blender and USD
* value types, respectively. */
template<typename T1, typename T2>
void create_input(pxr::UsdShadeShader &shader, const InputSpec &spec, const void *value)
{
const T1 *cast_value = static_cast<const T1 *>(value);
shader.CreateInput(spec.input_name, spec.input_type).Set(T2(cast_value->value));
}
/* Find the UVMAP node input to the given texture image node and convert it
* to a USD primvar reader shader. If no UVMAP node is found, create a primvar
* reader for the given default uv set. The primvar reader will be attached to
* the 'st' input of the given USD texture shader. */
static void create_uvmap_shader(const USDExporterContext &usd_export_context,
bNode *tex_node,
bNodeLink *uvmap_link,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeShader &usd_tex_shader,
const pxr::TfToken &default_uv)
pxr::UsdShadeInput &usd_input,
const pxr::TfToken &default_uv,
ReportList *reports)
{
bool found_uv_node = false;
bNode *uv_node = (uvmap_link && uvmap_link->fromnode ? uvmap_link->fromnode : nullptr);
/* Find UV input to the texture node. */
LISTBASE_FOREACH (bNodeSocket *, tex_node_sock, &tex_node->inputs) {
BLI_assert(!uv_node || uv_node->type == SH_NODE_UVMAP);
if (!tex_node_sock->link || !STREQ(tex_node_sock->name, "Vector")) {
continue;
const char *shader_name = uv_node ? uv_node->name : "uvmap";
pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
usd_export_context, usd_material, shader_name, SH_NODE_UVMAP);
if (!uv_shader) {
BKE_reportf(reports, RPT_WARNING, "%s: Couldn't create USD shader for UV map", __func__);
return;
}
pxr::TfToken uv_name = default_uv;
if (uv_node && uv_node->storage) {
NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(uv_node->storage);
/* We need to make valid here because actual uv primvar has been. */
uv_name = pxr::TfToken(pxr::TfMakeValidIdentifier(shader_uv_map->uv_map));
}
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(uv_name);
usd_input.ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
}
static void create_transform2d_shader(const USDExporterContext &usd_export_context,
bNodeLink *mapping_link,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeInput &usd_input,
const pxr::TfToken &default_uv,
ReportList *reports)
{
bNode *mapping_node = (mapping_link && mapping_link->fromnode ? mapping_link->fromnode :
nullptr);
BLI_assert(mapping_node && mapping_node->type == SH_NODE_MAPPING);
if (!mapping_node) {
return;
}
if (mapping_node->custom1 != TEXMAP_TYPE_POINT) {
if (bNodeSocket *socket = nodeFindSocket(mapping_node, SOCK_IN, "Vector")) {
create_uv_input(usd_export_context, socket, usd_material, usd_input, default_uv, reports);
}
return;
}
bNodeLink *uv_node_link = traverse_channel(tex_node_sock, SH_NODE_UVMAP);
if (uv_node_link == nullptr) {
continue;
}
pxr::UsdShadeShader transform2d_shader = create_usd_preview_shader(
usd_export_context, usd_material, mapping_node);
pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
usd_export_context, usd_material, uv_node_link->fromnode);
if (!transform2d_shader) {
BKE_reportf(reports, RPT_WARNING, "%s: Couldn't create USD shader for mapping node", __func__);
return;
}
if (!uv_shader.GetPrim().IsValid()) {
continue;
}
usd_input.ConnectToSource(transform2d_shader.ConnectableAPI(), usdtokens::result);
found_uv_node = true;
float scale[3] = {1.0f, 1.0f, 1.0f};
float loc[3] = {0.0f, 0.0f, 0.0f};
float rot[3] = {0.0f, 0.0f, 0.0f};
if (NodeShaderUVMap *shader_uv_map = static_cast<NodeShaderUVMap *>(
uv_node_link->fromnode->storage))
if (bNodeSocket *scale_socket = nodeFindSocket(mapping_node, SOCK_IN, "Scale")) {
copy_v3_v3(scale, ((bNodeSocketValueVector *)scale_socket->default_value)->value);
/* Ignore the Z scale. */
scale[2] = 1.0f;
}
if (bNodeSocket *loc_socket = nodeFindSocket(mapping_node, SOCK_IN, "Location")) {
copy_v3_v3(loc, ((bNodeSocketValueVector *)loc_socket->default_value)->value);
/* Ignore the Z translation. */
loc[2] = 0.0f;
}
if (bNodeSocket *rot_socket = nodeFindSocket(mapping_node, SOCK_IN, "Rotation")) {
copy_v3_v3(rot, ((bNodeSocketValueVector *)rot_socket->default_value)->value);
/* Ignore the X and Y rotations. */
rot[0] = 0.0f;
rot[1] = 0.0f;
}
if (pxr::UsdShadeInput scale_input = transform2d_shader.CreateInput(
usdtokens::scale, pxr::SdfValueTypeNames->Float2))
{
pxr::GfVec2f scale_val(scale[0], scale[1]);
scale_input.Set(scale_val);
}
if (pxr::UsdShadeInput trans_input = transform2d_shader.CreateInput(
usdtokens::translation, pxr::SdfValueTypeNames->Float2))
{
CharlesWardlaw marked this conversation as resolved
Review

Is checking for success during .Set really necessary for these and the ones below? Other .Set calls don't seem to check and these aren't completely untrusted inputs that would fail I think.

Is checking for success during `.Set` really necessary for these and the ones below? Other .Set calls don't seem to check and these aren't completely untrusted inputs that would fail I think.
Review

Spoke with Michael and we agree-- we can remove the checks here, as the block is only opened on successful param creation.

Spoke with Michael and we agree-- we can remove the checks here, as the block is only opened on successful param creation.
pxr::GfVec2f trans_val(loc[0], loc[1]);
trans_input.Set(trans_val);
}
if (pxr::UsdShadeInput rot_input = transform2d_shader.CreateInput(usdtokens::rotation,
pxr::SdfValueTypeNames->Float))
{
/* Convert to degrees. */
float rot_val = rot[2] * 180.0f / M_PI;
rot_input.Set(rot_val);
}
if (bNodeSocket *socket = nodeFindSocket(mapping_node, SOCK_IN, "Vector")) {
if (pxr::UsdShadeInput in_input = transform2d_shader.CreateInput(
usdtokens::in, pxr::SdfValueTypeNames->Float2))
{
/* We need to make valid here because actual uv primvar has been. */
std::string uv_set = pxr::TfMakeValidIdentifier(shader_uv_map->uv_map);
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token)
.Set(pxr::TfToken(uv_set));
usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
}
else {
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
create_uv_input(usd_export_context, socket, usd_material, in_input, default_uv, reports);
}
}
}
if (!found_uv_node) {
/* No UVMAP node was linked to the texture node. However, we generate
* a primvar reader node that specifies the UV set to sample, as some
* DCCs require this. */
pxr::UsdShadeShader uv_shader = create_usd_preview_shader(
usd_export_context, usd_material, "uvmap", SH_NODE_TEX_COORD);
if (uv_shader.GetPrim().IsValid()) {
uv_shader.CreateInput(usdtokens::varname, pxr::SdfValueTypeNames->Token).Set(default_uv);
usd_tex_shader.CreateInput(usdtokens::st, pxr::SdfValueTypeNames->Float2)
.ConnectToSource(uv_shader.ConnectableAPI(), usdtokens::result);
}
static void create_uv_input(const USDExporterContext &usd_export_context,
bNodeSocket *input_socket,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeInput &usd_input,
const pxr::TfToken &default_uv,
ReportList *reports)
{
if (!(usd_material && usd_input)) {
return;
}
if (bNodeLink *mapping_link = traverse_channel(input_socket, SH_NODE_MAPPING)) {
create_transform2d_shader(
usd_export_context, mapping_link, usd_material, usd_input, default_uv, reports);
return;
}
bNodeLink *uvmap_link = traverse_channel(input_socket, SH_NODE_UVMAP);
/* Note that uvmap_link might be null, but create_uv_shader() can handle this case. */
create_uvmap_shader(
usd_export_context, uvmap_link, usd_material, usd_input, default_uv, reports);
}
/* Generate a file name for an in-memory image that doesn't have a
@ -508,6 +621,35 @@ static pxr::TfToken get_node_tex_image_color_space(bNode *node)
return pxr::TfToken();
}
static pxr::TfToken get_node_tex_image_wrap(bNode *node)
{
if (node->type != SH_NODE_TEX_IMAGE) {
return pxr::TfToken();
}
if (node->storage == nullptr) {
return pxr::TfToken();
}
NodeTexImage *tex_image = static_cast<NodeTexImage *>(node->storage);
pxr::TfToken wrap;
switch (tex_image->extension) {
case SHD_IMAGE_EXTENSION_REPEAT:
wrap = usdtokens::repeat;
break;
case SHD_IMAGE_EXTENSION_EXTEND:
wrap = usdtokens::clamp;
break;
case SHD_IMAGE_EXTENSION_CLIP:
CharlesWardlaw marked this conversation as resolved
Review

Leftover debug trace?

Leftover debug trace?
wrap = usdtokens::black;
break;
}
return wrap;
}
/* Search the upstream node links connected to the given socket and return the first occurrence
* of the link connected to the node of the given type. Return null if no such link was found.
* The 'fromnode' and 'fromsock' members of the returned link are guaranteed to be not null. */
@ -561,6 +703,10 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
shader.CreateIdAttr(pxr::VtValue(usdtokens::uv_texture));
break;
}
case SH_NODE_MAPPING: {
shader.CreateIdAttr(pxr::VtValue(usdtokens::UsdTransform2d));
break;
}
case SH_NODE_TEX_COORD:
case SH_NODE_UVMAP: {
shader.CreateIdAttr(pxr::VtValue(usdtokens::primvar_float2));
@ -612,6 +758,12 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token).Set(colorSpace);
}
pxr::TfToken wrap = get_node_tex_image_wrap(node);
if (!wrap.IsEmpty()) {
shader.CreateInput(usdtokens::wrapS, pxr::SdfValueTypeNames->Token).Set(wrap);
shader.CreateInput(usdtokens::wrapT, pxr::SdfValueTypeNames->Token).Set(wrap);
}
return shader;
}
@ -856,13 +1008,15 @@ const pxr::TfToken token_for_input(const char *input_name)
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context,
pxr::SdfPath usd_path,
Material *material,
const std::string &active_uv)
const std::string &active_uv,
ReportList *reports)
{
pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Define(usd_export_context.stage,
usd_path);
if (material->use_nodes && usd_export_context.export_params.generate_preview_surface) {
create_usd_preview_surface_material(usd_export_context, material, usd_material, active_uv);
create_usd_preview_surface_material(
usd_export_context, material, usd_material, active_uv, reports);
}
else {
create_usd_viewport_material(usd_export_context, material, usd_material);

View File

@ -11,6 +11,7 @@
#include <string>
struct Material;
struct ReportList;
namespace blender::io::usd {
@ -24,7 +25,8 @@ struct USDExporterContext;
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context,
pxr::SdfPath usd_path,
Material *material,
const std::string &active_uv);
const std::string &active_uv,
ReportList *reports);
/* Returns a USDPreviewSurface token name for a given Blender shader Socket name,
* or an empty TfToken if the input name is not found in the map. */