Subdivision node: add input for vertex creases

This adds an input to the Subdivision node to specify a field to use
for controling vertex creases. Common code with edge creasing was
extracted into utility functions to avoid redundancy.

Differential Revision: https://developer.blender.org/D14199
This commit is contained in:
Kévin Dietrich 2022-05-04 18:59:08 +02:00
parent 82df48227b
commit 1a98bec40e
2 changed files with 82 additions and 23 deletions

View File

@ -2735,6 +2735,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
FOREACH_NODETREE_END;
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
version_node_input_socket_name(
ntree, GEO_NODE_SUBDIVISION_SURFACE, "Crease", "Edge Crease");
}
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 302, 13)) {

View File

@ -1,5 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
@ -21,7 +23,13 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH);
b.add_input<decl::Int>(N_("Level")).default_value(1).min(0).max(6);
b.add_input<decl::Float>(N_("Crease"))
b.add_input<decl::Float>(N_("Edge Crease"))
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.supports_field()
.subtype(PROP_FACTOR);
b.add_input<decl::Float>(N_("Vertex Crease"))
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
@ -44,6 +52,45 @@ static void node_init(bNodeTree *UNUSED(ntree), bNode *node)
node->storage = data;
}
#ifdef WITH_OPENSUBDIV
static void materialize_and_clamp_creases(const VArray<float> &crease_varray,
MutableSpan<float> creases)
{
threading::parallel_for(creases.index_range(), 1024, [&](IndexRange range) {
crease_varray.materialize(range, creases);
for (const int i : range) {
creases[i] = std::clamp(creases[i], 0.0f, 1.0f);
}
});
}
static void write_vertex_creases(Mesh &mesh, const VArray<float> &crease_varray)
{
float *crease;
if (CustomData_has_layer(&mesh.vdata, CD_CREASE)) {
crease = static_cast<float *>(CustomData_get_layer(&mesh.vdata, CD_CREASE));
}
else {
crease = static_cast<float *>(
CustomData_add_layer(&mesh.vdata, CD_CREASE, CD_DEFAULT, nullptr, mesh.totvert));
}
materialize_and_clamp_creases(crease_varray, {crease, mesh.totvert});
}
static void write_edge_creases(MeshComponent &mesh, const VArray<float> &crease_varray)
{
OutputAttribute_Typed<float> attribute = mesh.attribute_try_get_for_output_only<float>(
"crease", ATTR_DOMAIN_EDGE);
materialize_and_clamp_creases(crease_varray, attribute.as_span());
attribute.save();
}
static bool varray_is_nonzero(const VArray<float> &varray)
{
return !(varray.is_single() && varray.get_internal_single() == 0.0f);
}
#endif
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
@ -51,7 +98,8 @@ static void node_geo_exec(GeoNodeExecParams params)
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenSubdiv"));
#else
Field<float> crease_field = params.extract_input<Field<float>>("Crease");
Field<float> edge_crease_field = params.extract_input<Field<float>>("Edge Crease");
Field<float> vertex_crease_field = params.extract_input<Field<float>>("Vertex Crease");
const NodeGeometrySubdivisionSurface &storage = node_storage(params.node());
const int uv_smooth = storage.uv_smooth;
@ -69,27 +117,31 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
AttributeDomain domain = ATTR_DOMAIN_EDGE;
GeometryComponentFieldContext field_context{mesh_component, domain};
const int domain_size = mesh_component.attribute_domain_size(domain);
if (domain_size == 0) {
const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>();
const int verts_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT);
const int edges_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE);
if (verts_num == 0 || edges_num == 0) {
return;
}
FieldEvaluator evaluator(field_context, domain_size);
evaluator.add(crease_field);
evaluator.evaluate();
const VArray<float> &creases = evaluator.get_evaluated<float>(0);
GeometryComponentFieldContext point_context{mesh_component, ATTR_DOMAIN_POINT};
FieldEvaluator point_evaluator(point_context, verts_num);
point_evaluator.add(vertex_crease_field);
point_evaluator.evaluate();
const VArray<float> vertex_creases = point_evaluator.get_evaluated<float>(0);
OutputAttribute_Typed<float> crease = mesh_component.attribute_try_get_for_output_only<float>(
"crease", domain);
MutableSpan<float> crease_span = crease.as_span();
for (auto i : creases.index_range()) {
crease_span[i] = std::clamp(creases[i], 0.0f, 1.0f);
GeometryComponentFieldContext edge_context{mesh_component, ATTR_DOMAIN_EDGE};
FieldEvaluator edge_evaluator(edge_context, edges_num);
edge_evaluator.add(edge_crease_field);
edge_evaluator.evaluate();
const VArray<float> edge_creases = edge_evaluator.get_evaluated<float>(0);
const bool use_creases = varray_is_nonzero(vertex_creases) || varray_is_nonzero(edge_creases);
if (use_creases) {
write_vertex_creases(*geometry_set.get_mesh_for_write(), vertex_creases);
write_edge_creases(geometry_set.get_component_for_write<MeshComponent>(), edge_creases);
}
crease.save();
/* Initialize mesh settings. */
SubdivToMeshSettings mesh_settings;
@ -100,7 +152,7 @@ static void node_geo_exec(GeoNodeExecParams params)
SubdivSettings subdiv_settings;
subdiv_settings.is_simple = false;
subdiv_settings.is_adaptive = false;
subdiv_settings.use_creases = !(creases.is_single() && creases.get_internal_single() == 0.0f);
subdiv_settings.use_creases = use_creases;
subdiv_settings.level = subdiv_level;
subdiv_settings.vtx_boundary_interpolation =
@ -108,19 +160,19 @@ static void node_geo_exec(GeoNodeExecParams params)
subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth(
uv_smooth);
Mesh *mesh_in = mesh_component.get_for_write();
const Mesh &mesh_in = *geometry_set.get_mesh_for_read();
/* Apply subdivision to mesh. */
Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, mesh_in);
Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, &mesh_in);
/* In case of bad topology, skip to input mesh. */
if (subdiv == nullptr) {
return;
}
Mesh *mesh_out = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh_in);
Mesh *mesh_out = BKE_subdiv_to_mesh(subdiv, &mesh_settings, &mesh_in);
mesh_component.replace(mesh_out);
geometry_set.replace_mesh(mesh_out);
BKE_subdiv_free(subdiv);
});