mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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:
parent
4e02d3cdb9
commit
267b57e565
5 changed files with 35 additions and 44 deletions
|
@ -1216,7 +1216,7 @@ setup = [
|
||||||
"curl",
|
"curl",
|
||||||
"-o",
|
"-o",
|
||||||
"assets/models/bunny.meshlet_mesh",
|
"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",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ lz4_flex = { version = "0.11", default-features = false, features = [
|
||||||
], optional = true }
|
], optional = true }
|
||||||
range-alloc = { version = "0.1.3", optional = true }
|
range-alloc = { version = "0.1.3", optional = true }
|
||||||
half = { version = "2", features = ["bytemuck"], 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 }
|
metis = { version = "0.2", optional = true }
|
||||||
itertools = { version = "0.13", optional = true }
|
itertools = { version = "0.13", optional = true }
|
||||||
bitvec = { version = "1", optional = true }
|
bitvec = { version = "1", optional = true }
|
||||||
|
|
|
@ -14,9 +14,8 @@ use derive_more::derive::{Display, Error};
|
||||||
use half::f16;
|
use half::f16;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use meshopt::{
|
use meshopt::{
|
||||||
build_meshlets,
|
build_meshlets, ffi::meshopt_Meshlet, generate_vertex_remap_multi,
|
||||||
ffi::{meshopt_Meshlet, meshopt_simplifyWithAttributes},
|
simplify_with_attributes_and_locks, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream,
|
||||||
generate_vertex_remap_multi, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream,
|
|
||||||
};
|
};
|
||||||
use metis::Graph;
|
use metis::Graph;
|
||||||
use smallvec::SmallVec;
|
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`].
|
/// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`].
|
||||||
///
|
///
|
||||||
/// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4).
|
/// 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;
|
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`.
|
/// 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.
|
/// 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:
|
/// 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
|
/// * 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_buffer = mesh.create_packed_vertex_buffer_data();
|
||||||
let vertex_stride = mesh.get_vertex_size() as usize;
|
let vertex_stride = mesh.get_vertex_size() as usize;
|
||||||
let vertices = VertexDataAdapter::new(&vertex_buffer, vertex_stride, 0).unwrap();
|
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 meshlets = compute_meshlets(&indices, &vertices);
|
||||||
let mut bounding_spheres = meshlets
|
let mut bounding_spheres = meshlets
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -102,7 +102,7 @@ impl MeshletMesh {
|
||||||
Some(&indices),
|
Some(&indices),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut vertex_locks = vec![0; vertices.vertex_count];
|
let mut vertex_locks = vec![false; vertices.vertex_count];
|
||||||
|
|
||||||
// Build further LODs
|
// Build further LODs
|
||||||
let mut simplification_queue = Vec::from_iter(0..meshlets.len());
|
let mut simplification_queue = Vec::from_iter(0..meshlets.len());
|
||||||
|
@ -138,9 +138,14 @@ impl MeshletMesh {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplify the group to ~50% triangle count
|
// Simplify the group to ~50% triangle count
|
||||||
let Some((simplified_group_indices, mut group_error)) =
|
let Some((simplified_group_indices, mut group_error)) = simplify_meshlet_group(
|
||||||
simplify_meshlet_group(&group_meshlets, &meshlets, &vertices, &vertex_locks)
|
&group_meshlets,
|
||||||
else {
|
&meshlets,
|
||||||
|
&vertices,
|
||||||
|
vertex_normals,
|
||||||
|
vertex_stride,
|
||||||
|
&vertex_locks,
|
||||||
|
) else {
|
||||||
// Couldn't simplify the group enough, retry its meshlets later
|
// Couldn't simplify the group enough, retry its meshlets later
|
||||||
retry_queue.extend_from_slice(&group_meshlets);
|
retry_queue.extend_from_slice(&group_meshlets);
|
||||||
continue;
|
continue;
|
||||||
|
@ -338,7 +343,7 @@ fn group_meshlets(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lock_group_borders(
|
fn lock_group_borders(
|
||||||
vertex_locks: &mut [u8],
|
vertex_locks: &mut [bool],
|
||||||
groups: &[SmallVec<[usize; TARGET_MESHLETS_PER_GROUP]>],
|
groups: &[SmallVec<[usize; TARGET_MESHLETS_PER_GROUP]>],
|
||||||
meshlets: &Meshlets,
|
meshlets: &Meshlets,
|
||||||
position_only_vertex_remap: &[u32],
|
position_only_vertex_remap: &[u32],
|
||||||
|
@ -369,17 +374,17 @@ fn lock_group_borders(
|
||||||
// Lock vertices used by more than 1 group
|
// Lock vertices used by more than 1 group
|
||||||
for i in 0..vertex_locks.len() {
|
for i in 0..vertex_locks.len() {
|
||||||
let vertex_id = position_only_vertex_remap[i] as usize;
|
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(
|
fn simplify_meshlet_group(
|
||||||
group_meshlets: &[usize],
|
group_meshlets: &[usize],
|
||||||
meshlets: &Meshlets,
|
meshlets: &Meshlets,
|
||||||
vertices: &VertexDataAdapter<'_>,
|
vertices: &VertexDataAdapter<'_>,
|
||||||
vertex_locks: &[u8],
|
vertex_normals: &[f32],
|
||||||
|
vertex_stride: usize,
|
||||||
|
vertex_locks: &[bool],
|
||||||
) -> Option<(Vec<u32>, f16)> {
|
) -> Option<(Vec<u32>, f16)> {
|
||||||
// Build a new index buffer into the mesh vertex data by combining all meshlet data in the group
|
// Build a new index buffer into the mesh vertex data by combining all meshlet data in the group
|
||||||
let mut group_indices = Vec::new();
|
let mut group_indices = Vec::new();
|
||||||
|
@ -391,33 +396,19 @@ fn simplify_meshlet_group(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplify the group to ~50% triangle count
|
// Simplify the group to ~50% triangle count
|
||||||
// TODO: Simplify using vertex attributes
|
|
||||||
let mut error = 0.0;
|
let mut error = 0.0;
|
||||||
let simplified_group_indices = unsafe {
|
let simplified_group_indices = simplify_with_attributes_and_locks(
|
||||||
let vertex_data = vertices.reader.get_ref();
|
&group_indices,
|
||||||
let vertex_data = vertex_data.as_ptr().cast::<u8>();
|
vertices,
|
||||||
let positions = vertex_data.add(vertices.position_offset);
|
vertex_normals,
|
||||||
let mut result: Vec<u32> = vec![0; group_indices.len()];
|
&[0.5; 3],
|
||||||
let index_count = meshopt_simplifyWithAttributes(
|
vertex_stride,
|
||||||
result.as_mut_ptr().cast(),
|
vertex_locks,
|
||||||
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(),
|
|
||||||
group_indices.len() / 2,
|
group_indices.len() / 2,
|
||||||
f32::MAX,
|
f32::MAX,
|
||||||
(SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute).bits(),
|
SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute,
|
||||||
core::ptr::from_mut(&mut error),
|
Some(&mut error),
|
||||||
);
|
);
|
||||||
result.resize(index_count, 0u32);
|
|
||||||
result
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if we were able to simplify at least a little
|
// Check if we were able to simplify at least a little
|
||||||
if simplified_group_indices.len() as f32 / group_indices.len() as f32
|
if simplified_group_indices.len() as f32 / group_indices.len() as f32
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub(crate) use self::{
|
||||||
pub use self::asset::{MeshletMesh, MeshletMeshLoader, MeshletMeshSaver};
|
pub use self::asset::{MeshletMesh, MeshletMeshLoader, MeshletMeshSaver};
|
||||||
#[cfg(feature = "meshlet_processor")]
|
#[cfg(feature = "meshlet_processor")]
|
||||||
pub use self::from_mesh::{
|
pub use self::from_mesh::{
|
||||||
MeshToMeshletMeshConversionError, DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
|
MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
|
|
|
@ -17,7 +17,7 @@ use camera_controller::{CameraController, CameraControllerPlugin};
|
||||||
use std::{f32::consts::PI, path::Path, process::ExitCode};
|
use std::{f32::consts::PI, path::Path, process::ExitCode};
|
||||||
|
|
||||||
const ASSET_URL: &str =
|
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 {
|
fn main() -> ExitCode {
|
||||||
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {
|
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {
|
||||||
|
|
Loading…
Reference in a new issue