Hydra: export USD preview surface for materials #110836

Manually merged
Brecht Van Lommel merged 6 commits from brecht/blender:hydra-preview-surface into main 2023-08-07 16:22:52 +02:00
17 changed files with 179 additions and 136 deletions

View File

@ -306,7 +306,7 @@ void WM_OT_usd_export(wmOperatorType *ot)
"Animation",
"Export all frames in the render frame range, rather than only the current frame");
RNA_def_boolean(
ot->srna, "export_hair", false, "Hair", "Export hair particle systems as USD curves");
ot->srna, "export_hair", true, "Hair", "Export hair particle systems as USD curves");
RNA_def_boolean(
ot->srna, "export_uvmaps", true, "UV Maps", "Include all mesh UV maps in the export");
RNA_def_boolean(ot->srna,

View File

@ -20,16 +20,22 @@
namespace blender::io::hydra {
static std::string get_cache_file(const std::string &file_name, bool mkdir = true)
std::string image_cache_file_path()
{
char dir_path[FILE_MAX];
BLI_path_join(dir_path, sizeof(dir_path), BKE_tempdir_session(), "hydra", "image_cache");
return dir_path;
}
static std::string get_cache_file(const std::string &file_name, bool mkdir = true)
{
std::string dir_path = image_cache_file_path();
if (mkdir) {
BLI_dir_create_recursive(dir_path);
BLI_dir_create_recursive(dir_path.c_str());
}
char file_path[FILE_MAX];
BLI_path_join(file_path, sizeof(file_path), dir_path, file_name.c_str());
BLI_path_join(file_path, sizeof(file_path), dir_path.c_str(), file_name.c_str());
return file_path;
}

View File

@ -12,6 +12,8 @@ struct ImageUser;
namespace blender::io::hydra {
std::string image_cache_file_path();
std::string cache_or_get_image_file(Main *bmain, Scene *Scene, Image *image, ImageUser *iuser);
std::string cache_image_color(float color[4]);

View File

@ -9,6 +9,7 @@
#include <pxr/imaging/hd/material.h>
#include <pxr/imaging/hd/renderDelegate.h>
#include <pxr/imaging/hd/tokens.h>
#include <pxr/usdImaging/usdImaging/materialParamUtils.h>
#include "MEM_guardedalloc.h"
@ -19,16 +20,22 @@
#include "RNA_prototypes.h"
#include "RNA_types.h"
#include "DEG_depsgraph_query.h"
#include "bpy_rna.h"
#include "hydra_scene_delegate.h"
#include "image.h"
#include "intern/usd_exporter_context.h"
#include "intern/usd_writer_material.h"
namespace blender::io::hydra {
MaterialData::MaterialData(HydraSceneDelegate *scene_delegate,
const Material *material,
pxr::SdfPath const &prim_id)
: IdData(scene_delegate, (const ID *)material, prim_id)
: IdData(scene_delegate, &material->id, prim_id)
{
}
@ -36,6 +43,51 @@ void MaterialData::init()
{
ID_LOGN(1, "");
double_sided = (((Material *)id)->blend_flag & MA_BL_CULL_BACKFACE) == 0;
material_network_map_ = pxr::VtValue();
/* Create temporary in memory stage. */
pxr::UsdStageRefPtr stage = pxr::UsdStage::CreateInMemory();
pxr::UsdTimeCode time = pxr::UsdTimeCode::Default();
pxr::SdfPath material_library_path("/_materials");
pxr::SdfPath material_path = material_library_path.AppendChild(
pxr::TfToken(prim_id.GetElementString()));
/* Create USD export content to reuse USD file export code. */
USDExportParams export_params;
export_params.relative_paths = false;
export_params.export_textures = false; /* Don't copy all textures, is slow. */
export_params.evaluation_mode = DEG_get_mode(scene_delegate_->depsgraph);
usd::USDExporterContext export_context{scene_delegate_->bmain,
scene_delegate_->depsgraph,
stage,
material_library_path,
time,
export_params,
image_cache_file_path()};
/* Create USD material. */
pxr::UsdShadeMaterial usd_material = usd::create_usd_material(
export_context, material_path, (Material *)id, "st");
/* Convert USD material to Hydra material network map, adapted for render delegate. */
const pxr::HdRenderDelegate *render_delegate =
scene_delegate_->GetRenderIndex().GetRenderDelegate();
const pxr::TfTokenVector contextVector = render_delegate->GetMaterialRenderContexts();
pxr::TfTokenVector shaderSourceTypes = render_delegate->GetShaderSourceTypes();
pxr::HdMaterialNetworkMap network_map;
if (pxr::UsdShadeShader surface = usd_material.ComputeSurfaceSource(contextVector)) {
pxr::UsdImagingBuildHdMaterialNetworkFromTerminal(surface.GetPrim(),
pxr::HdMaterialTerminalTokens->surface,
shaderSourceTypes,
contextVector,
&network_map,
time);
}
material_network_map_ = pxr::VtValue(network_map);
}
void MaterialData::insert()
@ -76,7 +128,7 @@ pxr::VtValue MaterialData::get_data(pxr::TfToken const & /* key */) const
pxr::VtValue MaterialData::get_material_resource() const
{
return pxr::VtValue();
return material_network_map_;
}
pxr::HdCullStyle MaterialData::cull_style() const

View File

@ -16,6 +16,12 @@
namespace blender::io::hydra {
class MaterialData : public IdData {
public:
bool double_sided = true;
private:
pxr::VtValue material_network_map_;
public:
MaterialData(HydraSceneDelegate *scene_delegate,
const Material *material,
@ -29,8 +35,6 @@ class MaterialData : public IdData {
pxr::VtValue get_data(pxr::TfToken const &key) const override;
pxr::VtValue get_material_resource() const;
pxr::HdCullStyle cull_style() const;
bool double_sided = true;
};
using MaterialDataMap = Map<pxr::SdfPath, std::unique_ptr<MaterialData>>;

View File

@ -43,19 +43,11 @@ USDSceneDelegate::~USDSceneDelegate()
void USDSceneDelegate::populate(Depsgraph *depsgraph)
{
USDExportParams params = {};
params.export_hair = true;
params.export_uvmaps = true;
params.export_normals = true;
params.export_materials = true;
params.selected_objects_only = false;
params.visible_objects_only = true;
USDExportParams params;
params.use_instancing = true;
params.relative_paths = false; /* Unnecessary. */
params.export_textures = false; /* Don't copy all textures, is slow. */
params.evaluation_mode = DEG_get_mode(depsgraph);
params.generate_preview_surface = true;
params.export_textures = true;
params.overwrite_textures = true;
params.relative_paths = true;
/* Create clean directory for export. */
BLI_delete(temp_dir_.c_str(), true, true);

View File

@ -20,8 +20,9 @@ struct USDExporterContext {
Depsgraph *depsgraph;
const pxr::UsdStageRefPtr stage;
const pxr::SdfPath usd_path;
const USDHierarchyIterator *hierarchy_iterator;
pxr::UsdTimeCode time_code;
const USDExportParams &export_params;
std::string export_file_path;
};
} // namespace blender::io::usd

View File

@ -64,20 +64,6 @@ void USDHierarchyIterator::set_export_frame(float frame_nr)
export_time_ = pxr::UsdTimeCode(frame_nr);
}
std::string USDHierarchyIterator::get_export_file_path() const
{
/* Returns the same path that was passed to `stage_` object during it's creation (via
* `pxr::UsdStage::CreateNew` function). */
const pxr::SdfLayerHandle root_layer = stage_->GetRootLayer();
const std::string usd_export_file_path = root_layer->GetRealPath();
return usd_export_file_path;
}
const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
{
return export_time_;
}
USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
{
pxr::SdfPath path;
@ -88,7 +74,13 @@ USDExporterContext USDHierarchyIterator::create_usd_export_context(const Hierarc
path = pxr::SdfPath(context->export_path);
}
return USDExporterContext{bmain_, depsgraph_, stage_, path, this, params_};
/* Returns the same path that was passed to `stage_` object during it's creation (via
* `pxr::UsdStage::CreateNew` function). */
const pxr::SdfLayerHandle root_layer = stage_->GetRootLayer();
const std::string export_file_path = root_layer->GetRealPath();
return USDExporterContext{
bmain_, depsgraph_, stage_, path, export_time_, params_, export_file_path};
}
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(

View File

@ -35,8 +35,6 @@ class USDHierarchyIterator : public AbstractHierarchyIterator {
const USDExportParams &params);
void set_export_frame(float frame_nr);
std::string get_export_file_path() const;
const pxr::UsdTimeCode &get_export_time_code() const;
virtual std::string make_valid_name(const std::string &name) const override;

View File

@ -53,13 +53,13 @@ bool USDAbstractWriter::is_supported(const HierarchyContext * /*context*/) const
std::string USDAbstractWriter::get_export_file_path() const
{
return usd_export_context_.hierarchy_iterator->get_export_file_path();
return usd_export_context_.export_file_path;
}
pxr::UsdTimeCode USDAbstractWriter::get_export_time_code() const
{
if (is_animated_) {
return usd_export_context_.hierarchy_iterator->get_export_time_code();
return usd_export_context_.time_code;
}
/* By using the default timecode USD won't even write a single `timeSample` for non-animated
* data. Instead, it writes it as non-timesampled. */
@ -107,24 +107,14 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
/* Construct the material. */
pxr::TfToken material_name(usd_export_context_.hierarchy_iterator->get_id_name(&material->id));
pxr::TfToken material_name(pxr::TfMakeValidIdentifier(material->id.name + 2));
pxr::SdfPath usd_path = get_material_library_path().AppendChild(material_name);
pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
if (usd_material) {
return usd_material;
}
usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
create_usd_preview_surface_material(
this->usd_export_context_, material, usd_material, active_uv);
}
else {
create_usd_viewport_material(this->usd_export_context_, material, usd_material);
}
return usd_material;
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
return create_usd_material(usd_export_context_, usd_path, material, active_uv);
}
void USDAbstractWriter::write_visibility(const HierarchyContext &context,

View File

@ -108,14 +108,11 @@ static void create_uvmap_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &usd_material,
pxr::UsdShadeShader &usd_tex_shader,
const pxr::TfToken &default_uv);
static void export_texture(bNode *node,
const pxr::UsdStageRefPtr stage,
const bool allow_overwrite = false);
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);
static std::string get_tex_image_asset_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
bNode *node);
static InputSpecMap &preview_surface_input_map();
static bNodeLink *traverse_channel(bNodeSocket *input, short target_type);
@ -123,10 +120,10 @@ 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);
void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv)
static void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv)
{
if (!material) {
return;
@ -189,9 +186,7 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
/* 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);
export_texture(usd_export_context, input_node);
}
/* Look for a connected uv node. */
@ -270,6 +265,7 @@ void set_normal_texture_range(pxr::UsdShadeShader &usd_shader, const InputSpec &
bias_attr.Set(pxr::GfVec4f(-1.0f, -1.0f, -1.0f, -1.0f));
}
/* Create USD Shade Material network from Blender viewport display settings. */
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material)
@ -568,7 +564,15 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
return shader;
}
/* Creates a USD Preview Surface shader based on the given cycles shading node. */
/* Creates a USD Preview Surface shader based on the given cycles shading node.
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes
* are supported:
* - UVMap
* - Texture Coordinate
* - Image Texture
* - Principled BSDF
* More may be added in the future.
*/
static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &usd_export_context,
pxr::UsdShadeMaterial &material,
bNode *node)
@ -581,8 +585,7 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
}
/* For texture image nodes we set the image path and color space. */
std::string imagePath = get_tex_image_asset_filepath(
node, usd_export_context.stage, usd_export_context.export_params);
std::string imagePath = get_tex_image_asset_filepath(usd_export_context, node);
if (!imagePath.empty()) {
shader.CreateInput(usdtokens::file, pxr::SdfValueTypeNames->Asset)
.Set(pxr::SdfAssetPath(imagePath));
@ -611,9 +614,8 @@ static std::string get_tex_image_asset_filepath(Image *ima)
* generated based on the image name for in-memory textures when exporting textures.
* This function may return an empty string if the image does not have a filepath
* assigned and no asset path could be determined. */
static std::string get_tex_image_asset_filepath(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params)
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context,
bNode *node)
{
Image *ima = reinterpret_cast<Image *>(node->id);
if (!ima) {
@ -626,7 +628,7 @@ static std::string get_tex_image_asset_filepath(bNode *node,
/* Get absolute path. */
path = get_tex_image_asset_filepath(ima);
}
else if (export_params.export_textures) {
else if (usd_export_context.export_params.export_textures) {
/* Image has no filepath, but since we are exporting textures,
* check if this is an in-memory texture for which we can
* generate a file name. */
@ -637,7 +639,7 @@ static std::string get_tex_image_asset_filepath(bNode *node,
return path;
}
if (export_params.export_textures) {
if (usd_export_context.export_params.export_textures) {
/* The texture is exported to a 'textures' directory next to the
* USD root layer. */
@ -645,37 +647,35 @@ static std::string get_tex_image_asset_filepath(bNode *node,
char file_path[FILE_MAX];
BLI_path_split_file_part(path.c_str(), file_path, FILE_MAX);
if (export_params.relative_paths) {
if (usd_export_context.export_params.relative_paths) {
BLI_path_join(exp_path, FILE_MAX, ".", "textures", file_path);
}
else {
/* Create absolute path in the textures directory. */
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
std::string export_path = usd_export_context.export_file_path;
if (export_path.empty()) {
return path;
}
char dir_path[FILE_MAX];
BLI_path_split_dir_part(stage_path.c_str(), dir_path, FILE_MAX);
BLI_path_split_dir_part(export_path.c_str(), dir_path, FILE_MAX);
BLI_path_join(exp_path, FILE_MAX, dir_path, "textures", file_path);
}
BLI_string_replace_char(exp_path, '\\', '/');
return exp_path;
}
if (export_params.relative_paths) {
if (usd_export_context.export_params.relative_paths) {
/* Get the path relative to the USD. */
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
std::string export_path = usd_export_context.export_file_path;
if (export_path.empty()) {
return path;
}
char rel_path[FILE_MAX];
STRNCPY(rel_path, path.c_str());
BLI_path_rel(rel_path, stage_path.c_str());
BLI_path_rel(rel_path, export_path.c_str());
if (!BLI_path_is_rel(rel_path)) {
return path;
}
@ -770,12 +770,9 @@ static void copy_single_file(Image *ima, const std::string &dest_dir, const bool
}
}
/* Export the given texture node's image to a 'textures' directory
* next to given stage's root layer USD.
/* Export the given texture node's image to a 'textures' directory in the export path.
* Based on ImagesExporter::export_UV_Image() */
static void export_texture(bNode *node,
const pxr::UsdStageRefPtr stage,
const bool allow_overwrite)
static void export_texture(const USDExporterContext &usd_export_context, bNode *node)
{
if (!ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT)) {
return;
@ -786,14 +783,13 @@ static void export_texture(bNode *node,
return;
}
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
std::string export_path = usd_export_context.export_file_path;
if (export_path.empty()) {
return;
}
char usd_dir_path[FILE_MAX];
BLI_path_split_dir_part(stage_path.c_str(), usd_dir_path, FILE_MAX);
BLI_path_split_dir_part(export_path.c_str(), usd_dir_path, FILE_MAX);
char tex_dir_path[FILE_MAX];
BLI_path_join(tex_dir_path, FILE_MAX, usd_dir_path, "textures", SEP_STR);
@ -803,6 +799,7 @@ static void export_texture(bNode *node,
const bool is_dirty = BKE_image_is_dirty(ima);
const bool is_generated = ima->source == IMA_SRC_GENERATED;
const bool is_packed = BKE_image_has_packedfile(ima);
const bool allow_overwrite = usd_export_context.export_params.overwrite_textures;
std::string dest_dir(tex_dir_path);
@ -829,4 +826,22 @@ const pxr::TfToken token_for_input(const char *input_name)
return it->second.input_name;
}
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context,
pxr::SdfPath usd_path,
Material *material,
const std::string &active_uv)
{
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);
}
else {
create_usd_viewport_material(usd_export_context, material, usd_material);
}
return usd_material;
}
} // namespace blender::io::usd

View File

@ -16,31 +16,18 @@ namespace blender::io::usd {
struct USDExporterContext;
/* 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. */
const pxr::TfToken token_for_input(const char *input_name);
/**
* Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes
* are supported:
* - UVMap
* - Texture Coordinate
* - Image Texture
* - Principled BSDF
* More may be added in the future.
/* Create USDMaterial from Blender material.
*
* \param default_uv: used as the default UV set name sampled by the `primvar`
* reader shaders generated for image texture nodes that don't have an attached UVMap node.
*/
void create_usd_preview_surface_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const std::string &default_uv = "");
pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_context,
pxr::SdfPath usd_path,
Material *material,
const std::string &active_uv);
/* Entry point to create USD Shade Material network from Blender viewport display settings. */
void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material);
/* 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. */
const pxr::TfToken token_for_input(const char *input_name);
} // namespace blender::io::usd

View File

@ -109,7 +109,7 @@ TEST_F(UsdCurvesTest, usd_export_curves)
/* File sanity check. */
EXPECT_EQ(BLI_listbase_count(&bfile->main->objects), 6);
USDExportParams params{};
USDExportParams params;
const bool result = USD_export(context, output_filename.c_str(), &params, false);
EXPECT_TRUE(result) << "USD export should succed.";

View File

@ -209,10 +209,11 @@ TEST_F(UsdExportTest, usd_export_rain_mesh)
/* File sanity check. */
EXPECT_EQ(BLI_listbase_count(&bfile->main->objects), 3);
USDExportParams params{};
USDExportParams params;
params.export_materials = false;
params.export_normals = true;
params.export_uvmaps = false;
params.visible_objects_only = true;
params.evaluation_mode = eEvaluationMode::DAG_EVAL_VIEWPORT;
bool result = USD_export(context, output_filename.c_str(), &params, false);
ASSERT_TRUE(result) << "Writing to " << output_filename << " failed!";
@ -272,12 +273,13 @@ TEST_F(UsdExportTest, usd_export_material)
EXPECT_TRUE(bool(material));
USDExportParams params{};
params.export_normals = true;
USDExportParams params;
params.export_materials = true;
params.generate_preview_surface = true;
params.export_normals = true;
params.export_textures = false;
params.export_uvmaps = true;
params.evaluation_mode = eEvaluationMode::DAG_EVAL_VIEWPORT;
params.generate_preview_surface = true;
params.relative_paths = false;
const bool result = USD_export(context, output_filename.c_str(), &params, false);
ASSERT_TRUE(result) << "Unable to export stage to " << output_filename;

View File

@ -94,7 +94,9 @@ TEST_F(UsdUsdzExportTest, usdz_export)
<< "BLI_current_working_dir is not expected to return a different value than the given char "
"buffer.";
USDExportParams params{};
USDExportParams params;
params.export_materials = false;
params.visible_objects_only = false;
bool result = USD_export(context, output_filepath, &params, false);
ASSERT_TRUE(result) << "usd export to " << output_filepath << " failed.";

View File

@ -38,20 +38,20 @@ typedef enum eUSDTexNameCollisionMode {
} eUSDTexNameCollisionMode;
struct USDExportParams {
bool export_animation;
bool export_hair;
bool export_uvmaps;
bool export_normals;
bool export_materials;
bool selected_objects_only;
bool visible_objects_only;
bool use_instancing;
enum eEvaluationMode evaluation_mode;
bool generate_preview_surface;
bool export_textures;
bool overwrite_textures;
bool relative_paths;
char root_prim_path[1024]; /* FILE_MAX */
bool export_animation = false;
bool export_hair = true;
bool export_uvmaps = true;
bool export_normals = true;
bool export_materials = true;
bool selected_objects_only = false;
bool visible_objects_only = true;
bool use_instancing = false;
enum eEvaluationMode evaluation_mode = DAG_EVAL_VIEWPORT;
bool generate_preview_surface = true;
bool export_textures = true;
bool overwrite_textures = true;
bool relative_paths = true;
char root_prim_path[1024] = ""; /* FILE_MAX */
};
struct USDImportParams {

View File

@ -84,7 +84,7 @@ def main():
else:
report = render_report.Report("Storm USD", output_dir, idiff)
report.set_reference_dir("storm_usd_renders")
report.set_compare_engine('hydra_storm')
report.set_compare_engine('storm_hydra')
report.set_pixelated(True)