Geometry Nodes: Volume grid Prune and Voxelize nodes
Add two common building blocks for volume-grid workflows. - **Voxelize** turns all active tiles into fully dense voxels. For fog volumes, this will mean the "inside" sparse tiles will become individually adjustable voxel values. - **Prune** is the opposite action as voxelize. It can be important for certain workflows when large regions of constant values are created. The node can collapsed those regions into more efficient tiles or inner nodes. There are a few modes which are each useful for different use cases. Pull Request: https://projects.blender.org/blender/blender/pulls/147148
This commit is contained in:
@@ -955,6 +955,8 @@ class NODE_MT_gn_volume_operations_base(node_add_menu.NodeMenu):
|
||||
self.node_operator(layout, "GeometryNodeGridToMesh")
|
||||
self.node_operator(layout, "GeometryNodeSDFGridBoolean")
|
||||
self.node_operator(layout, "GeometryNodeFieldToGrid")
|
||||
self.node_operator(layout, "GeometryNodeGridPrune")
|
||||
self.node_operator(layout, "GeometryNodeGridVoxelize")
|
||||
|
||||
self.draw_assets_for_catalog(layout, self.menu_path)
|
||||
|
||||
|
||||
@@ -112,6 +112,9 @@ void set_mask_leaf_buffer_from_bools(openvdb::BoolGrid &grid,
|
||||
|
||||
void set_grid_background(openvdb::GridBase &grid_base, const GPointer value);
|
||||
|
||||
/** See #openvdb::tools::pruneInactive. */
|
||||
void prune_inactive(openvdb::GridBase &grid_base);
|
||||
|
||||
} // namespace blender::bke::volume_grid
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include <openvdb/Grid.h>
|
||||
# include <openvdb/tools/Prune.h>
|
||||
#endif
|
||||
|
||||
namespace blender::bke::volume_grid {
|
||||
@@ -800,6 +801,11 @@ void set_grid_background(openvdb::GridBase &grid_base, const GPointer value)
|
||||
});
|
||||
}
|
||||
|
||||
void prune_inactive(openvdb::GridBase &grid_base)
|
||||
{
|
||||
to_typed_grid(grid_base, [&](auto &grid) { openvdb::tools::pruneInactive(grid.tree()); });
|
||||
}
|
||||
|
||||
#endif /* WITH_OPENVDB */
|
||||
|
||||
} // namespace blender::bke::volume_grid
|
||||
|
||||
@@ -10170,7 +10170,9 @@ static void rna_def_nodes(BlenderRNA *brna)
|
||||
define("GeometryNode", "GeometryNodeGridGradient");
|
||||
define("GeometryNode", "GeometryNodeGridInfo");
|
||||
define("GeometryNode", "GeometryNodeGridLaplacian");
|
||||
define("GeometryNode", "GeometryNodeGridPrune");
|
||||
define("GeometryNode", "GeometryNodeGridToMesh");
|
||||
define("GeometryNode", "GeometryNodeGridVoxelize");
|
||||
define("GeometryNode", "GeometryNodeImageInfo");
|
||||
define("GeometryNode", "GeometryNodeImageTexture", def_geo_image_texture);
|
||||
define("GeometryNode", "GeometryNodeImportCSV");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "BKE_texture.h"
|
||||
#include "BKE_volume.hh"
|
||||
#include "BKE_volume_grid.hh"
|
||||
#include "BKE_volume_grid_process.hh"
|
||||
#include "BKE_volume_openvdb.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
@@ -235,7 +236,7 @@ struct DisplaceGridOp {
|
||||
* 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());
|
||||
blender::bke::volume_grid::prune_inactive(*temp_grid);
|
||||
|
||||
/* Overwrite the old volume grid with the new grid. */
|
||||
grid.clear();
|
||||
|
||||
@@ -101,7 +101,9 @@ set(SRC
|
||||
nodes/node_geo_grid_gradient.cc
|
||||
nodes/node_geo_grid_info.cc
|
||||
nodes/node_geo_grid_laplacian.cc
|
||||
nodes/node_geo_grid_prune.cc
|
||||
nodes/node_geo_grid_to_mesh.cc
|
||||
nodes/node_geo_grid_voxelize.cc
|
||||
nodes/node_geo_image.cc
|
||||
nodes/node_geo_image_info.cc
|
||||
nodes/node_geo_image_texture.cc
|
||||
|
||||
250
source/blender/nodes/geometry/nodes/node_geo_grid_prune.cc
Normal file
250
source/blender/nodes/geometry/nodes/node_geo_grid_prune.cc
Normal file
@@ -0,0 +1,250 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
#include "BKE_volume_grid_process.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include "openvdb/tools/Prune.h"
|
||||
#endif
|
||||
|
||||
namespace blender::nodes::node_geo_grid_prune_cc {
|
||||
|
||||
enum class Mode : int16_t {
|
||||
Inactive = 0,
|
||||
Threshold = 1,
|
||||
SDF = 2,
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_default_layout();
|
||||
const bNode *node = b.node_or_null();
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
|
||||
b.add_input(data_type, "Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output(data_type, "Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
static EnumPropertyItem mode_items[] = {
|
||||
{int(Mode::Inactive),
|
||||
"INACTIVE",
|
||||
0,
|
||||
"Inactive",
|
||||
"Turn inactive voxels and tiles into inactive background tiles"},
|
||||
{int(Mode::Threshold),
|
||||
"THRESHOLD",
|
||||
0,
|
||||
"Threshold",
|
||||
"Turn regions where all voxels have the same value and active state (within a tolerance "
|
||||
"threshold) into inactive background tiles"},
|
||||
{int(Mode::SDF),
|
||||
"SDF",
|
||||
0,
|
||||
"SDF",
|
||||
"Replace inactive tiles with inactive nodes. Faster than tolerance-based pruning, useful "
|
||||
"for cases like narrow-band SDF grids with only inside or outside background values."},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
b.add_input<decl::Menu>("Mode")
|
||||
.static_items(mode_items)
|
||||
.default_value(MenuValue(Mode::Threshold))
|
||||
.structure_type(StructureType::Single)
|
||||
.optional_label();
|
||||
if (data_type != SOCK_BOOLEAN) {
|
||||
auto &threshold = b.add_input(data_type, "Threshold")
|
||||
.structure_type(StructureType::Single)
|
||||
.usage_by_single_menu(int(Mode::Threshold));
|
||||
switch (data_type) {
|
||||
case SOCK_FLOAT: {
|
||||
auto &threshold_typed = static_cast<decl::FloatBuilder &>(threshold);
|
||||
threshold_typed.min(0.0f).default_value(0.01f);
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
auto &threshold_typed = static_cast<decl::VectorBuilder &>(threshold);
|
||||
threshold_typed.min(0.0f).default_value(float3(0.01f));
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
auto &threshold_typed = static_cast<decl::IntBuilder &>(threshold);
|
||||
threshold_typed.min(0).default_value(0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static std::optional<eNodeSocketDatatype> node_type_for_socket_type(const bNodeSocket &socket)
|
||||
{
|
||||
switch (socket.type) {
|
||||
case SOCK_FLOAT:
|
||||
return SOCK_FLOAT;
|
||||
case SOCK_BOOLEAN:
|
||||
return SOCK_BOOLEAN;
|
||||
case SOCK_INT:
|
||||
return SOCK_INT;
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
return SOCK_VECTOR;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (!USER_EXPERIMENTAL_TEST(&U, use_new_volume_nodes)) {
|
||||
return;
|
||||
}
|
||||
const std::optional<eNodeSocketDatatype> data_type = node_type_for_socket_type(
|
||||
params.other_socket());
|
||||
if (!data_type) {
|
||||
return;
|
||||
}
|
||||
params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeGridPrune");
|
||||
node.custom1 = *data_type;
|
||||
params.update_and_connect_available_socket(node, "Grid");
|
||||
});
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
bke::GVolumeGrid grid = params.extract_input<bke::GVolumeGrid>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::GridBase &grid_base = grid.get_for_write().grid_for_write(tree_token);
|
||||
switch (params.extract_input<Mode>("Mode")) {
|
||||
case Mode::Inactive: {
|
||||
bke::volume_grid::prune_inactive(grid_base);
|
||||
break;
|
||||
}
|
||||
case Mode::Threshold: {
|
||||
const VolumeGridType grid_type = bke::volume_grid::get_type(grid_base);
|
||||
switch (grid_type) {
|
||||
case VOLUME_GRID_BOOLEAN: {
|
||||
auto &grid = static_cast<openvdb::BoolGrid &>(grid_base);
|
||||
openvdb::tools::prune(grid.tree());
|
||||
break;
|
||||
}
|
||||
case VOLUME_GRID_MASK: {
|
||||
auto &grid = static_cast<openvdb::MaskGrid &>(grid_base);
|
||||
openvdb::tools::prune(grid.tree());
|
||||
break;
|
||||
}
|
||||
case VOLUME_GRID_FLOAT: {
|
||||
auto &grid = static_cast<openvdb::FloatGrid &>(grid_base);
|
||||
const float threshold = params.extract_input<float>("Threshold");
|
||||
openvdb::tools::prune(grid.tree(), threshold);
|
||||
break;
|
||||
}
|
||||
case VOLUME_GRID_INT: {
|
||||
auto &grid = static_cast<openvdb::Int32Grid &>(grid_base);
|
||||
const int threshold = params.extract_input<int>("Threshold");
|
||||
openvdb::tools::prune(grid.tree(), threshold);
|
||||
break;
|
||||
}
|
||||
case VOLUME_GRID_VECTOR_FLOAT: {
|
||||
auto &grid = static_cast<openvdb::Vec3fGrid &>(grid_base);
|
||||
const float3 threshold = params.extract_input<float3>("Threshold");
|
||||
openvdb::tools::prune(grid.tree(),
|
||||
openvdb::Vec3s(threshold.x, threshold.y, threshold.z));
|
||||
break;
|
||||
}
|
||||
case VOLUME_GRID_UNKNOWN:
|
||||
case VOLUME_GRID_DOUBLE:
|
||||
case VOLUME_GRID_INT64:
|
||||
case VOLUME_GRID_VECTOR_DOUBLE:
|
||||
case VOLUME_GRID_VECTOR_INT:
|
||||
case VOLUME_GRID_POINTS: {
|
||||
params.error_message_add(NodeWarningType::Error, "Unsupported grid type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mode::SDF: {
|
||||
const VolumeGridType grid_type = bke::volume_grid::get_type(grid_base);
|
||||
BKE_volume_grid_type_to_static_type(grid_type, [&](auto type_tag) {
|
||||
using GridT = typename decltype(type_tag)::type;
|
||||
if constexpr (bke::volume_grid::is_supported_grid_type<GridT>) {
|
||||
if constexpr (std::is_scalar_v<typename GridT::ValueType>) {
|
||||
GridT &grid = static_cast<GridT &>(grid_base);
|
||||
openvdb::tools::pruneLevelSet(grid.tree());
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = SOCK_FLOAT;
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
RNA_def_node_enum(srna,
|
||||
"data_type",
|
||||
"Data Type",
|
||||
"Node socket data type",
|
||||
rna_enum_node_socket_data_type_items,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
SOCK_FLOAT,
|
||||
grid_socket_type_items_filter_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeGridPrune");
|
||||
ntype.ui_name = "Prune Grid";
|
||||
ntype.ui_description =
|
||||
"Make the storage of a volume grid more efficient by collapsing data into tiles or inner "
|
||||
"nodes";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.gather_link_search_ops = node_gather_link_search_ops;
|
||||
ntype.geometry_node_execute = node_geo_exec;
|
||||
blender::bke::node_register_type(ntype);
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_geo_grid_prune_cc
|
||||
126
source/blender/nodes/geometry/nodes/node_geo_grid_voxelize.cc
Normal file
126
source/blender/nodes/geometry/nodes/node_geo_grid_voxelize.cc
Normal file
@@ -0,0 +1,126 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_volume_grid.hh"
|
||||
#include "BKE_volume_grid_process.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes::node_geo_grid_voxelize_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_default_layout();
|
||||
const bNode *node = b.node_or_null();
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
|
||||
b.add_input(data_type, "Grid").hide_value().structure_type(StructureType::Grid);
|
||||
b.add_output(data_type, "Grid").structure_type(StructureType::Grid).align_with_previous();
|
||||
}
|
||||
|
||||
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static std::optional<eNodeSocketDatatype> node_type_for_socket_type(const bNodeSocket &socket)
|
||||
{
|
||||
switch (socket.type) {
|
||||
case SOCK_FLOAT:
|
||||
return SOCK_FLOAT;
|
||||
case SOCK_BOOLEAN:
|
||||
return SOCK_BOOLEAN;
|
||||
case SOCK_INT:
|
||||
return SOCK_INT;
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
return SOCK_VECTOR;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (!USER_EXPERIMENTAL_TEST(&U, use_new_volume_nodes)) {
|
||||
return;
|
||||
}
|
||||
const std::optional<eNodeSocketDatatype> data_type = node_type_for_socket_type(
|
||||
params.other_socket());
|
||||
if (!data_type) {
|
||||
return;
|
||||
}
|
||||
params.add_item(IFACE_("Grid"), [data_type](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeGridVoxelize");
|
||||
node.custom1 = *data_type;
|
||||
params.update_and_connect_available_socket(node, "Grid");
|
||||
});
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
bke::GVolumeGrid grid = params.extract_input<bke::GVolumeGrid>("Grid");
|
||||
if (!grid) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
bke::VolumeTreeAccessToken tree_token;
|
||||
openvdb::GridBase &vdb_grid = grid.get_for_write().grid_for_write(tree_token);
|
||||
bke::volume_grid::to_typed_grid(vdb_grid,
|
||||
[&](auto &grid) { grid.tree().voxelizeActiveTiles(); });
|
||||
params.set_output("Grid", std::move(grid));
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = SOCK_FLOAT;
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
RNA_def_node_enum(srna,
|
||||
"data_type",
|
||||
"Data Type",
|
||||
"Node socket data type",
|
||||
rna_enum_node_socket_data_type_items,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
SOCK_FLOAT,
|
||||
grid_socket_type_items_filter_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
geo_node_type_base(&ntype, "GeometryNodeGridVoxelize");
|
||||
ntype.ui_name = "Voxelize Grid";
|
||||
ntype.ui_description =
|
||||
"Remove sparseness from a volume grid by making the active tiles into voxels";
|
||||
ntype.nclass = NODE_CLASS_GEOMETRY;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.gather_link_search_ops = node_gather_link_search_ops;
|
||||
ntype.geometry_node_execute = node_geo_exec;
|
||||
blender::bke::node_register_type(ntype);
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_geo_grid_voxelize_cc
|
||||
Reference in New Issue
Block a user