From 1f50beb9f28edd2fe54d97647222ad6ee5808c1c Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 7 Oct 2020 18:03:07 +0200 Subject: Volumes: new Volume Displace modifier This modifier uses a 3D texture to displace a volume. For now, this can only use the previously existing texture system, because we do not have a better alternative yet. Still, the results can be quite good and interesting. See D9075 for some examples. Reviewers: brecht, simonthommes Differential Revision: https://developer.blender.org/D9075 --- source/blender/makesdna/DNA_modifier_types.h | 20 ++ source/blender/makesrna/RNA_access.h | 1 + source/blender/makesrna/intern/rna_modifier.c | 75 +++++ source/blender/modifiers/CMakeLists.txt | 1 + source/blender/modifiers/MOD_modifiertypes.h | 1 + source/blender/modifiers/intern/MOD_util.c | 1 + .../modifiers/intern/MOD_volume_displace.cc | 345 +++++++++++++++++++++ 7 files changed, 444 insertions(+) create mode 100644 source/blender/modifiers/intern/MOD_volume_displace.cc diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index fa2f54c7aa0..3a3aa106dc0 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -96,6 +96,7 @@ typedef enum ModifierType { eModifierType_Fluid = 56, eModifierType_Simulation = 57, eModifierType_MeshToVolume = 58, + eModifierType_VolumeDisplace = 59, NUM_MODIFIER_TYPES, } ModifierType; @@ -2255,6 +2256,25 @@ typedef enum MeshToVolumeModifierResolutionMode { MESH_TO_VOLUME_RESOLUTION_MODE_VOXEL_SIZE = 1, } MeshToVolumeModifierResolutionMode; +typedef struct VolumeDisplaceModifierData { + ModifierData modifier; + + struct Tex *texture; + struct Object *texture_map_object; + int texture_map_mode; + + float strength; + float texture_mid_level[3]; + float texture_sample_radius; +} VolumeDisplaceModifierData; + +/* VolumeDisplaceModifierData->texture_map_mode */ +enum { + MOD_VOLUME_DISPLACE_MAP_LOCAL = 0, + MOD_VOLUME_DISPLACE_MAP_GLOBAL = 1, + MOD_VOLUME_DISPLACE_MAP_OBJECT = 2, +}; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 1c2db53b12c..0b882742839 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -709,6 +709,7 @@ extern StructRNA RNA_View3DShading; extern StructRNA RNA_ViewLayer; extern StructRNA RNA_ViewLayerEEVEE; extern StructRNA RNA_Volume; +extern StructRNA RNA_VolumeDisplaceModifier; extern StructRNA RNA_VoronoiTexture; extern StructRNA RNA_WalkNavigation; extern StructRNA RNA_WarpModifier; diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 3fff2feb10d..766497d53c1 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -272,6 +272,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { ICON_MOD_WAVE, "Wave", "Adds a ripple-like motion to an object’s geometry"}, + {eModifierType_VolumeDisplace, + "VOLUME_DISPLACE", + ICON_VOLUME_DATA, + "Volume Displace", + "Deform volume based on noise or other vector fields"}, /* TODO: Use correct icon. */ {0, "", 0, N_("Physics"), ""}, {eModifierType_Cloth, "CLOTH", ICON_MOD_CLOTH, "Cloth", ""}, {eModifierType_Collision, "COLLISION", ICON_MOD_PHYSICS, "Collision", ""}, @@ -7057,6 +7062,75 @@ static void rna_def_modifier_mesh_to_volume(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_volume_displace(BlenderRNA *brna) +{ + static const EnumPropertyItem prop_texture_map_mode_items[] = { + {MOD_VOLUME_DISPLACE_MAP_LOCAL, + "LOCAL", + 0, + "Local", + "Use the local coordinate system for the texture coordinates"}, + {MOD_VOLUME_DISPLACE_MAP_GLOBAL, + "GLOBAL", + 0, + "Global", + "Use the global coordinate system for the texture coordinates"}, + {MOD_VOLUME_DISPLACE_MAP_OBJECT, + "OBJECT", + 0, + "Object", + "Use the linked object's local coordinate system for the texture coordinates"}, + {0, NULL, 0, NULL, NULL}, + }; + + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "VolumeDisplaceModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "Volume Displace Modifier", ""); + RNA_def_struct_sdna(srna, "VolumeDisplaceModifierData"); + RNA_def_struct_ui_icon(srna, ICON_VOLUME_DATA); /* TODO: Use correct icon. */ + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_NONE); + RNA_def_property_ui_text(prop, "Strength", "Strength of the displacement"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "texture", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Texture", ""); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "texture_map_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prop_texture_map_mode_items); + RNA_def_property_ui_text(prop, "Texture Mapping Mode", ""); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "texture_map_object", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Object", "Object to use for texture mapping"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "texture_mid_level", PROP_FLOAT, PROP_XYZ); + RNA_def_property_ui_text( + prop, "Texture Mid Level", "Subtracted from the texture color to get a displacement vector"); + RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1f, 5); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "texture_sample_radius", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_ui_text( + prop, + "Texture Sample Radius", + "Smaller values result in better performance but might cut off the volume"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1f, 5); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + RNA_define_lib_overridable(false); +} + void RNA_def_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -7189,6 +7263,7 @@ void RNA_def_modifier(BlenderRNA *brna) rna_def_modifier_simulation(brna); # endif rna_def_modifier_mesh_to_volume(brna); + rna_def_modifier_volume_displace(brna); } #endif diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 37656ed7b60..f5786dc0701 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -101,6 +101,7 @@ set(SRC intern/MOD_util.c intern/MOD_uvproject.c intern/MOD_uvwarp.c + intern/MOD_volume_displace.cc intern/MOD_warp.c intern/MOD_wave.c intern/MOD_weighted_normal.c diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h index e12f91ff99b..6dc50e1b47d 100644 --- a/source/blender/modifiers/MOD_modifiertypes.h +++ b/source/blender/modifiers/MOD_modifiertypes.h @@ -87,6 +87,7 @@ extern ModifierTypeInfo modifierType_SurfaceDeform; extern ModifierTypeInfo modifierType_WeightedNormal; extern ModifierTypeInfo modifierType_Simulation; extern ModifierTypeInfo modifierType_MeshToVolume; +extern ModifierTypeInfo modifierType_VolumeDisplace; /* MOD_util.c */ void modifier_type_init(ModifierTypeInfo *types[]); diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index 75436435e97..c1fdaa487b5 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -344,5 +344,6 @@ void modifier_type_init(ModifierTypeInfo *types[]) INIT_TYPE(WeightedNormal); INIT_TYPE(Simulation); INIT_TYPE(MeshToVolume); + INIT_TYPE(VolumeDisplace); #undef INIT_TYPE } diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc new file mode 100644 index 00000000000..89b5a604d58 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -0,0 +1,345 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup modifiers + */ + +#include "BKE_lib_query.h" +#include "BKE_mesh_runtime.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_texture.h" +#include "BKE_volume.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_texture_types.h" +#include "DNA_volume_types.h" + +#include "DEG_depsgraph_build.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLO_read_write.h" + +#include "MEM_guardedalloc.h" + +#include "MOD_modifiertypes.h" +#include "MOD_ui_common.h" + +#include "RE_shader_ext.h" + +#include "RNA_access.h" + +#include "BLI_math_vector.h" + +#ifdef WITH_OPENVDB +# include +# include +# include +# include +# include +#endif + +static void initData(ModifierData *md) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast(md); + vdmd->texture = NULL; + vdmd->strength = 1.0f; + copy_v3_fl(vdmd->texture_mid_level, 0.5f); + vdmd->texture_sample_radius = 1.0f; +} + +static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast(md); + if (vdmd->texture != NULL) { + DEG_add_generic_id_relation(ctx->node, &vdmd->texture->id, "Volume Displace Modifier"); + } + if (vdmd->texture_map_mode == MOD_VOLUME_DISPLACE_MAP_OBJECT) { + if (vdmd->texture_map_object != NULL) { + DEG_add_object_relation( + ctx->node, vdmd->texture_map_object, DEG_OB_COMP_TRANSFORM, "Volume Displace Modifier"); + } + } +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast(md); + walk(userData, ob, (ID **)&vdmd->texture, IDWALK_CB_USER); + walk(userData, ob, (ID **)&vdmd->texture_map_object, IDWALK_CB_USER); +} + +static void foreachTexLink(ModifierData *md, Object *ob, TexWalkFunc walk, void *userData) +{ + walk(userData, ob, md, "texture"); +} + +static bool dependsOnTime(ModifierData *md) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast(md); + if (vdmd->texture) { + return BKE_texture_dependsOnTime(vdmd->texture); + } + return false; +} + +static void panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA ob_ptr; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); + VolumeDisplaceModifierData *vdmd = static_cast(ptr->data); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiTemplateID(layout, C, ptr, "texture", "texture.new", NULL, NULL, 0, ICON_NONE, NULL); + uiItemR(layout, ptr, "texture_map_mode", 0, "Texture Mapping", ICON_NONE); + + if (vdmd->texture_map_mode == MOD_VOLUME_DISPLACE_MAP_OBJECT) { + uiItemR(layout, ptr, "texture_map_object", 0, "Object", ICON_NONE); + } + + uiItemR(layout, ptr, "strength", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "texture_sample_radius", 0, "Sample Radius", ICON_NONE); + uiItemR(layout, ptr, "texture_mid_level", 0, "Mid Level", ICON_NONE); + + modifier_panel_end(layout, ptr); +} + +static void panelRegister(ARegionType *region_type) +{ + modifier_panel_register(region_type, eModifierType_VolumeDisplace, panel_draw); +} + +#ifdef WITH_OPENVDB + +static openvdb::Mat4s matrix_to_openvdb(const float m[4][4]) +{ + /* This constructor expects floats in row-major form. Therefore the matrix is transposed + * afterwards. */ + openvdb::Mat4s new_matrix{reinterpret_cast(m)}; + new_matrix = new_matrix.transpose(); + return new_matrix; +} + +template struct DisplaceOp { + /* Has to be copied for each thread. */ + typename GridType::ConstAccessor accessor; + /* This is the transform of the grid that is being displaced. */ + openvdb::Mat4s index_to_texture; + + Tex *texture; + const double strength; + const openvdb::Vec3d texture_mid_level; + + void operator()(const typename GridType::ValueOnIter &iter) const + { + const openvdb::Coord coord = iter.getCoord(); + const openvdb::Vec3d displace_vector = this->compute_displace_vector(coord); + /* Subtract vector because that makes the result more similar to advection and the mesh + * displace modifier. */ + const openvdb::Vec3d sample_coord = coord.asVec3d() - displace_vector; + const auto new_value = openvdb::tools::BoxSampler::sample(this->accessor, sample_coord); + iter.setValue(new_value); + } + + openvdb::Vec3d compute_displace_vector(const openvdb::Coord &coord) const + { + if (this->texture != NULL) { + const openvdb::Vec3f texture_pos = this->index_to_texture * coord.asVec3s(); + openvdb::Vec3d displace_vector = this->evaluate_texture(texture_pos); + return (displace_vector - this->texture_mid_level) * this->strength; + } + return openvdb::Vec3d{0, 0, 0}; + } + + openvdb::Vec3d evaluate_texture(const openvdb::Vec3f &pos) const + { + TexResult texture_result = {0}; + BKE_texture_get_value( + NULL, this->texture, const_cast(pos.asV()), &texture_result, false); + return {texture_result.tr, texture_result.tg, texture_result.tb}; + } +}; + +static float get_max_voxel_side_length(const openvdb::GridBase &grid) +{ + const openvdb::Mat3d matrix = grid.transform().baseMap()->getAffineMap()->getMat4().getMat3(); + const float max_voxel_side_length = std::max( + {matrix.col(0).length(), matrix.col(1).length(), matrix.col(2).length()}); + return max_voxel_side_length; +} + +struct DisplaceGridOp { + /* This is the grid that will be displaced. The output is copied back to the original grid. */ + openvdb::GridBase &base_grid; + + VolumeDisplaceModifierData &vdmd; + const ModifierEvalContext &ctx; + + template void operator()() + { + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + /* We don't support displacing these grid types yet. */ + return; + } + else { + this->displace_grid(); + } + } + + template void displace_grid() + { + GridType &grid = static_cast(base_grid); + + /* Make a copy of the original grid to work on. This will replace the original grid. */ + typename GridType::Ptr temp_grid = grid.deepCopy(); + + /* Dilate grid, because the currently inactive cells might become active during the displace + * operation. The quality of the approximation of the has a big impact on performance. */ + const float max_voxel_side_length = get_max_voxel_side_length(grid); + const float sample_radius = vdmd.texture_sample_radius * std::abs(vdmd.strength) / + max_voxel_side_length / 2.0f; + openvdb::tools::dilateActiveValues(temp_grid->tree(), + static_cast(std::ceil(sample_radius)), + openvdb::tools::NN_FACE_EDGE, + openvdb::tools::EXPAND_TILES); + + const openvdb::Mat4s index_to_texture = this->get_index_to_texture_transform(); + + /* Construct the operator that will be executed on every cell of the dilated grid. */ + DisplaceOp displace_op{grid.getConstAccessor(), + index_to_texture, + vdmd.texture, + vdmd.strength / max_voxel_side_length, + openvdb::Vec3d{vdmd.texture_mid_level}}; + + /* Run the operator. This is multi-threaded. It is important that the operator is not shared + * between the threads, because it contains a non-thread-safe accessor for the old grid. */ + openvdb::tools::foreach (temp_grid->beginValueOn(), + displace_op, + true, + /* Disable sharing of the operator. */ false); + + /* It is likely that we produced too many active cells. Those are removed here, to avoid + * slowing down subsequent operations. */ + typename GridType::ValueType prune_tolerance{0}; + openvdb::tools::deactivate(*temp_grid, temp_grid->background(), prune_tolerance); + openvdb::tools::prune(temp_grid->tree()); + + /* Overwrite the old volume grid with the new grid. */ + grid.clear(); + grid.merge(*temp_grid); + } + + openvdb::Mat4s get_index_to_texture_transform() const + { + const openvdb::Mat4s index_to_object{ + base_grid.transform().baseMap()->getAffineMap()->getMat4()}; + + switch (vdmd.texture_map_mode) { + case MOD_VOLUME_DISPLACE_MAP_LOCAL: { + return index_to_object; + } + case MOD_VOLUME_DISPLACE_MAP_GLOBAL: { + const openvdb::Mat4s object_to_world = matrix_to_openvdb(ctx.object->obmat); + return object_to_world * index_to_object; + } + case MOD_VOLUME_DISPLACE_MAP_OBJECT: { + if (vdmd.texture_map_object == NULL) { + return index_to_object; + } + const openvdb::Mat4s object_to_world = matrix_to_openvdb(ctx.object->obmat); + const openvdb::Mat4s world_to_texture = matrix_to_openvdb(vdmd.texture_map_object->imat); + return world_to_texture * object_to_world * index_to_object; + } + } + return {}; + } +}; + +#endif + +static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume) +{ +#ifdef WITH_OPENVDB + VolumeDisplaceModifierData *vdmd = reinterpret_cast(md); + + /* Iterate over all grids and displace them one by one. */ + const int grid_amount = BKE_volume_num_grids(volume); + for (int grid_index = 0; grid_index < grid_amount; grid_index++) { + VolumeGrid *volume_grid = BKE_volume_grid_get(volume, grid_index); + BLI_assert(volume_grid != NULL); + + openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false); + VolumeGridType grid_type = BKE_volume_grid_type(volume_grid); + + DisplaceGridOp displace_grid_op{*grid, *vdmd, *ctx}; + BKE_volume_grid_type_operation(grid_type, displace_grid_op); + } + + return volume; +#else + UNUSED_VARS(md, ctx); + BKE_modifier_set_error(md, "Compiled without OpenVDB"); + return volume; +#endif +} + +ModifierTypeInfo modifierType_VolumeDisplace = { + /* name */ "Volume Displace", + /* structName */ "VolumeDisplaceModifierData", + /* structSize */ sizeof(VolumeDisplaceModifierData), + /* srna */ &RNA_VolumeDisplaceModifier, + /* type */ eModifierTypeType_NonGeometrical, + /* flags */ static_cast(0), + /* icon */ ICON_VOLUME_DATA, /* TODO: Use correct icon. */ + + /* copyData */ BKE_modifier_copydata_generic, + + /* deformVerts */ NULL, + /* deformMatrices */ NULL, + /* deformVertsEM */ NULL, + /* deformMatricesEM */ NULL, + /* modifyMesh */ NULL, + /* modifyHair */ NULL, + /* modifyPointCloud */ NULL, + /* modifyVolume */ modifyVolume, + + /* initData */ initData, + /* requiredDataMask */ NULL, + /* freeData */ NULL, + /* isDisabled */ NULL, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ dependsOnTime, + /* dependsOnNormals */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ foreachTexLink, + /* freeRuntimeData */ NULL, + /* panelRegister */ panelRegister, + /* blendWrite */ NULL, + /* blendRead */ NULL, +}; -- cgit v1.2.3