From 267b57e565685f8e1555b4c0638c2649f0d0f549 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Mon, 4 Nov 2024 07:20:22 -0800 Subject: [PATCH] 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! --- Cargo.toml | 2 +- crates/bevy_pbr/Cargo.toml | 2 +- crates/bevy_pbr/src/meshlet/from_mesh.rs | 71 +++++++++++------------- crates/bevy_pbr/src/meshlet/mod.rs | 2 +- examples/3d/meshlet.rs | 2 +- 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4bb5ace1b9..9e1ea7c57d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", ], ] diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 6f1d913c65..5dde710843 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -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 } diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index d644a64cdb..5b5c503d3d 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -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, 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::(); - let positions = vertex_data.add(vertices.position_offset); - let mut result: Vec = 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::(), - vertices.vertex_count, - vertices.vertex_stride, - core::ptr::null(), - 0, - core::ptr::null(), - 0, - vertex_locks.as_ptr().cast(), - group_indices.len() / 2, - f32::MAX, - (SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute).bits(), - core::ptr::from_mut(&mut error), - ); - result.resize(index_count, 0u32); - result - }; + 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, + Some(&mut error), + ); // Check if we were able to simplify at least a little if simplified_group_indices.len() as f32 / group_indices.len() as f32 diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index f8a5d46747..74194d043f 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -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::{ diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index c3aeb76480..60e8791b7d 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -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() {