Geometry Nodes: new Volume Cube node

This commit adds a Volume Cube primitive node. It outputs a volume that
contains a single "density" float grid. The density per voxel can be
controlled with a field that depends on the voxel position (using the
existing Position node). Other field inputs are not supported.

The density field is evaluated on every voxel.

Possible future improvements are listed in D15198.

Differential Revision: https://developer.blender.org/D15198
This commit is contained in:
Chris Clyne 2022-06-17 13:26:22 +02:00 committed by Jacques Lucke
parent 75489b5887
commit 838c4a97f1
Notes: blender-bot 2023-02-14 10:32:59 +01:00
Referenced by issue #98940, Texture nodes CPU evaluation design
7 changed files with 203 additions and 0 deletions

View File

@ -714,6 +714,7 @@ geometry_node_categories = [
NodeItem("ShaderNodeVectorRotate"),
]),
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
NodeItem("GeometryNodeVolumeCube"),
NodeItem("GeometryNodeVolumeToMesh"),
]),
GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items),

View File

@ -1495,6 +1495,7 @@ struct TexResult;
#define GEO_NODE_REMOVE_ATTRIBUTE 1158
#define GEO_NODE_INPUT_INSTANCE_ROTATION 1159
#define GEO_NODE_INPUT_INSTANCE_SCALE 1160
#define GEO_NODE_VOLUME_CUBE 1161
/** \} */

View File

@ -4836,6 +4836,7 @@ static void registerGeometryNodes()
register_node_type_geo_translate_instances();
register_node_type_geo_triangulate();
register_node_type_geo_viewer();
register_node_type_geo_volume_cube();
register_node_type_geo_volume_to_mesh();
}

View File

@ -135,6 +135,7 @@ void register_node_type_geo_transform(void);
void register_node_type_geo_translate_instances(void);
void register_node_type_geo_triangulate(void);
void register_node_type_geo_viewer(void);
void register_node_type_geo_volume_cube(void);
void register_node_type_geo_volume_to_mesh(void);
#ifdef __cplusplus

View File

@ -397,6 +397,7 @@ DefNode(GeometryNode, GEO_NODE_TRANSLATE_INSTANCES, 0, "TRANSLATE_INSTANCES", Tr
DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "")
DefNode(GeometryNode, GEO_NODE_TRIM_CURVE, def_geo_curve_trim, "TRIM_CURVE", TrimCurve, "Trim Curve", "")
DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer", "")
DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "")
DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "")
/* undefine macros */

View File

@ -144,6 +144,7 @@ set(SRC
nodes/node_geo_translate_instances.cc
nodes/node_geo_triangulate.cc
nodes/node_geo_viewer.cc
nodes/node_geo_volume_cube.cc
nodes/node_geo_volume_to_mesh.cc
node_geometry_exec.cc

View File

@ -0,0 +1,197 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
# include <openvdb/tools/Dense.h>
# include <openvdb/tools/LevelSetUtil.h>
# include <openvdb/tools/ParticlesToLevelSet.h>
#endif
#include "node_geometry_util.hh"
#include "DNA_mesh_types.h"
#include "BLI_task.hh"
#include "BKE_geometry_set.hh"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_volume.h"
namespace blender::nodes::node_geo_volume_cube_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>(N_("Density"))
.description(N_("Volume density per voxel"))
.supports_field()
.default_value(1.0f);
b.add_input<decl::Float>(N_("Background"))
.description(N_("Value for voxels outside of the cube"));
b.add_input<decl::Vector>(N_("Min"))
.description(N_("Minimum boundary of volume"))
.default_value(float3(-1.0f));
b.add_input<decl::Vector>(N_("Max"))
.description(N_("Maximum boundary of volume"))
.default_value(float3(1.0f));
b.add_input<decl::Int>(N_("Resolution X"))
.description(N_("Number of voxels in the X axis"))
.default_value(32)
.min(2);
b.add_input<decl::Int>(N_("Resolution Y"))
.description(N_("Number of voxels in the Y axis"))
.default_value(32)
.min(2);
b.add_input<decl::Int>(N_("Resolution Z"))
.description(N_("Number of voxels in the Z axis"))
.default_value(32)
.min(2);
b.add_output<decl::Geometry>(N_("Volume"));
}
static float map(const float x,
const float in_min,
const float in_max,
const float out_min,
const float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
class Grid3DFieldContext : public FieldContext {
private:
int3 resolution_;
float3 bounds_min_;
float3 bounds_max_;
public:
Grid3DFieldContext(const int3 resolution, const float3 bounds_min, const float3 bounds_max)
: resolution_(resolution), bounds_min_(bounds_min), bounds_max_(bounds_max)
{
}
int64_t points_num() const
{
return static_cast<int64_t>(resolution_.x) * static_cast<int64_t>(resolution_.y) *
static_cast<int64_t>(resolution_.z);
}
GVArray get_varray_for_input(const FieldInput &field_input,
const IndexMask UNUSED(mask),
ResourceScope &UNUSED(scope)) const
{
const bke::AttributeFieldInput *attribute_field_input =
dynamic_cast<const bke::AttributeFieldInput *>(&field_input);
if (attribute_field_input == nullptr) {
return {};
}
if (attribute_field_input->attribute_name() != "position") {
return {};
}
Array<float3> positions(this->points_num());
threading::parallel_for(IndexRange(resolution_.x), 1, [&](const IndexRange x_range) {
/* Start indexing at current X slice. */
int64_t index = x_range.start() * resolution_.y * resolution_.z;
for (const int64_t x_i : x_range) {
const float x = map(x_i, 0.0f, resolution_.x - 1, bounds_min_.x, bounds_max_.x);
for (const int64_t y_i : IndexRange(resolution_.y)) {
const float y = map(y_i, 0.0f, resolution_.y - 1, bounds_min_.y, bounds_max_.y);
for (const int64_t z_i : IndexRange(resolution_.z)) {
const float z = map(z_i, 0.0f, resolution_.z - 1, bounds_min_.z, bounds_max_.z);
positions[index] = float3(x, y, z);
index++;
}
}
}
});
return VArray<float3>::ForContainer(std::move(positions));
}
};
#ifdef WITH_OPENVDB
static void node_geo_exec(GeoNodeExecParams params)
{
const float3 bounds_min = params.extract_input<float3>("Min");
const float3 bounds_max = params.extract_input<float3>("Max");
const int3 resolution = int3(params.extract_input<int>("Resolution X"),
params.extract_input<int>("Resolution Y"),
params.extract_input<int>("Resolution Z"));
if (resolution.x < 2 || resolution.y < 2 || resolution.z < 2) {
params.error_message_add(NodeWarningType::Error, TIP_("Resolution must be greater than 1"));
params.set_default_remaining_outputs();
return;
}
if (bounds_min.x == bounds_max.x || bounds_min.y == bounds_max.y ||
bounds_min.z == bounds_max.z) {
params.error_message_add(NodeWarningType::Error,
TIP_("Bounding box volume must be greater than 0"));
params.set_default_remaining_outputs();
return;
}
Field<float> input_field = params.extract_input<Field<float>>("Density");
/* Evaluate input field on a 3D grid. */
Grid3DFieldContext context(resolution, bounds_min, bounds_max);
FieldEvaluator evaluator(context, context.points_num());
Array<float> densities(context.points_num());
evaluator.add_with_destination(std::move(input_field), densities.as_mutable_span());
evaluator.evaluate();
/* Store resulting values in openvdb grid. */
const float background = params.extract_input<float>("Background");
openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(background);
grid->setGridClass(openvdb::GRID_FOG_VOLUME);
openvdb::tools::Dense<float, openvdb::tools::LayoutZYX> dense_grid{
openvdb::math::CoordBBox({0, 0, 0}, {resolution.x - 1, resolution.y - 1, resolution.z - 1}),
densities.data()};
openvdb::tools::copyFromDense(dense_grid, *grid, 0.0f);
grid->transform().preTranslate(openvdb::math::Vec3<float>(-0.5f));
const float3 scale_fac = (bounds_max - bounds_min) / float3(resolution - 1);
grid->transform().postScale(openvdb::math::Vec3<float>(scale_fac.x, scale_fac.y, scale_fac.z));
grid->transform().postTranslate(
openvdb::math::Vec3<float>(bounds_min.x, bounds_min.y, bounds_min.z));
Volume *volume = (Volume *)BKE_id_new_nomain(ID_VO, nullptr);
BKE_volume_init_grids(volume);
BKE_volume_grid_add_vdb(*volume, "density", std::move(grid));
GeometrySet r_geometry_set;
r_geometry_set.replace_volume(volume);
params.set_output("Volume", r_geometry_set);
}
#else
static void node_geo_exec(GeoNodeExecParams params)
{
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenVDB"));
params.set_default_remaining_outputs();
}
#endif /* WITH_OPENVDB */
} // namespace blender::nodes::node_geo_volume_cube_cc
void register_node_type_geo_volume_cube()
{
namespace file_ns = blender::nodes::node_geo_volume_cube_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_VOLUME_CUBE, "Volume Cube", NODE_CLASS_GEOMETRY);
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
nodeRegisterType(&ntype);
}