Skip to content

Commit e9b27fd

Browse files
committed
Implement tangent space generation with vertex splitting using meshopt
meshopt_generateTangents is used to generate per-corner tangent vectors. For unindexed inputs, we then simply copy the result to the output. For indexed inputs, we copy them into the existing vertices and split vertices with divergent tangents on the fly. Splitting may interfere with blend shapes as it may disturb shared indexing. While it would be possible to propagate the split vector into the blend shape code and apply it to the blend targets as well, for now we simply avoid doing the splitting in the first place if the mesh has blend shapes. Script binding for generate_tangents uses pre-splitting behavior for compatibility.
1 parent 8dbf8a8 commit e9b27fd

5 files changed

Lines changed: 90 additions & 113 deletions

File tree

modules/fbx/fbx_document.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -758,8 +758,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
758758
mesh_surface_tool->set_skin_weight_count(num_skin_weights == 8 ? SurfaceTool::SKIN_8_WEIGHTS : SurfaceTool::SKIN_4_WEIGHTS);
759759
mesh_surface_tool->index();
760760
if (generate_tangents) {
761-
//must generate mikktspace tangents.. ergh..
762-
mesh_surface_tool->generate_tangents();
761+
mesh_surface_tool->generate_tangents(/*split*/ !use_blend_shapes);
763762
}
764763
array = mesh_surface_tool->commit_to_arrays();
765764

@@ -828,8 +827,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
828827
blend_surface_tool->create_from_triangle_arrays(array_copy);
829828
blend_surface_tool->set_skin_weight_count(num_skin_weights == 8 ? SurfaceTool::SKIN_8_WEIGHTS : SurfaceTool::SKIN_4_WEIGHTS);
830829
if (generate_tangents) {
831-
//must generate mikktspace tangents.. ergh..
832-
blend_surface_tool->generate_tangents();
830+
blend_surface_tool->generate_tangents(/*split*/ false);
833831
}
834832
array_copy = blend_surface_tool->commit_to_arrays();
835833

modules/gltf/gltf_document.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,8 +1834,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
18341834
}
18351835
mesh_surface_tool->index();
18361836
if (generate_tangents && a.has("TEXCOORD_0")) {
1837-
//must generate mikktspace tangents.. ergh..
1838-
mesh_surface_tool->generate_tangents();
1837+
mesh_surface_tool->generate_tangents(/*split*/ !mesh_prim.has("targets"));
18391838
}
18401839
array = mesh_surface_tool->commit_to_arrays();
18411840

@@ -1974,7 +1973,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
19741973
}
19751974
blend_surface_tool->index();
19761975
if (generate_tangents) {
1977-
blend_surface_tool->generate_tangents();
1976+
blend_surface_tool->generate_tangents(/*split*/ false);
19781977
}
19791978
array_copy = blend_surface_tool->commit_to_arrays();
19801979

modules/meshoptimizer/register_types.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ void initialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
4747
SurfaceTool::generate_remap_func = meshopt_generateVertexRemap;
4848
SurfaceTool::remap_vertex_func = meshopt_remapVertexBuffer;
4949
SurfaceTool::remap_index_func = meshopt_remapIndexBuffer;
50+
SurfaceTool::generate_tangents_func = meshopt_generateTangents;
5051
}
5152

5253
void uninitialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
@@ -61,4 +62,5 @@ void uninitialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
6162
SurfaceTool::generate_remap_func = nullptr;
6263
SurfaceTool::remap_vertex_func = nullptr;
6364
SurfaceTool::remap_index_func = nullptr;
65+
SurfaceTool::generate_tangents_func = nullptr;
6466
}

scene/resources/surface_tool.cpp

Lines changed: 77 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ SurfaceTool::SimplifyScaleFunc SurfaceTool::simplify_scale_func = nullptr;
4343
SurfaceTool::GenerateRemapFunc SurfaceTool::generate_remap_func = nullptr;
4444
SurfaceTool::RemapVertexFunc SurfaceTool::remap_vertex_func = nullptr;
4545
SurfaceTool::RemapIndexFunc SurfaceTool::remap_index_func = nullptr;
46+
SurfaceTool::GenerateTangentsFunc SurfaceTool::generate_tangents_func = nullptr;
4647

4748
void SurfaceTool::strip_mesh_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) {
4849
ERR_FAIL_COND_MSG(!generate_remap_func || !remap_vertex_func || !remap_index_func, "Meshoptimizer library is not initialized.");
@@ -1053,123 +1054,105 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const
10531054
}
10541055
}
10551056

1056-
//mikktspace callbacks
1057-
namespace {
1058-
struct TangentGenerationContextUserData {
1059-
LocalVector<SurfaceTool::Vertex> *vertices;
1060-
LocalVector<int> *indices;
1057+
struct TangentVertex {
1058+
float position[3];
1059+
float normal[3];
1060+
float uv[2];
10611061
};
1062-
} // namespace
10631062

1064-
int SurfaceTool::mikktGetNumFaces(const SMikkTSpaceContext *pContext) {
1065-
TangentGenerationContextUserData &triangle_data = *reinterpret_cast<TangentGenerationContextUserData *>(pContext->m_pUserData);
1063+
static void _propagate_tangents_or_split(LocalVector<SurfaceTool::Vertex> &r_vertex_array, LocalVector<int> &r_index_array, const float *p_tangents, bool p_split) {
1064+
// Seed each vertex with one of its corner tangents; the loop below fixes any mismatches.
1065+
for (size_t i = 0; i < r_index_array.size(); i++) {
1066+
Vector4 tangent(p_tangents[i * 4 + 0], p_tangents[i * 4 + 1], p_tangents[i * 4 + 2], p_tangents[i * 4 + 3]);
1067+
r_vertex_array[r_index_array[i]].tangent = Vector4(p_tangents[i * 4 + 0], p_tangents[i * 4 + 1], p_tangents[i * 4 + 2], p_tangents[i * 4 + 3]);
1068+
}
10661069

1067-
if (triangle_data.indices->size() > 0) {
1068-
return triangle_data.indices->size() / 3;
1069-
} else {
1070-
return triangle_data.vertices->size() / 3;
1070+
if (!p_split) {
1071+
return;
10711072
}
1072-
}
10731073

1074-
int SurfaceTool::mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace) {
1075-
return 3; //always 3
1076-
}
1074+
LocalVector<uint32_t> splits;
1075+
splits.resize(r_vertex_array.size());
1076+
memset(splits.ptr(), -1, splits.size() * sizeof(uint32_t)); // ~0u means "no split copy"
10771077

1078-
void SurfaceTool::mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert) {
1079-
TangentGenerationContextUserData &triangle_data = *reinterpret_cast<TangentGenerationContextUserData *>(pContext->m_pUserData);
1080-
Vector3 v;
1081-
if (triangle_data.indices->size() > 0) {
1082-
uint32_t index = triangle_data.indices->operator[](iFace * 3 + iVert);
1083-
if (index < triangle_data.vertices->size()) {
1084-
v = triangle_data.vertices->operator[](index).vertex;
1078+
for (size_t i = 0; i < r_index_array.size(); i++) {
1079+
// Walk the chain of split copies looking for a vertex whose tangent matches.
1080+
Vector4 tangent(p_tangents[i * 4 + 0], p_tangents[i * 4 + 1], p_tangents[i * 4 + 2], p_tangents[i * 4 + 3]);
1081+
uint32_t v = r_index_array[i];
1082+
while (v != ~0u && r_vertex_array[v].tangent != tangent) {
1083+
v = splits[v];
10851084
}
1086-
} else {
1087-
v = triangle_data.vertices->operator[](iFace * 3 + iVert).vertex;
1088-
}
1089-
1090-
fvPosOut[0] = v.x;
1091-
fvPosOut[1] = v.y;
1092-
fvPosOut[2] = v.z;
1093-
}
10941085

1095-
void SurfaceTool::mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert) {
1096-
TangentGenerationContextUserData &triangle_data = *reinterpret_cast<TangentGenerationContextUserData *>(pContext->m_pUserData);
1097-
Vector3 v;
1098-
if (triangle_data.indices->size() > 0) {
1099-
uint32_t index = triangle_data.indices->operator[](iFace * 3 + iVert);
1100-
if (index < triangle_data.vertices->size()) {
1101-
v = triangle_data.vertices->operator[](index).normal;
1086+
// No match in chain: append a new split copy with the target tangent and chain it.
1087+
if (v == ~0u) {
1088+
v = r_vertex_array.size();
1089+
r_vertex_array.push_back(r_vertex_array[r_index_array[i]]);
1090+
r_vertex_array[v].tangent = tangent;
1091+
splits.push_back(splits[r_index_array[i]]);
1092+
splits[r_index_array[i]] = v;
11021093
}
1103-
} else {
1104-
v = triangle_data.vertices->operator[](iFace * 3 + iVert).normal;
1094+
1095+
r_index_array[i] = int(v);
11051096
}
1097+
}
11061098

1107-
fvNormOut[0] = v.x;
1108-
fvNormOut[1] = v.y;
1109-
fvNormOut[2] = v.z;
1099+
void SurfaceTool::_generate_tangents_bind() {
1100+
generate_tangents(/*split*/ false);
11101101
}
11111102

1112-
void SurfaceTool::mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert) {
1113-
TangentGenerationContextUserData &triangle_data = *reinterpret_cast<TangentGenerationContextUserData *>(pContext->m_pUserData);
1114-
Vector2 v;
1115-
if (triangle_data.indices->size() > 0) {
1116-
uint32_t index = triangle_data.indices->operator[](iFace * 3 + iVert);
1117-
if (index < triangle_data.vertices->size()) {
1118-
v = triangle_data.vertices->operator[](index).uv;
1119-
}
1120-
} else {
1121-
v = triangle_data.vertices->operator[](iFace * 3 + iVert).uv;
1103+
void SurfaceTool::generate_tangents(bool p_split) {
1104+
ERR_FAIL_COND_MSG(!generate_tangents_func, "Meshoptimizer library is not initialized.");
1105+
ERR_FAIL_COND_MSG(!(format & Mesh::ARRAY_FORMAT_TEX_UV), "UVs are required to generate tangents.");
1106+
ERR_FAIL_COND(!(format & Mesh::ARRAY_FORMAT_NORMAL));
1107+
ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES);
1108+
1109+
if (vertex_array.is_empty()) {
1110+
format |= Mesh::ARRAY_FORMAT_TANGENT;
1111+
return;
11221112
}
11231113

1124-
fvTexcOut[0] = v.x;
1125-
fvTexcOut[1] = v.y;
1126-
}
1114+
size_t corner_count = index_array.size() > 0 ? index_array.size() : vertex_array.size();
1115+
ERR_FAIL_COND(corner_count % 3 != 0);
11271116

1128-
void SurfaceTool::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
1129-
const tbool bIsOrientationPreserving, const int iFace, const int iVert) {
1130-
TangentGenerationContextUserData &triangle_data = *reinterpret_cast<TangentGenerationContextUserData *>(pContext->m_pUserData);
1131-
Vertex *vtx = nullptr;
1132-
if (triangle_data.indices->size() > 0) {
1133-
uint32_t index = triangle_data.indices->operator[](iFace * 3 + iVert);
1134-
if (index < triangle_data.vertices->size()) {
1135-
vtx = &triangle_data.vertices->operator[](index);
1136-
}
1137-
} else {
1138-
vtx = &triangle_data.vertices->operator[](iFace * 3 + iVert);
1139-
}
1117+
// We can't operate on SurfaceTool::Vertex directly because in double-precision builds, vectors use double components
1118+
// So we convert the inputs to single precision floats before generating tangents.
1119+
LocalVector<TangentVertex> tangent_vertices;
1120+
tangent_vertices.resize(vertex_array.size());
11401121

1141-
if (vtx != nullptr) {
1142-
vtx->tangent = Vector4(fvTangent[0], fvTangent[1], fvTangent[2], bIsOrientationPreserving ? 1.0f : -1.0f);
1122+
for (size_t i = 0; i < vertex_array.size(); i++) {
1123+
const Vertex &vertex = vertex_array[i];
1124+
TangentVertex &tangent_vertex = tangent_vertices[i];
1125+
1126+
tangent_vertex.position[0] = vertex.vertex.x;
1127+
tangent_vertex.position[1] = vertex.vertex.y;
1128+
tangent_vertex.position[2] = vertex.vertex.z;
1129+
tangent_vertex.normal[0] = vertex.normal.x;
1130+
tangent_vertex.normal[1] = vertex.normal.y;
1131+
tangent_vertex.normal[2] = vertex.normal.z;
1132+
tangent_vertex.uv[0] = vertex.uv.x;
1133+
tangent_vertex.uv[1] = vertex.uv.y;
11431134
}
1144-
}
11451135

1146-
void SurfaceTool::generate_tangents() {
1147-
ERR_FAIL_COND_MSG(!(format & Mesh::ARRAY_FORMAT_TEX_UV), "UVs are required to generate tangents.");
1148-
ERR_FAIL_COND(!(format & Mesh::ARRAY_FORMAT_NORMAL));
1136+
const TangentVertex *vertices = tangent_vertices.ptr();
11491137

1150-
SMikkTSpaceInterface mkif;
1151-
mkif.m_getNormal = mikktGetNormal;
1152-
mkif.m_getNumFaces = mikktGetNumFaces;
1153-
mkif.m_getNumVerticesOfFace = mikktGetNumVerticesOfFace;
1154-
mkif.m_getPosition = mikktGetPosition;
1155-
mkif.m_getTexCoord = mikktGetTexCoord;
1156-
mkif.m_setTSpace = mikktSetTSpaceDefault;
1157-
mkif.m_setTSpaceBasic = nullptr;
1138+
LocalVector<float> tangents;
1139+
tangents.resize(corner_count * 4);
11581140

1159-
SMikkTSpaceContext msc;
1160-
msc.m_pInterface = &mkif;
1141+
generate_tangents_func(tangents.ptr(),
1142+
index_array.size() > 0 ? reinterpret_cast<const unsigned int *>(index_array.ptr()) : nullptr, corner_count,
1143+
vertices->position, tangent_vertices.size(), sizeof(TangentVertex),
1144+
vertices->normal, sizeof(TangentVertex),
1145+
vertices->uv, sizeof(TangentVertex), 0);
11611146

1162-
TangentGenerationContextUserData triangle_data;
1163-
triangle_data.vertices = &vertex_array;
1164-
for (Vertex &vertex : vertex_array) {
1165-
vertex.tangent = Vector4();
1147+
if (index_array.size() > 0) {
1148+
_propagate_tangents_or_split(vertex_array, index_array, tangents.ptr(), p_split);
1149+
} else {
1150+
for (size_t i = 0; i < corner_count; i++) {
1151+
Vector4 tangent(tangents[i * 4 + 0], tangents[i * 4 + 1], tangents[i * 4 + 2], tangents[i * 4 + 3]);
1152+
vertex_array[i].tangent = tangent;
1153+
}
11661154
}
1167-
triangle_data.indices = &index_array;
1168-
msc.m_pUserData = &triangle_data;
1169-
1170-
bool res = genTangSpaceDefault(&msc);
11711155

1172-
ERR_FAIL_COND(!res);
11731156
format |= Mesh::ARRAY_FORMAT_TANGENT;
11741157
}
11751158

@@ -1355,7 +1338,7 @@ void SurfaceTool::_bind_methods() {
13551338
ClassDB::bind_method(D_METHOD("index"), &SurfaceTool::index);
13561339
ClassDB::bind_method(D_METHOD("deindex"), &SurfaceTool::deindex);
13571340
ClassDB::bind_method(D_METHOD("generate_normals", "flip"), &SurfaceTool::generate_normals, DEFVAL(false));
1358-
ClassDB::bind_method(D_METHOD("generate_tangents"), &SurfaceTool::generate_tangents);
1341+
ClassDB::bind_method(D_METHOD("generate_tangents"), &SurfaceTool::_generate_tangents_bind);
13591342

13601343
ClassDB::bind_method(D_METHOD("optimize_indices_for_cache"), &SurfaceTool::optimize_indices_for_cache);
13611344

scene/resources/surface_tool.h

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
#include "scene/resources/mesh.h"
3636
#include "servers/rendering/rendering_server_enums.h"
3737

38-
#include <thirdparty/misc/mikktspace.h>
39-
4038
class SurfaceTool : public RefCounted {
4139
GDCLASS(SurfaceTool, RefCounted);
4240

@@ -95,6 +93,8 @@ class SurfaceTool : public RefCounted {
9593
SIMPLIFY_REGULARIZE = 1 << 4, // From meshopt_SimplifyRegularize
9694
/* Allow collapses across attribute discontinuities, except for vertices that are tagged with 0x02 in vertex_lock. */
9795
SIMPLIFY_PERMISSIVE = 1 << 5, // From meshopt_SimplifyPermissive
96+
/* Produce tangents compatible with MikkTSpace (same weighting and fallbacks) at the cost of reduced quality. Not recommended unless normal maps are baked. */
97+
TANGENT_COMPATIBLE = 1 << 0, // From meshopt_TangentCompatible
9898
};
9999

100100
typedef void (*OptimizeVertexCacheFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
@@ -113,6 +113,8 @@ class SurfaceTool : public RefCounted {
113113
static RemapVertexFunc remap_vertex_func;
114114
typedef void (*RemapIndexFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const unsigned int *remap);
115115
static RemapIndexFunc remap_index_func;
116+
typedef void (*GenerateTangentsFunc)(float *result, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float *vertex_normals, size_t vertex_normals_stride, const float *vertex_uvs, size_t vertex_uvs_stride, unsigned int options);
117+
static GenerateTangentsFunc generate_tangents_func;
116118
static void strip_mesh_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices);
117119

118120
private:
@@ -176,17 +178,10 @@ class SurfaceTool : public RefCounted {
176178
void _create_list_from_arrays(Array arr, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat);
177179
void _create_list(const Ref<Mesh> &p_existing, int p_surface, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat);
178180

179-
//mikktspace callbacks
180-
static int mikktGetNumFaces(const SMikkTSpaceContext *pContext);
181-
static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace);
182-
static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert);
183-
static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert);
184-
static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert);
185-
static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
186-
const tbool bIsOrientationPreserving, const int iFace, const int iVert);
187-
188181
void _add_triangle_fan(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs = Vector<Vector2>(), const Vector<Color> &p_colors = Vector<Color>(), const Vector<Vector2> &p_uv2s = Vector<Vector2>(), const Vector<Vector3> &p_normals = Vector<Vector3>(), const TypedArray<Plane> &p_tangents = TypedArray<Plane>());
189182

183+
void _generate_tangents_bind();
184+
190185
protected:
191186
static void _bind_methods();
192187

@@ -220,7 +215,7 @@ class SurfaceTool : public RefCounted {
220215
void index();
221216
void deindex();
222217
void generate_normals(bool p_flip = false);
223-
void generate_tangents();
218+
void generate_tangents(bool p_split = false);
224219

225220
void optimize_indices_for_cache();
226221
AABB get_aabb() const;

0 commit comments

Comments
 (0)