Geometry Nodes: Add OpenVDB grid operators

Adds four new grid operator nodes that wrap OpenVDB's differential
operators. All nodes take a grid input and output a grid, potentially
with a different data type.

New nodes:
- Grid Curl: Calculate curl of vector field (Vec3 → Vec3)
- Grid Divergence: Calculate divergence of vector field (Vec3 → Float)
- Grid Gradient: Calculate gradient of scalar field (Float → Vec3)
- Grid Laplacian: Calculate Laplacian of scalar field (Float → Float)

These operators enable vector calculus operations on volume grids for
effects like flow analysis, vortex detection, etc.

Co-authored-by: Hans Goudey <hans@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/146845
This commit is contained in:
Brady Johnston
2025-10-02 16:28:46 +02:00
committed by Hans Goudey
parent fdfbb3ad20
commit 0a77a57c4e
8 changed files with 218 additions and 0 deletions

View File

@@ -935,6 +935,10 @@ class NODE_MT_gn_volume_sample_base(node_add_menu.NodeMenu):
layout = self.layout
self.node_operator(layout, "GeometryNodeSampleGrid")
self.node_operator(layout, "GeometryNodeSampleGridIndex")
self.node_operator(layout, "GeometryNodeGridCurl")
self.node_operator(layout, "GeometryNodeGridDivergence")
self.node_operator(layout, "GeometryNodeGridGradient")
self.node_operator(layout, "GeometryNodeGridLaplacian")
self.draw_assets_for_catalog(layout, self.menu_path)

View File

@@ -10165,7 +10165,11 @@ static void rna_def_nodes(BlenderRNA *brna)
define("GeometryNode", "GeometryNodeGizmoLinear");
define("GeometryNode", "GeometryNodeGizmoTransform", rna_def_geo_gizmo_transform);
define("GeometryNode", "GeometryNodeGreasePencilToCurves");
define("GeometryNode", "GeometryNodeGridCurl");
define("GeometryNode", "GeometryNodeGridDivergence");
define("GeometryNode", "GeometryNodeGridGradient");
define("GeometryNode", "GeometryNodeGridInfo");
define("GeometryNode", "GeometryNodeGridLaplacian");
define("GeometryNode", "GeometryNodeGridToMesh");
define("GeometryNode", "GeometryNodeImageInfo");
define("GeometryNode", "GeometryNodeImageTexture", def_geo_image_texture);

View File

@@ -96,7 +96,11 @@ set(SRC
nodes/node_geo_gizmo_linear.cc
nodes/node_geo_gizmo_transform.cc
nodes/node_geo_grease_pencil_to_curves.cc
nodes/node_geo_grid_curl.cc
nodes/node_geo_grid_divergence.cc
nodes/node_geo_grid_gradient.cc
nodes/node_geo_grid_info.cc
nodes/node_geo_grid_laplacian.cc
nodes/node_geo_grid_to_mesh.cc
nodes/node_geo_image.cc
nodes/node_geo_image_info.cc

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_volume_grid.hh"
#include "node_geometry_util.hh"
#include "openvdb/tools/GridOperators.h"
namespace blender::nodes::node_geo_grid_curl_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Vector>("Grid").hide_value().structure_type(StructureType::Grid);
b.add_output<decl::Vector>("Curl").structure_type(StructureType::Grid);
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float3> grid = params.extract_input<bke::VolumeGrid<float3>>("Grid");
if (!grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::Vec3SGrid &vdb_grid = grid.grid(tree_token);
openvdb::Vec3SGrid::Ptr curl_vdb_grid = openvdb::tools::curl(vdb_grid);
params.set_output("Curl", bke::VolumeGrid<float3>(std::move(curl_vdb_grid)));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGridCurl");
ntype.ui_name = "Grid Curl";
ntype.ui_description =
"Calculate the magnitude and direction of circulation of a directional vector grid";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grid_curl_cc

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_volume_grid.hh"
#include "node_geometry_util.hh"
#include "openvdb/tools/GridOperators.h"
namespace blender::nodes::node_geo_grid_divergence_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Vector>("Grid").hide_value().structure_type(StructureType::Grid);
b.add_output<decl::Float>("Divergence").structure_type(StructureType::Grid);
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float3> grid = params.extract_input<bke::VolumeGrid<float3>>("Grid");
if (!grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::Vec3SGrid &vdb_grid = grid.grid(tree_token);
openvdb::FloatGrid::Ptr divergence_vdb_grid = openvdb::tools::divergence(vdb_grid);
params.set_output("Divergence", bke::VolumeGrid<float>(std::move(divergence_vdb_grid)));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGridDivergence");
ntype.ui_name = "Grid Divergence";
ntype.ui_description =
"Calculate the flow into and out of each point of a directional vector grid";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grid_divergence_cc

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_volume_grid.hh"
#include "node_geometry_util.hh"
#include "openvdb/tools/GridOperators.h"
namespace blender::nodes::node_geo_grid_gradient_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
b.add_output<decl::Vector>("Gradient").structure_type(StructureType::Grid);
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float> grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
if (!grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::FloatGrid &vdb_grid = grid.grid(tree_token);
openvdb::Vec3SGrid::Ptr gradient_vdb_grid = openvdb::tools::gradient(vdb_grid);
params.set_output("Gradient", bke::VolumeGrid<float3>(std::move(gradient_vdb_grid)));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGridGradient");
ntype.ui_name = "Grid Gradient";
ntype.ui_description =
"Calculate the direction and magnitude of the change in values of a scalar grid";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grid_gradient_cc

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_volume_grid.hh"
#include "node_geometry_util.hh"
#include "openvdb/tools/GridOperators.h"
namespace blender::nodes::node_geo_grid_laplacian_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Grid").hide_value().structure_type(StructureType::Grid);
b.add_output<decl::Float>("Laplacian").structure_type(StructureType::Grid);
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float> grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
if (!grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::FloatGrid &vdb_grid = grid.grid(tree_token);
openvdb::FloatGrid::Ptr laplacian_vdb_grid = openvdb::tools::laplacian(vdb_grid);
params.set_output("Laplacian", bke::VolumeGrid<float>(std::move(laplacian_vdb_grid)));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeGridLaplacian");
ntype.ui_name = "Grid Laplacian";
ntype.ui_description = "Compute the divergence of the gradient of the input grid";
ntype.nclass = NODE_CLASS_GEOMETRY;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.gather_link_search_ops = search_link_ops_for_volume_grid_node;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grid_laplacian_cc

Binary file not shown.