From 5edda6cbcc091fa44a2b988e23f862d6db7d437e Mon Sep 17 00:00:00 2001 From: Michael B Johnson Date: Fri, 15 Dec 2023 21:11:41 +0100 Subject: [PATCH] USD: optionally author subdivision schema on export USD: optionally author subdivision schema on export This PR adds support for exporting USD assets which have the subdivision schema applied. Current behavior prior to this PR is that the effects of Subdivision Surface modifiers are always applied to their mesh prior to export, such that it is not possible to recover the original base mesh. In this PR we provide three options for the subdiv schema type: Ignore - Export base mesh without subdivision with USD Scheme = None Tessellate - Export subdivided mesh with USD Scheme = None Best Match (default) - Export base mesh with USD Scheme = Catmull-Clark "Best Match" here means that Blender will set a subdiv scheme type in the exported USD asset when it is possible to closely match the subdivision surface type that was authored in Blender. At this time Blender provides two subdivision types: Catmull-Clark and Simple. Because Simple does not have a corresponding subdivision type in USD, we do not attempt to convert or represent it, and instead the Simple subdiv modifier will be evaluated and applied to the mesh during export. Whenever a Catmull-Clark Subdivision Surface modifier is applied to an object, and is the last modifier in the stack, it is possible to set the subdiv scheme to Catmull-Clark for the respective prim in the exported USD file. Authored by Apple: Matt McLin Co-authored-by: Matt McLin Co-authored-by: Brecht Van Lommel Pull Request: https://projects.blender.org/blender/blender/pulls/113267 --- source/blender/editors/io/io_usd.cc | 32 +++++ source/blender/io/alembic/CMakeLists.txt | 2 - .../io/alembic/exporter/abc_export_capi.cc | 2 +- .../alembic/exporter/abc_subdiv_disabler.cc | 86 ------------ .../io/alembic/exporter/abc_subdiv_disabler.h | 40 ------ source/blender/io/common/CMakeLists.txt | 2 + .../blender/io/common/IO_subdiv_disabler.hh | 62 +++++++++ .../io/common/intern/subdiv_disabler.cc | 118 ++++++++++++++++ .../blender/io/usd/intern/usd_capi_export.cc | 11 ++ .../blender/io/usd/intern/usd_writer_mesh.cc | 127 +++++++++++++++++- .../blender/io/usd/intern/usd_writer_mesh.h | 7 +- source/blender/io/usd/usd.h | 8 ++ 12 files changed, 362 insertions(+), 135 deletions(-) delete mode 100644 source/blender/io/alembic/exporter/abc_subdiv_disabler.cc delete mode 100644 source/blender/io/alembic/exporter/abc_subdiv_disabler.h create mode 100644 source/blender/io/common/IO_subdiv_disabler.hh create mode 100644 source/blender/io/common/intern/subdiv_disabler.cc diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index a7a2f58a1b4..221dc3b3185 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -91,6 +91,26 @@ const EnumPropertyItem rna_enum_usd_tex_name_collision_mode_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +const EnumPropertyItem rna_enum_usd_export_subdiv_mode_items[] = { + {USD_SUBDIV_IGNORE, + "IGNORE", + 0, + "Ignore", + "Subdivision scheme = None, export base mesh without subdivision"}, + {USD_SUBDIV_TESSELLATE, + "TESSELLATE", + 0, + "Tessellate", + "Subdivision scheme = None, export subdivided mesh"}, + {USD_SUBDIV_BEST_MATCH, + "BEST_MATCH", + 0, + "Best Match", + "Subdivision scheme = Catmull-Clark, when possible. " + "Reverts to exporting the subdivided mesh for the Simple subdivision type"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + /* Stored in the wmOperator's customdata field to indicate it should run as a background job. * This is set when the operator is invoked, and not set when it is only executed. */ enum { AS_BACKGROUND_JOB = 1 }; @@ -155,6 +175,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) const bool export_mesh_colors = RNA_boolean_get(op->ptr, "export_mesh_colors"); const bool export_normals = RNA_boolean_get(op->ptr, "export_normals"); const bool export_materials = RNA_boolean_get(op->ptr, "export_materials"); + const eSubdivExportMode export_subdiv = eSubdivExportMode( + RNA_enum_get(op->ptr, "export_subdivision")); const bool use_instancing = RNA_boolean_get(op->ptr, "use_instancing"); const bool evaluation_mode = RNA_enum_get(op->ptr, "evaluation_mode"); @@ -174,6 +196,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) export_normals, export_mesh_colors, export_materials, + export_subdiv, selected_objects_only, visible_objects_only, use_instancing, @@ -211,6 +234,7 @@ static void wm_usd_export_draw(bContext * /*C*/, wmOperator *op) uiItemR(col, ptr, "export_uvmaps", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(col, ptr, "export_normals", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(col, ptr, "export_materials", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "export_subdivision", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(col, ptr, "root_prim_path", UI_ITEM_NONE, nullptr, ICON_NONE); col = uiLayoutColumn(box, true); @@ -335,6 +359,14 @@ void WM_OT_usd_export(wmOperatorType *ot) "Export viewport settings of materials as USD preview materials, and export " "material assignments as geometry subsets"); + RNA_def_enum(ot->srna, + "export_subdivision", + rna_enum_usd_export_subdiv_mode_items, + USD_SUBDIV_BEST_MATCH, + "Subdivision Scheme", + "Choose how subdivision modifiers will be mapped to the USD subdivision scheme " + "during export"); + RNA_def_boolean(ot->srna, "use_instancing", false, diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt index 9287fc546c9..3f7822dcb77 100644 --- a/source/blender/io/alembic/CMakeLists.txt +++ b/source/blender/io/alembic/CMakeLists.txt @@ -44,7 +44,6 @@ set(SRC exporter/abc_custom_props.cc exporter/abc_export_capi.cc exporter/abc_hierarchy_iterator.cc - exporter/abc_subdiv_disabler.cc exporter/abc_writer_abstract.cc exporter/abc_writer_camera.cc exporter/abc_writer_curves.cc @@ -72,7 +71,6 @@ set(SRC exporter/abc_archive.h exporter/abc_custom_props.h exporter/abc_hierarchy_iterator.h - exporter/abc_subdiv_disabler.h exporter/abc_writer_abstract.h exporter/abc_writer_camera.h exporter/abc_writer_curves.h diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc index 2922a606c05..f395cf86ee6 100644 --- a/source/blender/io/alembic/exporter/abc_export_capi.cc +++ b/source/blender/io/alembic/exporter/abc_export_capi.cc @@ -3,9 +3,9 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "ABC_alembic.h" +#include "IO_subdiv_disabler.hh" #include "abc_archive.h" #include "abc_hierarchy_iterator.h" -#include "abc_subdiv_disabler.h" #include "MEM_guardedalloc.h" diff --git a/source/blender/io/alembic/exporter/abc_subdiv_disabler.cc b/source/blender/io/alembic/exporter/abc_subdiv_disabler.cc deleted file mode 100644 index e74f1f26067..00000000000 --- a/source/blender/io/alembic/exporter/abc_subdiv_disabler.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* SPDX-FileCopyrightText: 2020 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "abc_subdiv_disabler.h" - -#include - -#include "BLI_listbase.h" - -#include "DEG_depsgraph.hh" -#include "DEG_depsgraph_query.hh" - -#include "DNA_layer_types.h" -#include "DNA_mesh_types.h" -#include "DNA_modifier_types.h" -#include "DNA_object_types.h" - -#include "BKE_layer.h" -#include "BKE_modifier.hh" - -namespace blender::io::alembic { - -SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph) {} - -SubdivModifierDisabler::~SubdivModifierDisabler() -{ - for (ModifierData *modifier : disabled_modifiers_) { - modifier->mode &= ~eModifierMode_DisableTemporary; - } -} - -void SubdivModifierDisabler::disable_modifiers() -{ - Scene *scene = DEG_get_input_scene(depsgraph_); - ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_); - - BKE_view_layer_synced_ensure(scene, view_layer); - LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { - Object *object = base->object; - - if (object->type != OB_MESH) { - continue; - } - - ModifierData *subdiv = get_subdiv_modifier(scene, object); - if (subdiv == nullptr) { - continue; - } - - /* This disables more modifiers than necessary, as it doesn't take restrictions like - * "export selected objects only" into account. However, with the subsurfs disabled, - * moving to a different frame is also going to be faster, so in the end this is probably - * a good thing to do. */ - subdiv->mode |= eModifierMode_DisableTemporary; - disabled_modifiers_.insert(subdiv); - DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); - } -} - -ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene, Object *ob) -{ - ModifierData *md = static_cast(ob->modifiers.last); - - for (; md; md = md->prev) { - if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) { - continue; - } - - if (md->type == eModifierType_Subsurf) { - SubsurfModifierData *smd = reinterpret_cast(md); - - if (smd->subdivType == ME_CC_SUBSURF) { - return md; - } - } - - /* mesh is not a subsurf. break */ - if (!ELEM(md->type, eModifierType_Displace, eModifierType_ParticleSystem)) { - return nullptr; - } - } - - return nullptr; -} - -} // namespace blender::io::alembic diff --git a/source/blender/io/alembic/exporter/abc_subdiv_disabler.h b/source/blender/io/alembic/exporter/abc_subdiv_disabler.h deleted file mode 100644 index cd568516102..00000000000 --- a/source/blender/io/alembic/exporter/abc_subdiv_disabler.h +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-FileCopyrightText: 2020 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma once - -#include - -struct Depsgraph; -struct ModifierData; -struct Object; -struct Scene; - -namespace blender::io::alembic { - -/** - * Temporarily all subdivision modifiers on mesh objects. - * The destructor restores all disabled modifiers. - * - * This is used to export unsubdivided meshes to Alembic. It is done in a separate step before the - * exporter starts iterating over all the frames, so that it only has to happen once per export. - */ -class SubdivModifierDisabler final { - private: - Depsgraph *depsgraph_; - std::set disabled_modifiers_; - - public: - explicit SubdivModifierDisabler(Depsgraph *depsgraph); - ~SubdivModifierDisabler(); - - void disable_modifiers(); - - /** - * Check if the mesh is a subsurf, ignoring disabled modifiers and - * displace if it's after subsurf. - */ - static ModifierData *get_subdiv_modifier(Scene *scene, Object *ob); -}; - -} // namespace blender::io::alembic diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt index 03365aa3c7e..10269e17d2e 100644 --- a/source/blender/io/common/CMakeLists.txt +++ b/source/blender/io/common/CMakeLists.txt @@ -18,12 +18,14 @@ set(SRC intern/object_identifier.cc intern/orientation.cc intern/path_util.cc + intern/subdiv_disabler.cc IO_abstract_hierarchy_iterator.h IO_dupli_persistent_id.hh IO_orientation.hh IO_path_util.hh IO_path_util_types.hh + IO_subdiv_disabler.hh IO_types.hh intern/dupli_parent_finder.hh ) diff --git a/source/blender/io/common/IO_subdiv_disabler.hh b/source/blender/io/common/IO_subdiv_disabler.hh new file mode 100644 index 00000000000..59ed65283dc --- /dev/null +++ b/source/blender/io/common/IO_subdiv_disabler.hh @@ -0,0 +1,62 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include "DNA_modifier_types.h" + +#include + +struct Depsgraph; +struct ModifierData; +struct Object; +struct Scene; + +namespace blender::io { + +/** + * This code is shared between the Alembic and USD exporters. + * Temporarily disable the subdiv modifier on mesh objects, + * if the subdiv modifier is last on the modifier stack. + * + * The destructor restores all disabled modifiers. + * + * Currently, this class is used to disable Catmull-Clark subdivision modifiers. + * It is done in a separate step before the exporter starts iterating over all + * the frames, so that it only has to happen once per export. + */ +class SubdivModifierDisabler final { + private: + Depsgraph *depsgraph_; + std::set disabled_modifiers_; + std::set modified_objects_; + + public: + explicit SubdivModifierDisabler(Depsgraph *depsgraph); + ~SubdivModifierDisabler(); + + /** + * Disable subdiv modifiers on all mesh objects. + */ + void disable_modifiers(); + + /** + * Return the Catmull-Clark subdiv modifier on the mesh, if it's the last modifier + * in the list or if it's the last modifier preceding any particle system modifiers. + * This function ignores Simple subdiv modifiers. + */ + static ModifierData *get_subdiv_modifier(Scene *scene, const Object *ob, ModifierMode mode); + + /* Disallow copying. */ + SubdivModifierDisabler(const SubdivModifierDisabler &) = delete; + SubdivModifierDisabler &operator=(const SubdivModifierDisabler &) = delete; + + private: + /** + * Disable the given modifier and add it to the disabled + * modifiers list. + */ + void disable_modifier(ModifierData *mod); +}; + +} // namespace blender::io diff --git a/source/blender/io/common/intern/subdiv_disabler.cc b/source/blender/io/common/intern/subdiv_disabler.cc new file mode 100644 index 00000000000..4281185db44 --- /dev/null +++ b/source/blender/io/common/intern/subdiv_disabler.cc @@ -0,0 +1,118 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "IO_subdiv_disabler.hh" + +#include + +#include "BLI_listbase.h" + +#include "DEG_depsgraph.hh" +#include "DEG_depsgraph_query.hh" + +#include "DNA_layer_types.h" +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "BKE_layer.h" +#include "BKE_modifier.hh" + +namespace blender::io { + +/* Returns the last subdiv modifier associated with an object, + * if that modifier should be disabled. + * We do not disable the subdiv modifier if other modifiers are + * applied after it, with the sole exception of particle modifiers, + * which are allowed. + * Returns nullptr if there is not any subdiv modifier to disable. + */ +ModifierData *SubdivModifierDisabler::get_subdiv_modifier(Scene *scene, + const Object *ob, + ModifierMode mode) +{ + ModifierData *md = static_cast(ob->modifiers.last); + + for (; md; md = md->prev) { + /* Ignore disabled modifiers. */ + if (!BKE_modifier_is_enabled(scene, md, mode)) { + continue; + } + + if (md->type == eModifierType_Subsurf) { + SubsurfModifierData *smd = reinterpret_cast(md); + + if (smd->subdivType == ME_CC_SUBSURF) { + /* This is a Catmull-Clark modifier. */ + return md; + } + + /* Not Catmull-Clark, so ignore it. */ + return nullptr; + } + + /* If any modifier other than a particle system exists after the + * subdiv modifier, then abort. */ + if (md->type != eModifierType_ParticleSystem) { + return nullptr; + } + } + + return nullptr; +} + +SubdivModifierDisabler::SubdivModifierDisabler(Depsgraph *depsgraph) : depsgraph_(depsgraph) {} + +SubdivModifierDisabler::~SubdivModifierDisabler() +{ + /* Enable previously disabled modifiers. */ + for (ModifierData *modifier : disabled_modifiers_) { + modifier->mode &= ~eModifierMode_DisableTemporary; + } + + /* Update object to render with restored modifiers in the viewport. */ + for (Object *object : modified_objects_) { + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); + } +} + +void SubdivModifierDisabler::disable_modifiers() +{ + eEvaluationMode eval_mode = DEG_get_mode(depsgraph_); + const ModifierMode mode = eval_mode == DAG_EVAL_VIEWPORT ? eModifierMode_Realtime : + eModifierMode_Render; + + Scene *scene = DEG_get_input_scene(depsgraph_); + ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph_); + + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { + Object *object = base->object; + + if (object->type != OB_MESH) { + continue; + } + + /* Check if a subdiv modifier exists, and should be disabled. */ + ModifierData *mod = get_subdiv_modifier(scene, object, mode); + if (!mod) { + continue; + } + + /* This might disable more modifiers than necessary, as it doesn't take restrictions like + * "export selected objects only" into account. However, with the subdivs disabled, + * moving to a different frame is also going to be faster, so in the end this is probably + * a good thing to do. */ + disable_modifier(mod); + modified_objects_.insert(object); + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); + } +} + +void SubdivModifierDisabler::disable_modifier(ModifierData *mod) +{ + mod->mode |= eModifierMode_DisableTemporary; + disabled_modifiers_.insert(mod); +} + +} // namespace blender::io diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index 26d02fb162c..6fae3909cf2 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -4,6 +4,7 @@ #include +#include "IO_subdiv_disabler.hh" #include "usd.h" #include "usd.hh" #include "usd_hierarchy_iterator.h" @@ -226,6 +227,16 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, Scene *scene = DEG_get_input_scene(depsgraph); Main *bmain = DEG_get_bmain(depsgraph); + SubdivModifierDisabler mod_disabler(depsgraph); + + /* If we want to set the subdiv scheme, then we need to the export the mesh + * without the subdiv modifier applied. */ + if ((params.export_subdiv == USD_SUBDIV_BEST_MATCH) || + (params.export_subdiv == USD_SUBDIV_IGNORE)) { + mod_disabler.disable_modifiers(); + BKE_scene_graph_update_tagged(depsgraph, bmain); + } + /* This whole `export_to_stage` function is assumed to cover about 80% of the whole export * process, from 0.1f to 0.9f. */ worker_status->progress = 0.10f; diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index b1a04c48228..8acf6f15db0 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -56,6 +56,35 @@ bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const return true; } +/* Get the last subdiv modifier, regardless of enable/disable status */ +static const SubsurfModifierData *get_last_subdiv_modifier(eEvaluationMode eval_mode, Object *obj) +{ + BLI_assert(obj); + + /* Return the subdiv modifier if it is the last modifier and has + * the required mode enabled. */ + + ModifierData *md = (ModifierData *)(obj->modifiers.last); + + if (!md) { + return nullptr; + } + + /* Determine if the modifier is enabled for the current evaluation mode. */ + ModifierMode mod_mode = (eval_mode == DAG_EVAL_RENDER) ? eModifierMode_Render : + eModifierMode_Realtime; + + if ((md->mode & mod_mode) != mod_mode) { + return nullptr; + } + + if (md->type == eModifierType_Subsurf) { + return reinterpret_cast(md); + } + + return nullptr; +} + void USDGenericMeshWriter::do_write(HierarchyContext &context) { Object *object_eval = context.object; @@ -67,7 +96,11 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context) } try { - write_mesh(context, mesh); + /* Fetch the subdiv modifier, if one exists and it is the last modifier. */ + const SubsurfModifierData *subsurfData = get_last_subdiv_modifier( + usd_export_context_.export_params.evaluation_mode, object_eval); + + write_mesh(context, mesh, subsurfData); if (needsfree) { free_export_mesh(mesh); @@ -376,7 +409,9 @@ struct USDMeshData { pxr::VtFloatArray corner_sharpnesses; }; -void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) +void USDGenericMeshWriter::write_mesh(HierarchyContext &context, + Mesh *mesh, + const SubsurfModifierData *subsurfData) { pxr::UsdTimeCode timecode = get_export_time_code(); pxr::UsdStageRefPtr stage = usd_export_context_.stage; @@ -465,18 +500,26 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) } write_custom_data(mesh, usd_mesh); + write_surface_velocity(mesh, usd_mesh); - if (usd_export_context_.export_params.export_normals) { + const pxr::TfToken subdiv_scheme = get_subdiv_scheme(subsurfData); + + /* Normals can be animated, so ensure these are written for each frame, + * unless a subdiv modifier is used, in which case normals are computed, + * not stored with the mesh. */ + if (usd_export_context_.export_params.export_normals && + subdiv_scheme == pxr::UsdGeomTokens->none) { write_normals(mesh, usd_mesh); } - write_surface_velocity(mesh, usd_mesh); /* TODO(Sybren): figure out what happens when the face groups change. */ if (frame_has_been_written_) { return; } - usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none); + /* The subdivision scheme is a uniform according to spec, + * so this value cannot be animated. */ + write_subdiv(subdiv_scheme, usd_mesh, subsurfData); if (usd_export_context_.export_params.export_materials) { assign_materials(context, usd_mesh, usd_mesh_data.face_groups); @@ -491,6 +534,80 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) } } +pxr::TfToken USDGenericMeshWriter::get_subdiv_scheme(const SubsurfModifierData *subsurfData) +{ + /* Default to setting the subdivision scheme to None. */ + pxr::TfToken subdiv_scheme = pxr::UsdGeomTokens->none; + + if (subsurfData) { + if (subsurfData->subdivType == SUBSURF_TYPE_CATMULL_CLARK) { + if (usd_export_context_.export_params.export_subdiv == USD_SUBDIV_BEST_MATCH) { + /* If a subdivision modifier exists, and it uses Catmull-Clark, then apply Catmull-Clark + * SubD scheme. */ + subdiv_scheme = pxr::UsdGeomTokens->catmullClark; + } + } + else { + /* "Simple" is currently the only other subdivision type provided by Blender, */ + /* and we do not yet provide a corresponding representation for USD export. */ + BKE_reportf(reports(), + RPT_WARNING, + "USD export: Simple subdivision not supported, exporting subdivided mesh"); + } + } + + return subdiv_scheme; +} + +void USDGenericMeshWriter::write_subdiv(const pxr::TfToken &subdiv_scheme, + pxr::UsdGeomMesh &usd_mesh, + const SubsurfModifierData *subsurfData) +{ + usd_mesh.CreateSubdivisionSchemeAttr().Set(subdiv_scheme); + if (subdiv_scheme == pxr::UsdGeomTokens->catmullClark) { + /* For Catmull-Clark, also consider the various interpolation modes. */ + /* For reference, see + * https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#face-varying-interpolation-rules + */ + switch (subsurfData->uv_smooth) { + case SUBSURF_UV_SMOOTH_NONE: + usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->all); + break; + case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS: + usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersOnly); + break; + case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_AND_JUNCTIONS: + usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus1); + break; + case SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_JUNCTIONS_AND_CONCAVE: + usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->cornersPlus2); + break; + case SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES: + usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->boundaries); + break; + case SUBSURF_UV_SMOOTH_ALL: + usd_mesh.CreateFaceVaryingLinearInterpolationAttr().Set(pxr::UsdGeomTokens->none); + break; + default: + BLI_assert_msg(0, "Unsupported UV smoothing mode."); + } + + /* For reference, see + * https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#boundary-interpolation-rules + */ + switch (subsurfData->boundary_smooth) { + case SUBSURF_BOUNDARY_SMOOTH_ALL: + usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeOnly); + break; + case SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS: + usd_mesh.CreateInterpolateBoundaryAttr().Set(pxr::UsdGeomTokens->edgeAndCorner); + break; + default: + BLI_assert_msg(0, "Unsupported boundary smoothing mode."); + } + } +} + static void get_positions(const Mesh *mesh, USDMeshData &usd_mesh_data) { const Span positions = mesh->vert_positions().cast(); diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h index e3c9a362242..e4f401f7532 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.h +++ b/source/blender/io/usd/intern/usd_writer_mesh.h @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include "DNA_modifier_types.h" #include "usd_writer_abstract.h" #include "BLI_map.hh" @@ -31,7 +32,11 @@ class USDGenericMeshWriter : public USDAbstractWriter { /* Mapping from material slot number to array of face indices with that material. */ using MaterialFaceGroups = Map; - void write_mesh(HierarchyContext &context, Mesh *mesh); + void write_mesh(HierarchyContext &context, Mesh *mesh, const SubsurfModifierData *subsurfData); + pxr::TfToken get_subdiv_scheme(const SubsurfModifierData *subsurfData); + void write_subdiv(const pxr::TfToken &subdiv_scheme, + pxr::UsdGeomMesh &usd_mesh, + const SubsurfModifierData *subsurfData); void get_geometry_data(const Mesh *mesh, struct USDMeshData &usd_mesh_data); void assign_materials(const HierarchyContext &context, pxr::UsdGeomMesh usd_mesh, diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 3b40d86cfb7..bb0521d5112 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -40,6 +40,13 @@ typedef enum eUSDTexNameCollisionMode { USD_TEX_NAME_COLLISION_OVERWRITE = 1, } eUSDTexNameCollisionMode; +typedef enum eSubdivExportMode { + USD_SUBDIV_IGNORE = 0, /* Subdivision scheme = None, export base mesh without subdivision. */ + USD_SUBDIV_TESSELLATE = 1, /* Subdivision scheme = None, export subdivided mesh. */ + USD_SUBDIV_BEST_MATCH = 2, /* Apply the USD subdivision scheme that is the closest match to Blender. */ + /* Reverts to USD_SUBDIV_TESSELLATE if the subdivision method is not supported. */ +} eSubdivExportMode; + struct USDExportParams { bool export_animation = false; bool export_hair = true; @@ -47,6 +54,7 @@ struct USDExportParams { bool export_normals = true; bool export_mesh_colors = true; bool export_materials = true; + eSubdivExportMode export_subdiv = USD_SUBDIV_BEST_MATCH; bool selected_objects_only = false; bool visible_objects_only = true; bool use_instancing = false;