Meshlet new error projection (#15846)

* 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()`
This commit is contained in:
JMS55 2024-10-22 13:14:30 -07:00 committed by GitHub
parent 9930df83ed
commit 9d54fe0370
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 233 additions and 137 deletions

View file

@ -1213,7 +1213,7 @@ setup = [
"curl", "curl",
"-o", "-o",
"assets/models/bunny.meshlet_mesh", "assets/models/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh", "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh",
], ],
] ]

View file

@ -18,7 +18,7 @@ shader_format_glsl = ["bevy_render/shader_format_glsl"]
trace = ["bevy_render/trace"] trace = ["bevy_render/trace"]
ios_simulator = ["bevy_render/ios_simulator"] ios_simulator = ["bevy_render/ios_simulator"]
# Enables the meshlet renderer for dense high-poly scenes (experimental) # Enables the meshlet renderer for dense high-poly scenes (experimental)
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:bevy_tasks"] meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:half", "dep:bevy_tasks"]
# Enables processing meshes into meshlet meshes # Enables processing meshes into meshlet meshes
meshlet_processor = [ meshlet_processor = [
"meshlet", "meshlet",
@ -50,16 +50,17 @@ bevy_window = { path = "../bevy_window", version = "0.15.0-dev" }
# other # other
bitflags = "2.3" bitflags = "2.3"
fixedbitset = "0.5" fixedbitset = "0.5"
# meshlet
lz4_flex = { version = "0.11", default-features = false, features = [
"frame",
], optional = true }
derive_more = { version = "1", default-features = false, features = [ derive_more = { version = "1", default-features = false, features = [
"error", "error",
"from", "from",
"display", "display",
] } ] }
# meshlet
lz4_flex = { version = "0.11", default-features = false, features = [
"frame",
], 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 }
meshopt = { version = "0.3.0", optional = true } meshopt = { version = "0.3.0", 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 }

View file

@ -9,6 +9,7 @@ use bevy_reflect::TypePath;
use bevy_tasks::block_on; use bevy_tasks::block_on;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use derive_more::derive::{Display, Error, From}; use derive_more::derive::{Display, Error, From};
use half::f16;
use lz4_flex::frame::{FrameDecoder, FrameEncoder}; use lz4_flex::frame::{FrameDecoder, FrameEncoder};
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -51,6 +52,8 @@ pub struct MeshletMesh {
pub(crate) meshlets: Arc<[Meshlet]>, pub(crate) meshlets: Arc<[Meshlet]>,
/// Spherical bounding volumes. /// Spherical bounding volumes.
pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>, pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>,
/// Meshlet group and parent group simplification errors.
pub(crate) meshlet_simplification_errors: Arc<[MeshletSimplificationError]>,
} }
/// A single meshlet within a [`MeshletMesh`]. /// A single meshlet within a [`MeshletMesh`].
@ -90,12 +93,12 @@ pub struct Meshlet {
#[derive(Copy, Clone, Pod, Zeroable)] #[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)] #[repr(C)]
pub struct MeshletBoundingSpheres { pub struct MeshletBoundingSpheres {
/// The bounding sphere used for frustum and occlusion culling for this meshlet. /// Bounding sphere used for frustum and occlusion culling for this meshlet.
pub self_culling: MeshletBoundingSphere, pub culling_sphere: MeshletBoundingSphere,
/// The bounding sphere used for determining if this meshlet is at the correct level of detail for a given view. /// Bounding sphere used for determining if this meshlet's group is at the correct level of detail for a given view.
pub self_lod: MeshletBoundingSphere, pub lod_group_sphere: MeshletBoundingSphere,
/// The bounding sphere used for determining if this meshlet's parent is at the correct level of detail for a given view. /// Bounding sphere used for determining if this meshlet's parent group is at the correct level of detail for a given view.
pub parent_lod: MeshletBoundingSphere, pub lod_parent_group_sphere: MeshletBoundingSphere,
} }
/// A spherical bounding volume used for a [`Meshlet`]. /// A spherical bounding volume used for a [`Meshlet`].
@ -106,6 +109,16 @@ pub struct MeshletBoundingSphere {
pub radius: f32, pub radius: f32,
} }
/// Simplification error used for choosing level of detail for a [`Meshlet`].
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct MeshletSimplificationError {
/// Simplification error used for determining if this meshlet's group is at the correct level of detail for a given view.
pub group_error: f16,
/// Simplification error used for determining if this meshlet's parent group is at the correct level of detail for a given view.
pub parent_group_error: f16,
}
/// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets. /// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
pub struct MeshletMeshSaver; pub struct MeshletMeshSaver;
@ -139,6 +152,7 @@ impl AssetSaver for MeshletMeshSaver {
write_slice(&asset.indices, &mut writer)?; write_slice(&asset.indices, &mut writer)?;
write_slice(&asset.meshlets, &mut writer)?; write_slice(&asset.meshlets, &mut writer)?;
write_slice(&asset.meshlet_bounding_spheres, &mut writer)?; write_slice(&asset.meshlet_bounding_spheres, &mut writer)?;
write_slice(&asset.meshlet_simplification_errors, &mut writer)?;
writer.finish()?; writer.finish()?;
Ok(()) Ok(())
@ -179,6 +193,7 @@ impl AssetLoader for MeshletMeshLoader {
let indices = read_slice(reader)?; let indices = read_slice(reader)?;
let meshlets = read_slice(reader)?; let meshlets = read_slice(reader)?;
let meshlet_bounding_spheres = read_slice(reader)?; let meshlet_bounding_spheres = read_slice(reader)?;
let meshlet_simplification_errors = read_slice(reader)?;
Ok(MeshletMesh { Ok(MeshletMesh {
vertex_positions, vertex_positions,
@ -187,6 +202,7 @@ impl AssetLoader for MeshletMeshLoader {
indices, indices,
meshlets, meshlets,
meshlet_bounding_spheres, meshlet_bounding_spheres,
meshlet_simplification_errors,
}) })
} }

View file

@ -1,6 +1,7 @@
#import bevy_pbr::meshlet_bindings::{ #import bevy_pbr::meshlet_bindings::{
meshlet_cluster_meshlet_ids, meshlet_cluster_meshlet_ids,
meshlet_bounding_spheres, meshlet_bounding_spheres,
meshlet_simplification_errors,
meshlet_cluster_instance_ids, meshlet_cluster_instance_ids,
meshlet_instance_uniforms, meshlet_instance_uniforms,
meshlet_second_pass_candidates, meshlet_second_pass_candidates,
@ -13,6 +14,7 @@
meshlet_hardware_raster_indirect_args, meshlet_hardware_raster_indirect_args,
meshlet_raster_clusters, meshlet_raster_clusters,
meshlet_raster_cluster_rightmost_slot, meshlet_raster_cluster_rightmost_slot,
MeshletBoundingSphere,
} }
#import bevy_render::maths::affine3_to_square #import bevy_render::maths::affine3_to_square
@ -48,8 +50,8 @@ fn cull_clusters(
let world_from_local = affine3_to_square(instance_uniform.world_from_local); let world_from_local = affine3_to_square(instance_uniform.world_from_local);
let world_scale = max(length(world_from_local[0]), max(length(world_from_local[1]), length(world_from_local[2]))); let world_scale = max(length(world_from_local[0]), max(length(world_from_local[1]), length(world_from_local[2])));
let bounding_spheres = meshlet_bounding_spheres[meshlet_id]; let bounding_spheres = meshlet_bounding_spheres[meshlet_id];
let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_culling.center, 1.0); let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0);
let culling_bounding_sphere_radius = world_scale * bounding_spheres.self_culling.radius; let culling_bounding_sphere_radius = world_scale * bounding_spheres.culling_sphere.radius;
#ifdef MESHLET_FIRST_CULLING_PASS #ifdef MESHLET_FIRST_CULLING_PASS
// Frustum culling // Frustum culling
@ -60,19 +62,10 @@ fn cull_clusters(
} }
} }
// Calculate view-space LOD bounding sphere for the cluster // Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible)
let lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_lod.center, 1.0); let simplification_errors = unpack2x16float(meshlet_simplification_errors[meshlet_id]);
let lod_bounding_sphere_radius = world_scale * bounding_spheres.self_lod.radius; let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale);
let lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(lod_bounding_sphere_center.xyz, 1.0)).xyz; let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale);
// Calculate view-space LOD bounding sphere for the cluster's parent
let parent_lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.parent_lod.center, 1.0);
let parent_lod_bounding_sphere_radius = world_scale * bounding_spheres.parent_lod.radius;
let parent_lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(parent_lod_bounding_sphere_center.xyz, 1.0)).xyz;
// Check LOD cut (cluster error imperceptible, and parent error not imperceptible)
let lod_is_ok = lod_error_is_imperceptible(lod_bounding_sphere_center_view_space, lod_bounding_sphere_radius);
let parent_lod_is_ok = lod_error_is_imperceptible(parent_lod_bounding_sphere_center_view_space, parent_lod_bounding_sphere_radius);
if !lod_is_ok || parent_lod_is_ok { return; } if !lod_is_ok || parent_lod_is_ok { return; }
#endif #endif
@ -80,8 +73,8 @@ fn cull_clusters(
#ifdef MESHLET_FIRST_CULLING_PASS #ifdef MESHLET_FIRST_CULLING_PASS
let previous_world_from_local = affine3_to_square(instance_uniform.previous_world_from_local); let previous_world_from_local = affine3_to_square(instance_uniform.previous_world_from_local);
let previous_world_from_local_scale = max(length(previous_world_from_local[0]), max(length(previous_world_from_local[1]), length(previous_world_from_local[2]))); let previous_world_from_local_scale = max(length(previous_world_from_local[0]), max(length(previous_world_from_local[1]), length(previous_world_from_local[2])));
let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.self_culling.center, 1.0); let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0);
let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.self_culling.radius; let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.culling_sphere.radius;
let occlusion_culling_bounding_sphere_center_view_space = (previous_view.view_from_world * vec4(occlusion_culling_bounding_sphere_center.xyz, 1.0)).xyz; let occlusion_culling_bounding_sphere_center_view_space = (previous_view.view_from_world * vec4(occlusion_culling_bounding_sphere_center.xyz, 1.0)).xyz;
#else #else
let occlusion_culling_bounding_sphere_center = culling_bounding_sphere_center; let occlusion_culling_bounding_sphere_center = culling_bounding_sphere_center;
@ -148,14 +141,23 @@ fn cull_clusters(
meshlet_raster_clusters[buffer_slot] = cluster_id; meshlet_raster_clusters[buffer_slot] = cluster_id;
} }
// https://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space/21649403#21649403 // https://github.com/zeux/meshoptimizer/blob/1e48e96c7e8059321de492865165e9ef071bffba/demo/nanite.cpp#L115
fn lod_error_is_imperceptible(cp: vec3<f32>, r: f32) -> bool { fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_error: f32, world_from_local: mat4x4<f32>, world_scale: f32) -> bool {
let d2 = dot(cp, cp); let sphere_world_space = (world_from_local * vec4(lod_sphere.center, 1.0)).xyz;
let r2 = r * r; let radius_world_space = world_scale * lod_sphere.radius;
let sphere_diameter_uv = view.clip_from_view[0][0] * r / sqrt(d2 - r2); let error_world_space = world_scale * simplification_error;
let view_size = f32(max(view.viewport.z, view.viewport.w));
let sphere_diameter_pixels = sphere_diameter_uv * view_size; var projected_error = error_world_space;
return sphere_diameter_pixels < 1.0; 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_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;
}
projected_error *= view.clip_from_view[1][1] * 0.5;
projected_error *= view.viewport.w;
return projected_error < 1.0;
} }
// https://zeux.io/2023/01/12/approximate-projected-bounds // https://zeux.io/2023/01/12/approximate-projected-bounds

View file

@ -1,4 +1,6 @@
use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh}; use super::asset::{
Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh, MeshletSimplificationError,
};
use alloc::borrow::Cow; use alloc::borrow::Cow;
use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles}; use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles};
use bevy_render::{ use bevy_render::{
@ -7,13 +9,12 @@ use bevy_render::{
}; };
use bevy_utils::HashMap; use bevy_utils::HashMap;
use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
use core::ops::Range; use core::{iter, ops::Range};
use derive_more::derive::{Display, Error}; use derive_more::derive::{Display, Error};
use half::f16;
use itertools::Itertools; use itertools::Itertools;
use meshopt::{ use meshopt::{
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds, build_meshlets, ffi::meshopt_Meshlet, simplify, Meshlets, SimplifyOptions, VertexDataAdapter,
ffi::{meshopt_Bounds, meshopt_Meshlet},
simplify, Meshlets, SimplifyOptions, VertexDataAdapter,
}; };
use metis::Graph; use metis::Graph;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -69,19 +70,21 @@ impl MeshletMesh {
let mut bounding_spheres = meshlets let mut bounding_spheres = meshlets
.iter() .iter()
.map(|meshlet| compute_meshlet_bounds(meshlet, &vertices)) .map(|meshlet| compute_meshlet_bounds(meshlet, &vertices))
.map(convert_meshlet_bounds)
.map(|bounding_sphere| MeshletBoundingSpheres { .map(|bounding_sphere| MeshletBoundingSpheres {
self_culling: bounding_sphere, culling_sphere: bounding_sphere,
self_lod: MeshletBoundingSphere { lod_group_sphere: bounding_sphere,
center: bounding_sphere.center, lod_parent_group_sphere: MeshletBoundingSphere {
center: Vec3::ZERO,
radius: 0.0, radius: 0.0,
}, },
parent_lod: MeshletBoundingSphere {
center: bounding_sphere.center,
radius: f32::MAX,
},
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut simplification_errors = iter::repeat(MeshletSimplificationError {
group_error: f16::ZERO,
parent_group_error: f16::MAX,
})
.take(meshlets.len())
.collect::<Vec<_>>();
// Build further LODs // Build further LODs
let mut simplification_queue = 0..meshlets.len(); let mut simplification_queue = 0..meshlets.len();
@ -107,22 +110,13 @@ impl MeshletMesh {
continue; continue;
}; };
// Force parent error to be >= child error (we're currently building the parent from its children) // Compute LOD data for the group
group_error = group_meshlets.iter().fold(group_error, |acc, meshlet_id| { let group_bounding_sphere = compute_lod_group_data(
acc.max(bounding_spheres[*meshlet_id].self_lod.radius) &group_meshlets,
}); &mut group_error,
&mut bounding_spheres,
// Build a new LOD bounding sphere for the simplified group as a whole &mut simplification_errors,
let mut group_bounding_sphere = convert_meshlet_bounds(compute_cluster_bounds( );
&simplified_group_indices,
&vertices,
));
group_bounding_sphere.radius = group_error;
// For each meshlet in the group set their parent LOD bounding sphere to that of the simplified group
for meshlet_id in group_meshlets {
bounding_spheres[meshlet_id].parent_lod = group_bounding_sphere;
}
// Build new meshlets using the simplified group // Build new meshlets using the simplified group
let new_meshlets_count = split_simplified_group_into_new_meshlets( let new_meshlets_count = split_simplified_group_into_new_meshlets(
@ -131,22 +125,24 @@ impl MeshletMesh {
&mut meshlets, &mut meshlets,
); );
// Calculate the culling bounding sphere for the new meshlets and set their LOD bounding spheres // Calculate the culling bounding sphere for the new meshlets and set their LOD group data
let new_meshlet_ids = (meshlets.len() - new_meshlets_count)..meshlets.len(); let new_meshlet_ids = (meshlets.len() - new_meshlets_count)..meshlets.len();
bounding_spheres.extend( bounding_spheres.extend(new_meshlet_ids.clone().map(|meshlet_id| {
new_meshlet_ids MeshletBoundingSpheres {
.map(|meshlet_id| { culling_sphere: compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices),
compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices) lod_group_sphere: group_bounding_sphere,
}) lod_parent_group_sphere: MeshletBoundingSphere {
.map(convert_meshlet_bounds) center: Vec3::ZERO,
.map(|bounding_sphere| MeshletBoundingSpheres { radius: 0.0,
self_culling: bounding_sphere, },
self_lod: group_bounding_sphere, }
parent_lod: MeshletBoundingSphere { }));
center: group_bounding_sphere.center, simplification_errors.extend(
radius: f32::MAX, iter::repeat(MeshletSimplificationError {
}, group_error,
}), parent_group_error: f16::MAX,
})
.take(new_meshlet_ids.len()),
); );
} }
@ -179,6 +175,7 @@ impl MeshletMesh {
indices: meshlets.triangles.into(), indices: meshlets.triangles.into(),
meshlets: bevy_meshlets.into(), meshlets: bevy_meshlets.into(),
meshlet_bounding_spheres: bounding_spheres.into(), meshlet_bounding_spheres: bounding_spheres.into(),
meshlet_simplification_errors: simplification_errors.into(),
}) })
} }
} }
@ -303,7 +300,7 @@ fn simplify_meshlet_group(
group_meshlets: &[usize], group_meshlets: &[usize],
meshlets: &Meshlets, meshlets: &Meshlets,
vertices: &VertexDataAdapter<'_>, vertices: &VertexDataAdapter<'_>,
) -> Option<(Vec<u32>, f32)> { ) -> 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();
for meshlet_id in group_meshlets { for meshlet_id in group_meshlets {
@ -330,10 +327,55 @@ fn simplify_meshlet_group(
return None; return None;
} }
// Convert error from diameter to radius Some((simplified_group_indices, f16::from_f32(error)))
error *= 0.5; }
Some((simplified_group_indices, error)) fn compute_lod_group_data(
group_meshlets: &[usize],
group_error: &mut f16,
bounding_spheres: &mut [MeshletBoundingSpheres],
simplification_errors: &mut [MeshletSimplificationError],
) -> MeshletBoundingSphere {
let mut group_bounding_sphere = MeshletBoundingSphere {
center: Vec3::ZERO,
radius: 0.0,
};
// Compute the lod group sphere center as a weighted average of the children spheres
let mut weight = 0.0;
for meshlet_id in group_meshlets {
let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere;
group_bounding_sphere.center +=
meshlet_lod_bounding_sphere.center * meshlet_lod_bounding_sphere.radius;
weight += meshlet_lod_bounding_sphere.radius;
}
group_bounding_sphere.center /= weight;
// Force parent group sphere to contain all child group spheres (we're currently building the parent from its children)
// TODO: This does not produce the absolute minimal bounding sphere. Doing so is non-trivial.
// "Smallest enclosing balls of balls" http://www.inf.ethz.ch/personal/emo/DoctThesisFiles/fischer05.pdf
for meshlet_id in group_meshlets {
let meshlet_lod_bounding_sphere = bounding_spheres[*meshlet_id].lod_group_sphere;
let d = meshlet_lod_bounding_sphere
.center
.distance(group_bounding_sphere.center);
group_bounding_sphere.radius = group_bounding_sphere
.radius
.max(meshlet_lod_bounding_sphere.radius + d);
}
// Force parent error to be >= child error (we're currently building the parent from its children)
for meshlet_id in group_meshlets {
*group_error = group_error.max(simplification_errors[*meshlet_id].group_error);
}
// Set the children's lod parent group data to the new lod group we just made
for meshlet_id in group_meshlets {
bounding_spheres[*meshlet_id].lod_parent_group_sphere = group_bounding_sphere;
simplification_errors[*meshlet_id].parent_group_error = *group_error;
}
group_bounding_sphere
} }
fn split_simplified_group_into_new_meshlets( fn split_simplified_group_into_new_meshlets(
@ -449,7 +491,11 @@ fn build_and_compress_meshlet_vertex_data(
}); });
} }
fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere { fn compute_meshlet_bounds(
meshlet: meshopt::Meshlet<'_>,
vertices: &VertexDataAdapter<'_>,
) -> MeshletBoundingSphere {
let bounds = meshopt::compute_meshlet_bounds(meshlet, vertices);
MeshletBoundingSphere { MeshletBoundingSphere {
center: bounds.center.into(), center: bounds.center.into(),
radius: bounds.radius, radius: bounds.radius,

View file

@ -25,9 +25,9 @@ fn get_meshlet_triangle_count(meshlet: ptr<function, Meshlet>) -> u32 {
} }
struct MeshletBoundingSpheres { struct MeshletBoundingSpheres {
self_culling: MeshletBoundingSphere, culling_sphere: MeshletBoundingSphere,
self_lod: MeshletBoundingSphere, lod_group_sphere: MeshletBoundingSphere,
parent_lod: MeshletBoundingSphere, lod_parent_group_sphere: MeshletBoundingSphere,
} }
struct MeshletBoundingSphere { struct MeshletBoundingSphere {
@ -62,16 +62,17 @@ var<push_constant> cluster_count: u32;
var<push_constant> meshlet_raster_cluster_rightmost_slot: u32; var<push_constant> meshlet_raster_cluster_rightmost_slot: u32;
@group(0) @binding(0) var<storage, read> meshlet_cluster_meshlet_ids: array<u32>; // Per cluster @group(0) @binding(0) var<storage, read> meshlet_cluster_meshlet_ids: array<u32>; // Per cluster
@group(0) @binding(1) var<storage, read> meshlet_bounding_spheres: array<MeshletBoundingSpheres>; // Per meshlet @group(0) @binding(1) var<storage, read> meshlet_bounding_spheres: array<MeshletBoundingSpheres>; // Per meshlet
@group(0) @binding(2) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster @group(0) @binding(2) var<storage, read> meshlet_simplification_errors: array<u32>; // Per meshlet
@group(0) @binding(3) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance @group(0) @binding(3) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
@group(0) @binding(4) var<storage, read> meshlet_view_instance_visibility: array<u32>; // 1 bit per entity instance, packed as a bitmask @group(0) @binding(4) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
@group(0) @binding(5) var<storage, read_write> meshlet_second_pass_candidates: array<atomic<u32>>; // 1 bit per cluster , packed as a bitmask @group(0) @binding(5) var<storage, read> meshlet_view_instance_visibility: array<u32>; // 1 bit per entity instance, packed as a bitmask
@group(0) @binding(6) var<storage, read_write> meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles @group(0) @binding(6) var<storage, read_write> meshlet_second_pass_candidates: array<atomic<u32>>; // 1 bit per cluster , packed as a bitmask
@group(0) @binding(7) var<storage, read_write> meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles @group(0) @binding(7) var<storage, read_write> meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(8) var<storage, read_write> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles @group(0) @binding(8) var<storage, read_write> meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(9) var depth_pyramid: texture_2d<f32>; // From the end of the last frame for the first culling pass, and from the first raster pass for the second culling pass @group(0) @binding(9) var<storage, read_write> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles
@group(0) @binding(10) var<uniform> view: View; @group(0) @binding(10) var depth_pyramid: texture_2d<f32>; // From the end of the last frame for the first culling pass, and from the first raster pass for the second culling pass
@group(0) @binding(11) var<uniform> previous_view: PreviousViewUniforms; @group(0) @binding(11) var<uniform> view: View;
@group(0) @binding(12) var<uniform> previous_view: PreviousViewUniforms;
fn should_cull_instance(instance_id: u32) -> bool { fn should_cull_instance(instance_id: u32) -> bool {
let bit_offset = instance_id % 32u; let bit_offset = instance_id % 32u;

View file

@ -1,5 +1,5 @@
use super::{ use super::{
asset::{Meshlet, MeshletBoundingSpheres}, asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError},
persistent_buffer::PersistentGpuBuffer, persistent_buffer::PersistentGpuBuffer,
MeshletMesh, MeshletMesh,
}; };
@ -26,7 +26,8 @@ pub struct MeshletMeshManager {
pub indices: PersistentGpuBuffer<Arc<[u8]>>, pub indices: PersistentGpuBuffer<Arc<[u8]>>,
pub meshlets: PersistentGpuBuffer<Arc<[Meshlet]>>, pub meshlets: PersistentGpuBuffer<Arc<[Meshlet]>>,
pub meshlet_bounding_spheres: PersistentGpuBuffer<Arc<[MeshletBoundingSpheres]>>, pub meshlet_bounding_spheres: PersistentGpuBuffer<Arc<[MeshletBoundingSpheres]>>,
meshlet_mesh_slices: HashMap<AssetId<MeshletMesh>, [Range<BufferAddress>; 6]>, pub meshlet_simplification_errors: PersistentGpuBuffer<Arc<[MeshletSimplificationError]>>,
meshlet_mesh_slices: HashMap<AssetId<MeshletMesh>, [Range<BufferAddress>; 7]>,
} }
impl FromWorld for MeshletMeshManager { impl FromWorld for MeshletMeshManager {
@ -42,6 +43,10 @@ impl FromWorld for MeshletMeshManager {
"meshlet_bounding_spheres", "meshlet_bounding_spheres",
render_device, render_device,
), ),
meshlet_simplification_errors: PersistentGpuBuffer::new(
"meshlet_simplification_errors",
render_device,
),
meshlet_mesh_slices: HashMap::new(), meshlet_mesh_slices: HashMap::new(),
} }
} }
@ -81,6 +86,9 @@ impl MeshletMeshManager {
let meshlet_bounding_spheres_slice = self let meshlet_bounding_spheres_slice = self
.meshlet_bounding_spheres .meshlet_bounding_spheres
.queue_write(Arc::clone(&meshlet_mesh.meshlet_bounding_spheres), ()); .queue_write(Arc::clone(&meshlet_mesh.meshlet_bounding_spheres), ());
let meshlet_simplification_errors_slice = self
.meshlet_simplification_errors
.queue_write(Arc::clone(&meshlet_mesh.meshlet_simplification_errors), ());
[ [
vertex_positions_slice, vertex_positions_slice,
@ -89,11 +97,12 @@ impl MeshletMeshManager {
indices_slice, indices_slice,
meshlets_slice, meshlets_slice,
meshlet_bounding_spheres_slice, meshlet_bounding_spheres_slice,
meshlet_simplification_errors_slice,
] ]
}; };
// If the MeshletMesh asset has not been uploaded to the GPU yet, queue it for uploading // If the MeshletMesh asset has not been uploaded to the GPU yet, queue it for uploading
let [_, _, _, _, meshlets_slice, _] = self let [_, _, _, _, meshlets_slice, _, _] = self
.meshlet_mesh_slices .meshlet_mesh_slices
.entry(asset_id) .entry(asset_id)
.or_insert_with_key(queue_meshlet_mesh) .or_insert_with_key(queue_meshlet_mesh)
@ -106,7 +115,7 @@ impl MeshletMeshManager {
pub fn remove(&mut self, asset_id: &AssetId<MeshletMesh>) { pub fn remove(&mut self, asset_id: &AssetId<MeshletMesh>) {
if let Some( if let Some(
[vertex_positions_slice, vertex_normals_slice, vertex_uvs_slice, indices_slice, meshlets_slice, meshlet_bounding_spheres_slice], [vertex_positions_slice, vertex_normals_slice, vertex_uvs_slice, indices_slice, meshlets_slice, meshlet_bounding_spheres_slice, meshlet_simplification_errors_slice],
) = self.meshlet_mesh_slices.remove(asset_id) ) = self.meshlet_mesh_slices.remove(asset_id)
{ {
self.vertex_positions self.vertex_positions
@ -117,6 +126,8 @@ impl MeshletMeshManager {
self.meshlets.mark_slice_unused(meshlets_slice); self.meshlets.mark_slice_unused(meshlets_slice);
self.meshlet_bounding_spheres self.meshlet_bounding_spheres
.mark_slice_unused(meshlet_bounding_spheres_slice); .mark_slice_unused(meshlet_bounding_spheres_slice);
self.meshlet_simplification_errors
.mark_slice_unused(meshlet_simplification_errors_slice);
} }
} }
} }
@ -145,4 +156,7 @@ pub fn perform_pending_meshlet_mesh_writes(
meshlet_mesh_manager meshlet_mesh_manager
.meshlet_bounding_spheres .meshlet_bounding_spheres
.perform_writes(&render_queue, &render_device); .perform_writes(&render_queue, &render_device);
meshlet_mesh_manager
.meshlet_simplification_errors
.perform_writes(&render_queue, &render_device);
} }

View file

@ -290,6 +290,7 @@ impl Plugin for MeshletPlugin {
} }
} }
/// The meshlet mesh equivalent of [`bevy_render::mesh::Mesh3d`].
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)] #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default)] #[reflect(Component, Default)]
#[require(Transform, Visibility)] #[require(Transform, Visibility)]

View file

@ -1,10 +1,42 @@
use super::{ use super::{
asset::{Meshlet, MeshletBoundingSpheres}, asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError},
persistent_buffer::PersistentGpuBufferable, persistent_buffer::PersistentGpuBufferable,
}; };
use alloc::sync::Arc; use alloc::sync::Arc;
use bevy_math::Vec2; use bevy_math::Vec2;
impl PersistentGpuBufferable for Arc<[Meshlet]> {
type Metadata = (u64, u64, u64);
fn size_in_bytes(&self) -> usize {
self.len() * size_of::<Meshlet>()
}
fn write_bytes_le(
&self,
(vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata,
buffer_slice: &mut [u8],
) {
let vertex_position_offset = (vertex_position_offset * 8) as u32;
let vertex_attribute_offset = (vertex_attribute_offset as usize / size_of::<u32>()) as u32;
let index_offset = index_offset as u32;
for (i, meshlet) in self.iter().enumerate() {
let size = size_of::<Meshlet>();
let i = i * size;
let bytes = bytemuck::cast::<_, [u8; size_of::<Meshlet>()]>(Meshlet {
start_vertex_position_bit: meshlet.start_vertex_position_bit
+ vertex_position_offset,
start_vertex_attribute_id: meshlet.start_vertex_attribute_id
+ vertex_attribute_offset,
start_index_id: meshlet.start_index_id + index_offset,
..*meshlet
});
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
}
}
}
impl PersistentGpuBufferable for Arc<[u8]> { impl PersistentGpuBufferable for Arc<[u8]> {
type Metadata = (); type Metadata = ();
@ -41,38 +73,6 @@ impl PersistentGpuBufferable for Arc<[Vec2]> {
} }
} }
impl PersistentGpuBufferable for Arc<[Meshlet]> {
type Metadata = (u64, u64, u64);
fn size_in_bytes(&self) -> usize {
self.len() * size_of::<Meshlet>()
}
fn write_bytes_le(
&self,
(vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata,
buffer_slice: &mut [u8],
) {
let vertex_position_offset = (vertex_position_offset * 8) as u32;
let vertex_attribute_offset = (vertex_attribute_offset as usize / size_of::<u32>()) as u32;
let index_offset = index_offset as u32;
for (i, meshlet) in self.iter().enumerate() {
let size = size_of::<Meshlet>();
let i = i * size;
let bytes = bytemuck::cast::<_, [u8; size_of::<Meshlet>()]>(Meshlet {
start_vertex_position_bit: meshlet.start_vertex_position_bit
+ vertex_position_offset,
start_vertex_attribute_id: meshlet.start_vertex_attribute_id
+ vertex_attribute_offset,
start_index_id: meshlet.start_index_id + index_offset,
..*meshlet
});
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
}
}
}
impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> { impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> {
type Metadata = (); type Metadata = ();
@ -84,3 +84,15 @@ impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> {
buffer_slice.clone_from_slice(bytemuck::cast_slice(self)); buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
} }
} }
impl PersistentGpuBufferable for Arc<[MeshletSimplificationError]> {
type Metadata = ();
fn size_in_bytes(&self) -> usize {
self.len() * size_of::<MeshletSimplificationError>()
}
fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) {
buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
}
}

View file

@ -135,6 +135,7 @@ impl ResourceManager {
storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None),
storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None),
storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None),
storage_buffer_read_only_sized(false, None),
storage_buffer_sized(false, None), storage_buffer_sized(false, None),
storage_buffer_sized(false, None), storage_buffer_sized(false, None),
storage_buffer_sized(false, None), storage_buffer_sized(false, None),
@ -624,6 +625,7 @@ pub fn prepare_meshlet_view_bind_groups(
let entries = BindGroupEntries::sequential(( let entries = BindGroupEntries::sequential((
cluster_meshlet_ids.as_entire_binding(), cluster_meshlet_ids.as_entire_binding(),
meshlet_mesh_manager.meshlet_bounding_spheres.binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(),
meshlet_mesh_manager.meshlet_simplification_errors.binding(),
cluster_instance_ids.as_entire_binding(), cluster_instance_ids.as_entire_binding(),
instance_manager.instance_uniforms.binding().unwrap(), instance_manager.instance_uniforms.binding().unwrap(),
view_resources.instance_visibility.as_entire_binding(), view_resources.instance_visibility.as_entire_binding(),
@ -652,6 +654,7 @@ pub fn prepare_meshlet_view_bind_groups(
let entries = BindGroupEntries::sequential(( let entries = BindGroupEntries::sequential((
cluster_meshlet_ids.as_entire_binding(), cluster_meshlet_ids.as_entire_binding(),
meshlet_mesh_manager.meshlet_bounding_spheres.binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(),
meshlet_mesh_manager.meshlet_simplification_errors.binding(),
cluster_instance_ids.as_entire_binding(), cluster_instance_ids.as_entire_binding(),
instance_manager.instance_uniforms.binding().unwrap(), instance_manager.instance_uniforms.binding().unwrap(),
view_resources.instance_visibility.as_entire_binding(), view_resources.instance_visibility.as_entire_binding(),

View file

@ -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/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh"; "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/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() {