Skip to content

Improve tangent space generation - #119161

Merged
Repiteo merged 1 commit into
godotengine:masterfrom
zeux:nice-tangents
Jul 1, 2026
Merged

Improve tangent space generation#119161
Repiteo merged 1 commit into
godotengine:masterfrom
zeux:nice-tangents

Conversation

@zeux

@zeux zeux commented May 2, 2026

Copy link
Copy Markdown
Contributor

This change switches from mikktspace.c tangent frame generation to the new comparable functionality provided by meshoptimizer via meshopt_generateTangents; see documentation.

It simultaneously achieves three things: fixes common tangent artifacts inherent to MikkTSpace, fixes incorrect tangents on seams with UV mirroring due to incorrect mikktspace.h usage (#114508) and speeds up the generation process ~6-7x (e.g. testing on a 2M triangle mesh, the tangent generation goes from ~2.5 seconds to ~330 msec).

Splitting vertices with divergent tangents is correct, but may disturb shared indexing when used with blend shapes. Because of this, meshes with blend shapes skip splitting and will continue to have the same problems as shown in #114508. This could be improved in the future, as it stands this change maintains behavior here.

Technically, "fixing common tangent artifacts" also implies that the tangents we generate are not-exactly-MikkTSpace. Because Godot previously didn't handle UV mirroring at all and the cases that the improved weighting fixes are objectively broken in MikkTSpace, I don't think this will be a problem; but if it ever does become a problem, we can always enable the "compatible" option which generates the same tangents as MikkTSpace by using the same weighting.

CSG code is using mikktspace directly instead of SurfaceTool. So we'd need an extra change after this to switch that over to meshopt too to be able to remove mikktspace; for now, this only replaces import path.

Script-side calls to generate_tangents preserve the behavior of not splitting vertices with different tangent windings out of caution.

Fixes #114508

@zeux
zeux force-pushed the nice-tangents branch from 82f2f20 to 8d6b7df Compare May 2, 2026 02:04
@Chaosus Chaosus added this to the 4.x milestone May 2, 2026
@zeux zeux changed the title (WIP) Improved tangent space generation Improved tangent space generation May 3, 2026
@AThousandShips AThousandShips changed the title Improved tangent space generation Improve tangent space generation May 4, 2026
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.
@zeux
zeux marked this pull request as ready for review June 30, 2026 22:20
@zeux
zeux requested review from a team as code owners June 30, 2026 22:20
@zeux

zeux commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Since all dependent PRs were merged I've squashed this and removed the draft status; the code is otherwise unchanged.

@Calinou Calinou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected. Code looks good to me.

Preview

master This PR Difference (dssim)
Image Image Image

Benchmark

PC specifications
  • CPU: AMD Ryzen 9 9950X3D
  • GPU: NVIDIA GeForce RTX 5090
  • RAM: 64 GB (2×32 GB DDR5-6000 CL30)
  • SSD: Solidigm P44 Pro 2 TB
  • OS: Linux (Fedora 44)

Time taken to import octavus.obj as a 3D scene 101 times (average of 10 runs). Material import, LOD generation and shadow mesh generation are disabled to isolate tangent generation as much as possible.

Using an optimized editor binary (production=yes lto=full) in headless mode.

(master)
Benchmark 1: bin/godot.linuxbsd.editor.x86_64.master --headless --path /tmp/4 --import
  Time (mean ± σ):     38.916 s ±  1.367 s    [User: 38.572 s, System: 0.383 s]
  Range (min … max):   38.005 s … 42.657 s    10 runs

(This PR)
Benchmark 2: bin/godot.linuxbsd.editor.x86_64 --headless --path /tmp/4 --import
  Time (mean ± σ):     34.029 s ±  0.077 s    [User: 33.705 s, System: 0.378 s]
  Range (min … max):   33.915 s … 34.206 s    10 runs

@Repiteo Repiteo modified the milestones: 4.x, 4.8 Jul 1, 2026
@Repiteo
Repiteo merged commit 6e72052 into godotengine:master Jul 1, 2026
20 checks passed
@Repiteo

Repiteo commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Thanks!

BendyLand pushed a commit to BendyLand/voltaire that referenced this pull request Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect generated tangents on mirrored UVs in glTF import

4 participants