Meshlet new error projection
- New error projection code taken from @zeux's meshoptimizer nanite.cpp demo for determining LOD (thanks zeux!)
- Builder:
compute_lod_group_data() - Runtime:
lod_error_is_imperceptible()
After thinking on it I think I should raise the 1px threshold, to some value determined empirically by doing some comparisons vs forcing lod 0. I think I'm probably overestimating the error and using too-fine grained LODs based on how this PR is responding to camera movement. Will have to test before merging.
Strangely enough I find it impossible to ever render LOD 0 (EDIT: with a 1px threshold)🤔. Higher LODs always seem to have low enough error.
Setting the threshold to 0.5 does let me render LOD 0.
On the other hand, 0.5 threshold means the cliffs seem to take too long to start using coarser LODs as I move away from it. Doing error projection sucks 😭.
I also found https://hhoppe.com/vdrpm.pdf, section 4 "Screen-space geometric error" seems to have a formula. Reading time.
Think I'm happy with the state of this PR now, actually. Going to upload a new bunny and let people test, but I'm calling this done (and an improvement over main).
Just a note, and I'm not suggesting a change here :) but if you ever hit the perspective distortion issues and want a quick fix, this switches from radial distance to the actual projected distance:
diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl
index 8ff42bced..48b10399c 100644
--- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl
+++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl
@@ -147,11 +147,12 @@ fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_
let sphere_world_space = (world_from_local * vec4(lod_sphere.center, 1.0)).xyz;
let radius_world_space = world_scale * lod_sphere.radius;
let error_world_space = world_scale * simplification_error;
+ let sphere_view_space = (view.view_from_world * vec4(sphere_world_space, 1.0)).xyz;
var projected_error = error_world_space;
if view.clip_from_view[3][3] != 1.0 {
// Perspective
- let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space;
+ let distance_to_closest_point_on_sphere = -sphere_view_space.z - radius_world_space;
let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]);
projected_error /= distance_to_closest_point_on_sphere_clamped_to_znear;
}
This works about the same for objects near the center of the screen. For objects close to screen edges, current formula (radial distance) returns a larger distance to sphere, which may effectively underestimate the projected error as objects near edges get stretched more. The tweak above should keep the error estimate conservative, and address the TODO comment.
(.z is negated because in Bevy's view space coordinate system, points in front of the camera have negative Z, similarly to project_view_space_sphere_to_screen_space_aabb)
I personally don't like this because this introduces a dependence on the camera rotation, so LODs switch when camera rotates around its viewpoint; it's correct to do this, however, so in case this ever becomes necessary the change is simple.
@zeux I tried it out, and I actually agree with you. It feels weird that LODs get more detailed on the edges of the screen. If anything, I would want the reverse, so that the things you're looking directly at are more detailed.
Thanks for the code though, good to try both ways and confirm.
@JMS55 can you explain why this is important to get into the 0.15 release for me please?
It's a significant improvement/bugfix for meshlets. We don't have to merge any of the ones in the milestone, but I'd like to so that the blog post I'm going to write covering meshlet changes from bevy 0.14->0.15 can match with what's in mainline.
If you want don't want to block the release or RC on it that's fine, but I would like to get them merged sooner than later so I can move on to the next set of improvements.
Great, thanks for the context. I'll let Cart or one of the rendering SMEs make the final call: I don't feel strongly either way.