Geometry Nodes: use smooth normals in Distribute Points on Faces node
buildbot/vdev-code-daily-coordinator Build done. Details

Previously, the node used the "true" normal of every looptri. Now it uses the
"loop normals" which includes e.g. smooth faces and custom normals. The true
normal can still be used on the points by capturing it before the Distribute node.

We do intend to expose the smooth normals separately in geometry nodes as well,
but this is an important first step.

It's also necessary to generate child hair between guide hair strands that don't
have visible artifacts at face boundaries.

For perfect backward compatibility, the node still has a "Legacy Normal" option
in the side bar. Creating the exact same behavior with existing nodes isn't
really possible unfortunately because of the specifics of how the Distribute
node used to compute the normals using looptris.

Pull Request #104414
This commit is contained in:
Jacques Lucke 2023-02-11 13:25:59 +01:00
parent 6478eb565a
commit 0f708fa2e3
4 changed files with 114 additions and 29 deletions

View File

@ -25,7 +25,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 8
#define BLENDER_FILE_SUBVERSION 9
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -3892,6 +3892,21 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 305, 9)) {
/* Enable legacy normal and rotation outputs in Distribute Points on Faces node. */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type != NTREE_GEOMETRY) {
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type != GEO_NODE_DISTRIBUTE_POINTS_ON_FACES) {
continue;
}
node->custom2 = true;
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -9645,6 +9645,14 @@ static void def_geo_distribute_points_on_faces(StructRNA *srna)
RNA_def_property_enum_default(prop, GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM);
RNA_def_property_ui_text(prop, "Distribution Method", "Method to use for scattering points");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "use_legacy_normal", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "custom2", 1);
RNA_def_property_ui_text(prop,
"Legacy Normal",
"Output the normal and rotation values that have been output "
"before the node started taking smooth normals into account");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_curve_spline_type(StructRNA *srna)

View File

@ -67,6 +67,11 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE);
}
static void node_layout_ex(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "use_legacy_normal", 0, nullptr, ICON_NONE);
}
static void node_point_distribute_points_on_faces_update(bNodeTree *ntree, bNode *node)
{
bNodeSocket *sock_distance_min = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 2));
@ -325,11 +330,73 @@ struct AttributeOutputs {
};
} // namespace
static void compute_normal_outputs(const Mesh &mesh,
const Span<float3> bary_coords,
const Span<int> looptri_indices,
MutableSpan<float3> r_normals)
{
Array<float3> corner_normals(mesh.totloop);
BKE_mesh_calc_normals_split_ex(
const_cast<Mesh *>(&mesh), nullptr, reinterpret_cast<float(*)[3]>(corner_normals.data()));
const Span<MLoopTri> looptris = mesh.looptris();
threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) {
for (const int i : range) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const float3 &bary_coord = bary_coords[i];
const float3 normal = math::normalize(
bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords(
bary_coord, looptri, corner_normals.as_span()));
r_normals[i] = normal;
}
});
}
static void compute_legacy_normal_outputs(const Mesh &mesh,
const Span<float3> bary_coords,
const Span<int> looptri_indices,
MutableSpan<float3> r_normals)
{
const Span<float3> positions = mesh.vert_positions();
const Span<MLoop> loops = mesh.loops();
const Span<MLoopTri> looptris = mesh.looptris();
for (const int i : bary_coords.index_range()) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const int v0_index = loops[looptri.tri[0]].v;
const int v1_index = loops[looptri.tri[1]].v;
const int v2_index = loops[looptri.tri[2]].v;
const float3 v0_pos = positions[v0_index];
const float3 v1_pos = positions[v1_index];
const float3 v2_pos = positions[v2_index];
float3 normal;
normal_tri_v3(normal, v0_pos, v1_pos, v2_pos);
r_normals[i] = normal;
}
}
static void compute_rotation_output(const Span<float3> normals, MutableSpan<float3> r_rotations)
{
threading::parallel_for(normals.index_range(), 256, [&](const IndexRange range) {
for (const int i : range) {
r_rotations[i] = normal_to_euler_rotation(normals[i]);
}
});
}
BLI_NOINLINE static void compute_attribute_outputs(const Mesh &mesh,
PointCloud &points,
const Span<float3> bary_coords,
const Span<int> looptri_indices,
const AttributeOutputs &attribute_outputs)
const AttributeOutputs &attribute_outputs,
const bool use_legacy_normal)
{
MutableAttributeAccessor point_attributes = points.attributes_for_write();
@ -348,33 +415,24 @@ BLI_NOINLINE static void compute_attribute_outputs(const Mesh &mesh,
attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT);
}
const Span<float3> positions = mesh.vert_positions();
const Span<MLoop> loops = mesh.loops();
const Span<MLoopTri> looptris = mesh.looptris();
for (const int i : bary_coords.index_range()) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const float3 &bary_coord = bary_coords[i];
const int v0_index = loops[looptri.tri[0]].v;
const int v1_index = loops[looptri.tri[1]].v;
const int v2_index = loops[looptri.tri[2]].v;
const float3 v0_pos = positions[v0_index];
const float3 v1_pos = positions[v1_index];
const float3 v2_pos = positions[v2_index];
ids.span[i] = noise::hash(noise::hash_float(bary_coord), looptri_index);
float3 normal;
if (!normals.span.is_empty() || !rotations.span.is_empty()) {
normal_tri_v3(normal, v0_pos, v1_pos, v2_pos);
threading::parallel_for(bary_coords.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
const int looptri_index = looptri_indices[i];
const float3 &bary_coord = bary_coords[i];
ids.span[i] = noise::hash(noise::hash_float(bary_coord), looptri_index);
}
if (!normals.span.is_empty()) {
normals.span[i] = normal;
});
if (normals) {
if (use_legacy_normal) {
compute_legacy_normal_outputs(mesh, bary_coords, looptri_indices, normals.span);
}
if (!rotations.span.is_empty()) {
rotations.span[i] = normal_to_euler_rotation(normal);
else {
compute_normal_outputs(mesh, bary_coords, looptri_indices, normals.span);
}
if (rotations) {
compute_rotation_output(normals.span, rotations.span);
}
}
@ -507,7 +565,9 @@ static void point_distribution_calculate(GeometrySet &geometry_set,
propagate_existing_attributes(mesh, attributes, *pointcloud, bary_coords, looptri_indices);
compute_attribute_outputs(mesh, *pointcloud, bary_coords, looptri_indices, attribute_outputs);
const bool use_legacy_normal = params.node().custom2 != 0;
compute_attribute_outputs(
mesh, *pointcloud, bary_coords, looptri_indices, attribute_outputs, use_legacy_normal);
}
static void node_geo_exec(GeoNodeExecParams params)
@ -521,8 +581,9 @@ static void node_geo_exec(GeoNodeExecParams params)
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
AttributeOutputs attribute_outputs;
attribute_outputs.normal_id = params.get_output_anonymous_attribute_id_if_needed("Normal");
attribute_outputs.rotation_id = params.get_output_anonymous_attribute_id_if_needed("Rotation");
attribute_outputs.normal_id = params.get_output_anonymous_attribute_id_if_needed(
"Normal", bool(attribute_outputs.rotation_id));
lazy_threading::send_hint();
@ -567,5 +628,6 @@ void register_node_type_geo_distribute_points_on_faces()
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.draw_buttons_ex = file_ns::node_layout_ex;
nodeRegisterType(&ntype);
}