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
This commit is contained in:
Hans Goudey
2025-01-20 23:57:32 +01:00
committed by Hans Goudey
parent 46ece39c1a
commit b36eb69038
7 changed files with 56 additions and 13 deletions

View File

@@ -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

View File

@@ -375,11 +375,17 @@ class IDAttributeFieldInput : public GeometryFieldInput {
VArray<float3> curve_normals_varray(const CurvesGeometry &curves, AttrDomain domain);
VArray<float3> mesh_normals_varray(const Mesh &mesh, const IndexMask &mask, AttrDomain domain);
VArray<float3> 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<float3>())
NormalFieldInput(const bool legacy_corner_normals = false)
: GeometryFieldInput(CPPType::get<float3>()), legacy_corner_normals_(legacy_corner_normals)
{
category_ = Category::Generated;
}

View File

@@ -122,7 +122,8 @@ void MeshComponent::count_memory(MemoryCounter &memory) const
VArray<float3> 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<float3> mesh_normals_varray(const Mesh &mesh,
return VArray<float3>::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<float3>::ForSpan(mesh.face_normals()), AttrDomain::Face, AttrDomain::Corner);
if (no_corner_normals) {
return mesh.attributes().adapt_domain(
VArray<float3>::ForSpan(mesh.face_normals()), AttrDomain::Face, AttrDomain::Corner);
}
return VArray<float3>::ForSpan(mesh.corner_normals());
}
default:
return {};

View File

@@ -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());

View File

@@ -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. */

View File

@@ -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");

View File

@@ -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<decl::Vector>("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<float3> normal_field{std::make_shared<bke::NormalFieldInput>()};
const bool legacy_corner_normals = bool(params.node().custom1);
Field<float3> normal_field{std::make_shared<bke::NormalFieldInput>(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)