Blender V5.0
node_geo_uv_tangent.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BKE_mesh.hh"
6#include "BKE_mesh_tangent.hh"
7
8#include "BLI_math_vector.hh"
9
10#include "node_geometry_util.hh"
11
13
14enum class Method {
15 Exact = 0,
16 Fast = 1,
17};
18
20 {int(Method::Exact),
21 "EXACT",
22 0,
23 N_("Exact"),
24 N_("Calculation using the MikkTSpace library, consistent with tangents used elsewhere in "
25 "Blender")},
26 {int(Method::Fast),
27 "FAST",
28 0,
29 N_("Fast"),
30 N_("Significantly faster method that approximates tangents interpolated across face corners "
31 "with matching UVs. For a value actually tangential to the surface, use the cross product "
32 "with the normal.")},
33 {0, nullptr, 0, nullptr, nullptr},
34};
35
37{
38 b.add_input<decl::Menu>("Method").static_items(method_items).optional_label();
39 b.add_input<decl::Vector>("UV").dimensions(2).subtype(PROP_XYZ).supports_field();
40 b.add_output<decl::Vector>("Tangent").field_source_reference_all();
41}
42
44 const float3 &p2,
45 const float3 &p3,
46 const float2 &uv1,
47 const float2 &uv2,
48 const float2 &uv3)
49{
50 const float x1 = p2.x - p1.x;
51 const float x2 = p3.x - p1.x;
52 const float y1 = p2.y - p1.y;
53 const float y2 = p3.y - p1.y;
54 const float z1 = p2.z - p1.z;
55 const float z2 = p3.z - p1.z;
56 const float s1 = uv2.x - uv1.x;
57 const float s2 = uv3.x - uv1.x;
58 const float t1 = uv2.y - uv1.y;
59 const float t2 = uv3.y - uv1.y;
60 const float r = math::safe_rcp(s1 * t2 - s2 * t1);
61 const float3 tangent((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
62 return tangent;
63}
64
65static void calc_uv_tangents_simple(const Span<float3> positions,
66 const Span<int> corner_verts,
67 const Span<int3> corner_tris,
68 const GroupedSpan<int> vert_to_corners_map,
69 const Span<float3> uvs,
70 MutableSpan<float3> r_corner_tangents)
71{
72 BLI_assert(r_corner_tangents.size() == corner_verts.size());
73
74 /* Compute a tangent vector for each triangle. */
75 threading::parallel_for(corner_tris.index_range(), 256, [&](const IndexRange range) {
76 for (const int tri_i : range) {
77 const int3 &tri = corner_tris[tri_i];
78 const float3 tangent = compute_triangle_tangent(positions[corner_verts[tri[0]]],
79 positions[corner_verts[tri[1]]],
80 positions[corner_verts[tri[2]]],
81 uvs[tri[0]].xy(),
82 uvs[tri[1]].xy(),
83 uvs[tri[2]].xy());
84 /* Writing the result separately for every triangle simplifies the next loop. */
85 r_corner_tangents[tri[0]] = tangent;
86 r_corner_tangents[tri[1]] = tangent;
87 r_corner_tangents[tri[2]] = tangent;
88 }
89 });
90
91 /* Mix the tangent vectors in vertices where multiple corners share the same uv. */
92 threading::parallel_for(positions.index_range(), 512, [&](const IndexRange range) {
93 struct SharedCorners {
94 float2 uv;
95 Vector<int, 10> corners;
96 float3 tangent_sum = float3(0.0f);
97 };
98 Vector<SharedCorners> shared_corners;
99 for (const int vert : range) {
100 const Span<int> corners = vert_to_corners_map[vert];
101
102 shared_corners.clear();
103 for (const int corner : corners) {
104 const float2 uv = uvs[corner].xy();
105 /* This is only the non-interpolated tangent right now. */
106 const float3 &tri_tangent = r_corner_tangents[corner];
107 bool found = false;
108 for (SharedCorners &shared_corner : shared_corners) {
109 if (math::distance_manhattan(uv, shared_corner.uv) < 0.00001f) {
110 shared_corner.corners.append(corner);
111 shared_corner.tangent_sum += tri_tangent;
112 found = true;
113 break;
114 }
115 }
116 if (!found) {
117 shared_corners.append({uv, {corner}, tri_tangent});
118 }
119 }
120 for (const SharedCorners &shared_corner : shared_corners) {
121 const float3 tangent = math::normalize(shared_corner.tangent_sum);
122 for (const int corner : shared_corner.corners) {
123 r_corner_tangents[corner] = tangent;
124 }
125 }
126 }
127 });
128}
129
131 private:
132 Method method_;
133 Field<float3> uv_field_;
134
135 public:
137 : bke::MeshFieldInput(CPPType::get<float3>(), "Tangent Field"),
138 method_(method),
139 uv_field_(std::move(uv))
140 {
142 }
143
145 const AttrDomain domain,
146 const IndexMask & /*mask*/) const override
147 {
148 const bke::AttributeAccessor attributes = mesh.attributes();
149
150 const bke::MeshFieldContext corner_context{mesh, AttrDomain::Corner};
151 FieldEvaluator evaluator{corner_context, mesh.corners_num};
152 evaluator.add(uv_field_);
153 evaluator.evaluate();
154 const VArraySpan uvs = evaluator.get_evaluated<float3>(0);
155
156 Array<float3> corner_tangents(mesh.corners_num);
157 switch (method_) {
158 case Method::Fast: {
159 calc_uv_tangents_simple(mesh.vert_positions(),
160 mesh.corner_verts(),
161 mesh.corner_tris(),
162 mesh.vert_to_corner_map(),
163 uvs,
164 corner_tangents);
165 break;
166 }
167 case Method::Exact: {
168 const VArraySpan sharp_faces = *attributes.lookup<bool>("sharp_face",
170 Array<float2> uvs_float2(uvs.size());
171 threading::parallel_for(corner_tangents.index_range(), 4096, [&](const IndexRange range) {
172 for (const int64_t corner : range) {
173 uvs_float2[corner] = uvs[corner].xy();
174 }
175 });
176 Array<Array<float4>> mikk_tangents = bke::mesh::calc_uv_tangents(mesh.vert_positions(),
177 mesh.faces(),
178 mesh.corner_verts(),
179 mesh.corner_tris(),
180 mesh.corner_tri_faces(),
181 sharp_faces,
182 mesh.vert_normals(),
183 mesh.face_normals(),
184 mesh.corner_normals(),
185 {uvs_float2});
186 threading::parallel_for(corner_tangents.index_range(), 4096, [&](const IndexRange range) {
187 for (const int64_t corner : range) {
188 corner_tangents[corner] = mikk_tangents[0][corner].xyz();
189 }
190 });
191 break;
192 }
193 }
194
195 return attributes.adapt_domain(VArray<float3>::from_container(std::move(corner_tangents)),
197 domain);
198 }
199
200 void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
201 {
202 uv_field_.node().for_each_field_input_recursive(fn);
203 }
204
205 bool is_equal_to(const FieldNode &other) const override
206 {
207 if (const TangentFieldInput *other_endpoint = dynamic_cast<const TangentFieldInput *>(&other))
208 {
209 return method_ == other_endpoint->method_ && uv_field_ == other_endpoint->uv_field_;
210 }
211 return false;
212 }
213
214 uint64_t hash() const override
215 {
216 return get_default_hash(method_, uv_field_);
217 }
218
219 std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
220 {
221 return AttrDomain::Corner;
222 }
223};
224
226{
227 const Method method = params.extract_input<Method>("Method");
228 Field<float3> uv_field = params.extract_input<Field<float3>>("UV");
229 params.set_output("Tangent",
230 Field<float3>(std::make_shared<TangentFieldInput>(method, uv_field)));
231}
232
233static void node_register()
234{
235 static blender::bke::bNodeType ntype;
236
237 geo_node_type_base(&ntype, "GeometryNodeUVTangent");
238 ntype.ui_name = "UV Tangent";
239 ntype.ui_description = "Generate tangent directions based on a UV map";
240 ntype.nclass = NODE_CLASS_INPUT;
241 ntype.declare = node_declare;
244}
245NOD_REGISTER_NODE(node_register)
246
247} // namespace blender::nodes::node_geo_uv_tangent_cc
#define NODE_CLASS_INPUT
Definition BKE_node.hh:447
#define BLI_assert(a)
Definition BLI_assert.h:46
#define final(a, b, c)
Definition BLI_hash.h:19
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_XYZ
Definition RNA_types.hh:269
unsigned long long int uint64_t
AttributeSet attributes
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
static VArray from_container(ContainerT container)
GAttributeReader lookup(const StringRef attribute_id) const
FieldInput(const CPPType &type, std::string debug_name="")
Definition field.cc:677
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
FieldNode(FieldNodeType node_type)
Definition FN_field.hh:547
std::optional< AttrDomain > preferred_domain(const Mesh &) const override
void for_each_field_input_recursive(FunctionRef< void(const FieldInput &)> fn) const override
GVArray get_varray_for_context(const Mesh &mesh, const AttrDomain domain, const IndexMask &) const override
bool is_equal_to(const FieldNode &other) const override
TangentFieldInput(const Method method, Field< float3 > uv)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
Array< Array< float4 > > calc_uv_tangents(Span< float3 > vert_positions, OffsetIndices< int > faces, Span< int > corner_verts, Span< int3 > corner_tris, Span< int > corner_tri_faces, Span< bool > sharp_faces, Span< float3 > vert_normals, Span< float3 > face_normals, Span< float3 > corner_normals, Span< Span< float2 > > uv_maps)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
T safe_rcp(const T &a)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T distance_manhattan(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
static void calc_uv_tangents_simple(const Span< float3 > positions, const Span< int > corner_verts, const Span< int3 > corner_tris, const GroupedSpan< int > vert_to_corners_map, const Span< float3 > uvs, MutableSpan< float3 > r_corner_tangents)
static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
static float3 compute_triangle_tangent(const float3 &p1, const float3 &p2, const float3 &p3, const float2 &uv1, const float2 &uv2, const float2 &uv3)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
VecBase< float, 2 > float2
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
int corners_num
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
NodeDeclareFunction declare
Definition BKE_node.hh:362
#define N_(msgid)