Meshlet normal-aware LOD and meshoptimizer upgrade (#16111)

# Objective

- Choose LOD based on normal simplification error in addition to
position error
- Update meshoptimizer to 0.22, which has a bunch of simplifier
improvements

## Testing

- Did you test these changes? If so, how?
- Visualize normals, and compare LOD changes before and after. Normals
no longer visibly change as the LOD cut changes.
- Are there any parts that need more testing?
  - No
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- Run the meshlet example in this PR and on main and move around to
change the LOD cut. Before running each example, in
meshlet_mesh_material.wgsl, replace `let color = vec3(rand_f(&rng),
rand_f(&rng), rand_f(&rng));` with `let color =
(vertex_output.world_normal + 1.0) / 2.0;`. Make sure to download the
appropriate bunny asset for each branch!
This commit is contained in:
JMS55 2024-11-04 07:20:22 -08:00 committed by GitHub
parent 4e02d3cdb9
commit 267b57e565
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 35 additions and 44 deletions

View file

@ -1216,7 +1216,7 @@ setup = [
"curl",
"-o",
"assets/models/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8483db58832542383820c3f44e4730e566910be7/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/defbd9b32072624d40d57de7d345c66a9edf5d0b/bunny.meshlet_mesh",
],
]

View file

@ -62,7 +62,7 @@ lz4_flex = { version = "0.11", default-features = false, features = [
], optional = true }
range-alloc = { version = "0.1.3", optional = true }
half = { version = "2", features = ["bytemuck"], optional = true }
meshopt = { version = "0.3.0", optional = true }
meshopt = { version = "0.4", optional = true }
metis = { version = "0.2", optional = true }
itertools = { version = "0.13", optional = true }
bitvec = { version = "1", optional = true }

View file

@ -14,9 +14,8 @@ use derive_more::derive::{Display, Error};
use half::f16;
use itertools::Itertools;
use meshopt::{
build_meshlets,
ffi::{meshopt_Meshlet, meshopt_simplifyWithAttributes},
generate_vertex_remap_multi, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream,
build_meshlets, ffi::meshopt_Meshlet, generate_vertex_remap_multi,
simplify_with_attributes_and_locks, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream,
};
use metis::Graph;
use smallvec::SmallVec;
@ -29,7 +28,7 @@ const SIMPLIFICATION_FAILURE_PERCENTAGE: f32 = 0.95;
/// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`].
///
/// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4).
pub const DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4;
pub const MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4;
const CENTIMETERS_PER_METER: f32 = 100.0;
@ -54,7 +53,7 @@ impl MeshletMesh {
/// Vertices are snapped to the nearest (1/2^x)th of a centimeter, where x = `vertex_position_quantization_factor`.
/// E.g. if x = 4, then vertices are snapped to the nearest 1/2^4 = 1/16th of a centimeter.
///
/// Use [`DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR`] as a default, adjusting lower to save memory and disk space, and higher to prevent artifacts if needed.
/// Use [`MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR`] as a default, adjusting lower to save memory and disk space, and higher to prevent artifacts if needed.
///
/// To ensure that two different meshes do not have cracks between them when placed directly next to each other:
/// * Use the same quantization factor when converting each mesh to a meshlet mesh
@ -72,6 +71,7 @@ impl MeshletMesh {
let vertex_buffer = mesh.create_packed_vertex_buffer_data();
let vertex_stride = mesh.get_vertex_size() as usize;
let vertices = VertexDataAdapter::new(&vertex_buffer, vertex_stride, 0).unwrap();
let vertex_normals = bytemuck::cast_slice(&vertex_buffer[12..16]);
let mut meshlets = compute_meshlets(&indices, &vertices);
let mut bounding_spheres = meshlets
.iter()
@ -102,7 +102,7 @@ impl MeshletMesh {
Some(&indices),
);
let mut vertex_locks = vec![0; vertices.vertex_count];
let mut vertex_locks = vec![false; vertices.vertex_count];
// Build further LODs
let mut simplification_queue = Vec::from_iter(0..meshlets.len());
@ -138,9 +138,14 @@ impl MeshletMesh {
}
// Simplify the group to ~50% triangle count
let Some((simplified_group_indices, mut group_error)) =
simplify_meshlet_group(&group_meshlets, &meshlets, &vertices, &vertex_locks)
else {
let Some((simplified_group_indices, mut group_error)) = simplify_meshlet_group(
&group_meshlets,
&meshlets,
&vertices,
vertex_normals,
vertex_stride,
&vertex_locks,
) else {
// Couldn't simplify the group enough, retry its meshlets later
retry_queue.extend_from_slice(&group_meshlets);
continue;
@ -338,7 +343,7 @@ fn group_meshlets(
}
fn lock_group_borders(
vertex_locks: &mut [u8],
vertex_locks: &mut [bool],
groups: &[SmallVec<[usize; TARGET_MESHLETS_PER_GROUP]>],
meshlets: &Meshlets,
position_only_vertex_remap: &[u32],
@ -369,17 +374,17 @@ fn lock_group_borders(
// Lock vertices used by more than 1 group
for i in 0..vertex_locks.len() {
let vertex_id = position_only_vertex_remap[i] as usize;
vertex_locks[i] = (position_only_locks[vertex_id] == -2) as u8;
vertex_locks[i] = position_only_locks[vertex_id] == -2;
}
}
#[allow(unsafe_code)]
#[allow(clippy::undocumented_unsafe_blocks)]
fn simplify_meshlet_group(
group_meshlets: &[usize],
meshlets: &Meshlets,
vertices: &VertexDataAdapter<'_>,
vertex_locks: &[u8],
vertex_normals: &[f32],
vertex_stride: usize,
vertex_locks: &[bool],
) -> Option<(Vec<u32>, f16)> {
// Build a new index buffer into the mesh vertex data by combining all meshlet data in the group
let mut group_indices = Vec::new();
@ -391,33 +396,19 @@ fn simplify_meshlet_group(
}
// Simplify the group to ~50% triangle count
// TODO: Simplify using vertex attributes
let mut error = 0.0;
let simplified_group_indices = unsafe {
let vertex_data = vertices.reader.get_ref();
let vertex_data = vertex_data.as_ptr().cast::<u8>();
let positions = vertex_data.add(vertices.position_offset);
let mut result: Vec<u32> = vec![0; group_indices.len()];
let index_count = meshopt_simplifyWithAttributes(
result.as_mut_ptr().cast(),
group_indices.as_ptr().cast(),
group_indices.len(),
positions.cast::<f32>(),
vertices.vertex_count,
vertices.vertex_stride,
core::ptr::null(),
0,
core::ptr::null(),
0,
vertex_locks.as_ptr().cast(),
let simplified_group_indices = simplify_with_attributes_and_locks(
&group_indices,
vertices,
vertex_normals,
&[0.5; 3],
vertex_stride,
vertex_locks,
group_indices.len() / 2,
f32::MAX,
(SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute).bits(),
core::ptr::from_mut(&mut error),
SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute,
Some(&mut error),
);
result.resize(index_count, 0u32);
result
};
// Check if we were able to simplify at least a little
if simplified_group_indices.len() as f32 / group_indices.len() as f32

View file

@ -36,7 +36,7 @@ pub(crate) use self::{
pub use self::asset::{MeshletMesh, MeshletMeshLoader, MeshletMeshSaver};
#[cfg(feature = "meshlet_processor")]
pub use self::from_mesh::{
MeshToMeshletMeshConversionError, DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
};
use self::{

View file

@ -17,7 +17,7 @@ use camera_controller::{CameraController, CameraControllerPlugin};
use std::{f32::consts::PI, path::Path, process::ExitCode};
const ASSET_URL: &str =
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8483db58832542383820c3f44e4730e566910be7/bunny.meshlet_mesh";
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/defbd9b32072624d40d57de7d345c66a9edf5d0b/bunny.meshlet_mesh";
fn main() -> ExitCode {
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {