Geometry Nodes: use smooth normals in Distribute Points on Faces node
buildbot/vdev-code-daily-coordinator Build done.
Details
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:
parent
6478eb565a
commit
0f708fa2e3
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue