Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable normal raycaster for LOD generation by default #93727

Merged
merged 1 commit into from
Aug 16, 2024

Conversation

zeux
Copy link
Contributor

@zeux zeux commented Jun 29, 2024

Normal raycaster makes LOD generation process >2x slower and often generates normals that look significantly worse compared to what the simplifier comes up with by default, in addition to increasing the vertex buffer size by ~15% due to vertex splitting. This was likely different before last meshoptimizer upgrade, as the attribute metric was not functioning properly, but now it looks like it's doing more harm than good.

This change makes it disabled by default but keeps an easy option to re-enable it per mesh using LOD parameters for now until we get more confidence and can remove the code outright.

Because it likely doesn't make sense to keep the raycaster forever, the scripting API isn't changed, and it's just off-by-default there with no way to re-enable.

@zeux
Copy link
Contributor Author

zeux commented Jun 29, 2024

Quality data, using the scene from #93587. In every screenshot the left hand side is the new behavior (raycast off), the right hand side is the current behavior (raycast on). The screenshots are generated by importing the scene again, and tweaking LOD bias for both meshes in concert until we see differences. Note, you will never see these up close, so I am sometimes intentionally picking very very very low quality LODs to show the delta.

Here raycast=off is much better:

image
image

Here they are about the same:

image
image

Here on the first pair (second-to-last LOD) raycast=off is much better; on the second pair (last LOD) raycast=off is better on the face but raycast=on handles the skirt a little better.

image
image

About the same here, maybe left is a little nicer?

image

Left (raycast=off) clearly better.

image
image

Left (raycast=off) a little better.

image

Left (raycast=off) is mostly better but the left side of the face is not ideal, that's probably a tie overall.

image

Both look good here.

image

Left (raycast=off) a little better on the ears in first pair; second pair (last LOD) left is better on the face.

image
image

Left (raycast=off) better.

image

@zeux
Copy link
Contributor Author

zeux commented Jun 29, 2024

Performance data, using a smaller variant of the scene in #93587 that has 100 meshes instead of 800 (trimmed duplicates).

Current import time: 37 seconds (according to --verbose); about 75% in generate_lods (according to perf)
New import time: 22 seconds (according to --verbose); about 48% in generate_lods (according to perf)

37*0.75 = 27.75, 22*0.48 = 10.56, so ~2.6x faster LOD generation give or take overall. Most of the time in generate_lods is now spent inside meshopt_simplify, which can hopefully be improved in future PRs using a different simplification strategy to reduce repeated work.

Additionally, and maybe more importantly, the use of raycaster produces many new normal values that result in vertex splitting (as in, existing vertices get duplicated). The extra vertices will be part of the vertex arrays and will consume memory, increasing the amount of vertex data - on the scene from the referenced issue, the aggregate number of vertices with raycast=off (with this change) is 2.8M, but with raycast=on it's 3.3M (so 17% more GPU memory spent on vertex data).

@zeux zeux marked this pull request as ready for review June 29, 2024 02:04
@zeux zeux requested a review from a team as a code owner June 29, 2024 02:04

int *ptrw = new_indices.ptrw();
for (unsigned int j = 0; j < new_index_count; j++) {
vertex_corners[ptrw[j]].push_back(j);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note, ptrw[] here is already remapped by the shared loop above, so this is equivalent to old code that used vertex_corners[remapped] - no need to remap here again.

@fire
Copy link
Member

fire commented Jun 29, 2024

As far as I can tell one of historical uses of this was to squeeze out more uniformly distributed lod levels all the way to destruction of the mesh.

We can revisit this!

@fire
Copy link
Member

fire commented Jun 29, 2024

Can we pick some meshes that are more human created these samples seem to have a wobbliness associated with video to mesh ai generation algorithms. I expect better results on artistic meshes! but for completeness I think we need to check.

@zeux
Copy link
Contributor Author

zeux commented Jun 29, 2024

Yeah that's fair. I grabbed a couple models from Sketchfab. Left = raycast off, right = raycast on.

https://sketchfab.com/3d-models/3dmodel-based-on-shinypants-by-alan-blackwell-29e4f890f9e140618142f774c11ef233
Here both models are decent but the raycast=on model has a couple cases where the normal is very incorrect, here on the visible leg (and similar artifact on the other side of the model).

image

https://sketchfab.com/3d-models/chairmans-chair--20mb-02152d9f5083418399fbb5f64b129bf6
Here I don't love either result :) but I guess raycast=off shading is smoother which may be less visually distracting in the distance. But neither is obviously better.

image

https://sketchfab.com/3d-models/half-life-alyx-skybox-building-3-2efb2bd5d137451486c9285bbf91228f
Here I had to turn the house ~90 degrees (the front side is perfect on both), the left is still raycast=off - I think in raycast version the rays here hit the building at an odd angle or something and the normals get picked incorrectly.

image

https://sketchfab.com/3d-models/mossberg-590-mariner-dc5b2d69587344229b60b1a7dbe07a81
These are the same basically, I wasn't able to find any angle at which there's a meaningful difference.

image

https://sketchfab.com/3d-models/starship-troopers-arachnid-hopper-2429bfa8df1949c9af070ac445151a8f
These are very close. It's hard to tell, I think raycast=on has harsher lighting as the normals have more creases but overall in LOD scenarios both are fine.

image

@zeux
Copy link
Contributor Author

zeux commented Jun 29, 2024

Ok and final one... these take time to capture :D

https://sketchfab.com/3d-models/tactical-axe-adc24921a6534ed8ba72dff3b8ec9fae
This one is interesting because the handle is better processed by the raycast=on version, but the actual axe surface is better preserved by the raycast=off version, and it's larger so more visually apparent perhaps. But both are probably fine in the distance.

image

Copy link
Member

@fire fire left a comment

Choose a reason for hiding this comment

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

@lyuma Can I have a second opinion on this?

I agree with the approach to this problem with the lod system.

Previously, I wasn't able to systematically make a justification for disabling the normal raycaster, now thanks to zeux we have some justification.

@fire
Copy link
Member

fire commented Jul 1, 2024

I do think this is good for 4.4, we're trying to slowdown merges for 4.3 to manage the release.

@fire
Copy link
Member

fire commented Jul 2, 2024

In our https://docs.godotengine.org/en/stable/contributing/workflow/pr_workflow.html workflow we tend to bias towards one commit unless rare circumstances. Can you squash?

Edited: I'm running the pr through my tests cases.

@Sluggernot
Copy link

Sluggernot commented Jul 2, 2024

Unsure if this is helpful but my local release build tests:
These tests were completed using Reimport for each file.
As expected, the Hydralisk_obj file was unaffected. It is just one ginormous obj.
Still awesome as a change just for perf, let alone visual fidelity.

Before:
Godot Engine v4.3.beta.custom_build.811ce36c6 (2024-06-28 13:55:14 UTC)
Vulkan 1.3.277 - Forward+ - Using Device #0: AMD - AMD Radeon RX 5700 XT
EditorFileSystem: "res://chess_set.glb" import took 18322 ms.
EditorFileSystem: "res://Hydralisk_obj.obj" import took 42330 ms.
EditorFileSystem: "res://scene.glb" import took 440525 ms.

Zeux changes:
Godot Engine v4.3.beta.custom_build.81bf8de4c (2024-06-29 02:02:55 UTC)
EditorFileSystem: "res://chess_set.glb" import took 10499 ms.
EditorFileSystem: "res://Hydralisk_obj.obj" import took 42996 ms.
EditorFileSystem: "res://scene.glb" import took 234556 ms.

(For anyone curious about Hydralisk specifically, it appears to be an insanely detailed Hydralisk from Starcraft, made in Z-Brush. It's a 350MB mesh.)

Normal raycaster makes LOD generation process >2x slower and often
generates normals that look significantly worse compared to what the
simplifier comes up with by default. This was likely different before
last meshoptimizer upgrade, as the attribute metric was not functioning
properly, but now it looks like it's doing more harm than good.

This change makes it disabled by default but keeps an easy option to
re-enable it per mesh using LOD parameters for now until we get more
confidence and can remove the code outright.

Because the long term plan would be to disable this feature entirely,
the scripting API isn't changed, and it's just off-by-default there with
no way to re-enable.
@zeux
Copy link
Contributor Author

zeux commented Jul 2, 2024

Updated the PR with a squashed commit (no code changes)

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

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

Tested locally (rebased on top of master f0d15bb), it works as expected.

Testing project: test_base_mesh.zip

Import speed seems to be ever so slightly faster. With this PR, it takes 21 seconds instead of 23 seconds to open the project with .godot/ removed, reimport all 3D scenes then exit the editor. (This is with the import speed-optimized editor settings.)

PC specifications
  • CPU: Intel Core i9-13900K
  • GPU: NVIDIA GeForce RTX 4090
  • RAM: 64 GB (2×32 GB DDR5-5800 C30)
  • SSD: Solidigm P44 Pro 2 TB
  • OS: Linux (Fedora 39)

Strangely enough, primitive count when previewing the Camera3D in the scene hasn't changed at all with this PR:

LOD disabled Mesh LOD Threshold1 = 1.0 Mesh LOD Threshold = 1024.0
LOD disabled 1 1024

Footnotes

  1. Can be changed in the Project Settings.

@zeux
Copy link
Contributor Author

zeux commented Jul 3, 2024

primitive count when previewing the Camera3D in the scene hasn't changed at all with this PR

Yeah that's expected - the extra normal splits increase the vertex count (which the widget doesn't show and it's more of a factor wrt total mesh memory consumption), the primitive count should be consistent between the setting being off/on.

edit this is shown in the profiler under "Monitors -> Video -> Buffer Mem", and it's 64.39 MB after this change and 67.53 MB before this change, so ~5% savings overall here.

@zeux
Copy link
Contributor Author

zeux commented Jul 3, 2024

Also double checked that test file and the import performance seems reasonably in-line; this one has a very different performance profile, as it's using a lot of generally smaller .obj files instead of a big .gltf file; after the change, LOD generation only accounts for ~10% of the time, so if out of 21 seconds it's 2 seconds give or take, then before the change it would have been about twice that, for 2 extra seconds of import time.

image

@akien-mga akien-mga removed this from the 4.x milestone Jul 3, 2024
@akien-mga akien-mga added this to the 4.4 milestone Jul 3, 2024
Copy link
Member

@clayjohn clayjohn left a comment

Choose a reason for hiding this comment

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

I think the PR looks great overall and I appreciate the careful testing.

I have raised this with the asset team on rocketchat and will continue the discussion there, but IMO we should just remove the raycaster code entirely. We added that code in the first place to mitigate what we saw as quality issues in vanilla MeshOptimizer. Now that MeshOptimizer is producing comparable quality normals, I would just remove it entirely

@zeux
Copy link
Contributor Author

zeux commented Jul 3, 2024

@clayjohn My expectation was basically to do that as a second step in uhh 4.5 or something - my understanding is that neither this PR nor a future removal of the setting is technically a breaking change, other than it affecting a re-import of a given asset (in hopefully positive ways!), so this provides a smoother transition which gives some opportunity to discover cases where raycasted normals were better. I'm fine with either, the raycast code doesn't interfere with the rest of the flow thankfully, so it's not too difficult to keep working, but future changes to the simplifier may be harder to test because there's both modes to consider.

@clayjohn
Copy link
Member

clayjohn commented Jul 4, 2024

We discussed this a bit on the chat and I have changed my position somewhat.

I think ideally we would not have an import setting for this and I suspect that no one will notice the change anyway. However, I do think that zeux' suggestion to provide the setting for compatibility reasons makes sense.

Therefore, I suggest the following:

  1. Let's merge this PR as-is early in the 4.4 release cycle
  2. If users report bugs, we can advise them to enable the raycast option
  3. If a lot of users run into issues, we can make switch to enabling it by default before 4.4 releases
  4. If we don't get any bug reports, then we can just remove the setting altogether before 4.4 releases

@akien-mga akien-mga merged commit e58a753 into godotengine:master Aug 16, 2024
18 checks passed
@akien-mga
Copy link
Member

Thanks!

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.

7 participants