From b36eb690387960fe5ea75d5e36c0dda240c5d460 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Jan 2025 23:57:32 +0100 Subject: [PATCH] Geometry Nodes: Output proper face corner normals Previously, when evaluated on the face corner domain, the normal input node just returned the face normals, as if the mesh was completely flat shaded. This ignores face and edge smoothness, and custom face corner normals. In the past couple years the expected behavior of accessing normals has become much clearer and this behavior is clearly a mistake in retrospect. This commit exposes the same face corner normals used everywhere else in Blender when the node is evaluated on the corner domain. The old behavior is accessible with a node property in the sidebar. There is versioning so old files have the property set and get the same results. This is split from !132583. Pull Request: https://projects.blender.org/blender/blender/pulls/133340 --- source/blender/blenkernel/BKE_blender_version.h | 2 +- .../blender/blenkernel/BKE_geometry_fields.hh | 10 ++++++++-- .../intern/geometry_component_mesh.cc | 14 +++++++------- .../blenkernel/intern/geometry_fields.cc | 2 +- .../blender/blenloader/intern/versioning_400.cc | 17 +++++++++++++++++ source/blender/makesrna/intern/rna_nodetree.cc | 13 ++++++++++++- .../geometry/nodes/node_geo_input_normal.cc | 11 ++++++++++- 7 files changed, 56 insertions(+), 13 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 1cc3f9aabfa..89194f19b9e 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -31,7 +31,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 23 +#define BLENDER_FILE_SUBVERSION 24 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_geometry_fields.hh b/source/blender/blenkernel/BKE_geometry_fields.hh index 88cba1bc15a..e16eec52cf2 100644 --- a/source/blender/blenkernel/BKE_geometry_fields.hh +++ b/source/blender/blenkernel/BKE_geometry_fields.hh @@ -375,11 +375,17 @@ class IDAttributeFieldInput : public GeometryFieldInput { VArray curve_normals_varray(const CurvesGeometry &curves, AttrDomain domain); -VArray mesh_normals_varray(const Mesh &mesh, const IndexMask &mask, AttrDomain domain); +VArray mesh_normals_varray(const Mesh &mesh, + const IndexMask &mask, + AttrDomain domain, + bool no_corner_normals = false); class NormalFieldInput : public GeometryFieldInput { + bool legacy_corner_normals_ = false; + public: - NormalFieldInput() : GeometryFieldInput(CPPType::get()) + NormalFieldInput(const bool legacy_corner_normals = false) + : GeometryFieldInput(CPPType::get()), legacy_corner_normals_(legacy_corner_normals) { category_ = Category::Generated; } diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 0c90f68bb06..6b67721ea9f 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -122,7 +122,8 @@ void MeshComponent::count_memory(MemoryCounter &memory) const VArray mesh_normals_varray(const Mesh &mesh, const IndexMask &mask, - const AttrDomain domain) + const AttrDomain domain, + const bool no_corner_normals) { switch (domain) { case AttrDomain::Face: { @@ -148,12 +149,11 @@ VArray mesh_normals_varray(const Mesh &mesh, return VArray::ForContainer(std::move(edge_normals)); } case AttrDomain::Corner: { - /* The normals on corners are just the mesh's face normals, so start with the face normal - * array and copy the face normal for each of its corners. In this case using the mesh - * component's generic domain interpolation is fine, the data will still be normalized, - * since the face normal is just copied to every corner. */ - return mesh.attributes().adapt_domain( - VArray::ForSpan(mesh.face_normals()), AttrDomain::Face, AttrDomain::Corner); + if (no_corner_normals) { + return mesh.attributes().adapt_domain( + VArray::ForSpan(mesh.face_normals()), AttrDomain::Face, AttrDomain::Corner); + } + return VArray::ForSpan(mesh.corner_normals()); } default: return {}; diff --git a/source/blender/blenkernel/intern/geometry_fields.cc b/source/blender/blenkernel/intern/geometry_fields.cc index a62a217dff5..482dde5f453 100644 --- a/source/blender/blenkernel/intern/geometry_fields.cc +++ b/source/blender/blenkernel/intern/geometry_fields.cc @@ -718,7 +718,7 @@ GVArray NormalFieldInput::get_varray_for_context(const GeometryFieldContext &con const IndexMask &mask) const { if (const Mesh *mesh = context.mesh()) { - return mesh_normals_varray(*mesh, mask, context.domain()); + return mesh_normals_varray(*mesh, mask, context.domain(), legacy_corner_normals_); } if (const CurvesGeometry *curves = context.curves_or_strokes()) { return curve_normals_varray(*curves, context.domain()); diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index cdc191bfff1..379705cc425 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -3591,6 +3591,17 @@ static void version_group_input_socket_data_block_reference(bNodeTree &ntree) } } +static void version_geometry_normal_input_node(bNodeTree &ntree) +{ + if (ntree.type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { + if (STREQ(node->idname, "GeometryNodeInputNormal")) { + node->custom1 = 1; + } + } + } +} + void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 1)) { @@ -5644,6 +5655,12 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 24)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + version_geometry_normal_input_node(*ntree); + } + } + /* Always run this versioning; meshes are written with the legacy format which always needs to * be converted to the new format on file load. Can be moved to a subversion check in a larger * breaking release. */ diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 43ef380e8eb..d8bc8bc8203 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10620,6 +10620,17 @@ static void def_geo_input_collection(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_input_normal(BlenderRNA * /*brna*/, StructRNA *srna) +{ + PropertyRNA *prop = RNA_def_property(srna, "legacy_corner_normals", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "custom1", 1); + RNA_def_property_ui_text( + prop, + "Flat Corner Normals", + "Always use face normals for the face corner domain, matching old behavior of the node"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_input_object(BlenderRNA * /*brna*/, StructRNA *srna) { PropertyRNA *prop; @@ -12426,7 +12437,7 @@ static void rna_def_nodes(BlenderRNA *brna) define("GeometryNode", "GeometryNodeInputMeshVertexNeighbors"); define("GeometryNode", "GeometryNodeInputNamedAttribute"); define("GeometryNode", "GeometryNodeInputNamedLayerSelection"); - define("GeometryNode", "GeometryNodeInputNormal"); + define("GeometryNode", "GeometryNodeInputNormal", def_geo_input_normal); define("GeometryNode", "GeometryNodeInputObject", def_geo_input_object); define("GeometryNode", "GeometryNodeInputPosition"); define("GeometryNode", "GeometryNodeInputRadius"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index 3d7443ed6fd..385491e317a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -4,6 +4,8 @@ #include "node_geometry_util.hh" +#include "UI_interface.hh" + namespace blender::nodes::node_geo_input_normal_cc { static void node_declare(NodeDeclarationBuilder &b) @@ -11,9 +13,15 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Normal").field_source(); } +static void node_layout_ex(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "legacy_corner_normals", UI_ITEM_NONE, std::nullopt, ICON_NONE); +} + static void node_geo_exec(GeoNodeExecParams params) { - Field normal_field{std::make_shared()}; + const bool legacy_corner_normals = bool(params.node().custom1); + Field normal_field{std::make_shared(legacy_corner_normals)}; params.set_output("Normal", std::move(normal_field)); } @@ -30,6 +38,7 @@ static void node_register() ntype.nclass = NODE_CLASS_INPUT; ntype.geometry_node_execute = node_geo_exec; ntype.declare = node_declare; + ntype.draw_buttons_ex = node_layout_ex; blender::bke::node_register_type(&ntype); } NOD_REGISTER_NODE(node_register)