diff --git a/Cargo.toml b/Cargo.toml index b12b688e589c1..97c0d265da3ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1213,7 +1213,7 @@ setup = [ "curl", "-o", "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", ], ] diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 72b2b53938bd9..67552945fc87e 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -18,7 +18,7 @@ shader_format_glsl = ["bevy_render/shader_format_glsl"] trace = ["bevy_render/trace"] ios_simulator = ["bevy_render/ios_simulator"] # 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 meshlet_processor = [ "meshlet", @@ -50,16 +50,17 @@ bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } # other bitflags = "2.3" fixedbitset = "0.5" -# meshlet -lz4_flex = { version = "0.11", default-features = false, features = [ - "frame", -], optional = true } derive_more = { version = "1", default-features = false, features = [ "error", "from", "display", ] } +# meshlet +lz4_flex = { version = "0.11", default-features = false, features = [ + "frame", +], optional = true } range-alloc = { version = "0.1.3", optional = true } +half = { version = "2", features = ["bytemuck"], optional = true } meshopt = { version = "0.3.0", optional = true } metis = { version = "0.2", optional = true } itertools = { version = "0.13", optional = true } diff --git a/crates/bevy_pbr/src/meshlet/asset.rs b/crates/bevy_pbr/src/meshlet/asset.rs index 62e4a2d29e9a4..2589730338ee6 100644 --- a/crates/bevy_pbr/src/meshlet/asset.rs +++ b/crates/bevy_pbr/src/meshlet/asset.rs @@ -9,6 +9,7 @@ use bevy_reflect::TypePath; use bevy_tasks::block_on; use bytemuck::{Pod, Zeroable}; use derive_more::derive::{Display, Error, From}; +use half::f16; use lz4_flex::frame::{FrameDecoder, FrameEncoder}; use std::io::{Read, Write}; @@ -51,6 +52,8 @@ pub struct MeshletMesh { pub(crate) meshlets: Arc<[Meshlet]>, /// Spherical bounding volumes. 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`]. @@ -90,12 +93,12 @@ pub struct Meshlet { #[derive(Copy, Clone, Pod, Zeroable)] #[repr(C)] pub struct MeshletBoundingSpheres { - /// The bounding sphere used for frustum and occlusion culling for this meshlet. - pub self_culling: MeshletBoundingSphere, - /// The bounding sphere used for determining if this meshlet is at the correct level of detail for a given view. - pub self_lod: MeshletBoundingSphere, - /// The bounding sphere used for determining if this meshlet's parent is at the correct level of detail for a given view. - pub parent_lod: MeshletBoundingSphere, + /// Bounding sphere used for frustum and occlusion culling for this meshlet. + pub culling_sphere: MeshletBoundingSphere, + /// Bounding sphere used for determining if this meshlet's group is at the correct level of detail for a given view. + pub lod_group_sphere: MeshletBoundingSphere, + /// Bounding sphere used for determining if this meshlet's parent group is at the correct level of detail for a given view. + pub lod_parent_group_sphere: MeshletBoundingSphere, } /// A spherical bounding volume used for a [`Meshlet`]. @@ -106,6 +109,16 @@ pub struct MeshletBoundingSphere { 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. pub struct MeshletMeshSaver; @@ -139,6 +152,7 @@ impl AssetSaver for MeshletMeshSaver { write_slice(&asset.indices, &mut writer)?; write_slice(&asset.meshlets, &mut writer)?; write_slice(&asset.meshlet_bounding_spheres, &mut writer)?; + write_slice(&asset.meshlet_simplification_errors, &mut writer)?; writer.finish()?; Ok(()) @@ -179,6 +193,7 @@ impl AssetLoader for MeshletMeshLoader { let indices = read_slice(reader)?; let meshlets = read_slice(reader)?; let meshlet_bounding_spheres = read_slice(reader)?; + let meshlet_simplification_errors = read_slice(reader)?; Ok(MeshletMesh { vertex_positions, @@ -187,6 +202,7 @@ impl AssetLoader for MeshletMeshLoader { indices, meshlets, meshlet_bounding_spheres, + meshlet_simplification_errors, }) } diff --git a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl index fe5df60f12082..442a57824e551 100644 --- a/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl +++ b/crates/bevy_pbr/src/meshlet/cull_clusters.wgsl @@ -1,6 +1,7 @@ #import bevy_pbr::meshlet_bindings::{ meshlet_cluster_meshlet_ids, meshlet_bounding_spheres, + meshlet_simplification_errors, meshlet_cluster_instance_ids, meshlet_instance_uniforms, meshlet_second_pass_candidates, @@ -13,6 +14,7 @@ meshlet_hardware_raster_indirect_args, meshlet_raster_clusters, meshlet_raster_cluster_rightmost_slot, + MeshletBoundingSphere, } #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_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 culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_culling.center, 1.0); - let culling_bounding_sphere_radius = world_scale * bounding_spheres.self_culling.radius; + 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.culling_sphere.radius; #ifdef MESHLET_FIRST_CULLING_PASS // Frustum culling @@ -60,19 +62,10 @@ fn cull_clusters( } } - // Calculate view-space LOD bounding sphere for the cluster - let lod_bounding_sphere_center = world_from_local * vec4(bounding_spheres.self_lod.center, 1.0); - let lod_bounding_sphere_radius = world_scale * bounding_spheres.self_lod.radius; - let lod_bounding_sphere_center_view_space = (view.view_from_world * vec4(lod_bounding_sphere_center.xyz, 1.0)).xyz; - - // 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); + // Check LOD cut (cluster group error imperceptible, and parent group error not imperceptible) + let simplification_errors = unpack2x16float(meshlet_simplification_errors[meshlet_id]); + let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale); + let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale); if !lod_is_ok || parent_lod_is_ok { return; } #endif @@ -80,8 +73,8 @@ fn cull_clusters( #ifdef MESHLET_FIRST_CULLING_PASS 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 occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.self_culling.center, 1.0); - let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.self_culling.radius; + 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.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; #else let occlusion_culling_bounding_sphere_center = culling_bounding_sphere_center; @@ -148,14 +141,23 @@ fn cull_clusters( meshlet_raster_clusters[buffer_slot] = cluster_id; } -// https://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space/21649403#21649403 -fn lod_error_is_imperceptible(cp: vec3, r: f32) -> bool { - let d2 = dot(cp, cp); - let r2 = r * r; - let sphere_diameter_uv = view.clip_from_view[0][0] * r / sqrt(d2 - r2); - let view_size = f32(max(view.viewport.z, view.viewport.w)); - let sphere_diameter_pixels = sphere_diameter_uv * view_size; - return sphere_diameter_pixels < 1.0; +// https://github.com/zeux/meshoptimizer/blob/1e48e96c7e8059321de492865165e9ef071bffba/demo/nanite.cpp#L115 +fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_error: f32, world_from_local: mat4x4, world_scale: f32) -> bool { + 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; + + 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_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 diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 64874b57e3c95..05db8adec4aef 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -1,4 +1,6 @@ -use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh}; +use super::asset::{ + Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh, MeshletSimplificationError, +}; use alloc::borrow::Cow; use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles}; use bevy_render::{ @@ -7,13 +9,12 @@ use bevy_render::{ }; use bevy_utils::HashMap; use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; -use core::ops::Range; +use core::{iter, ops::Range}; use derive_more::derive::{Display, Error}; +use half::f16; use itertools::Itertools; use meshopt::{ - build_meshlets, compute_cluster_bounds, compute_meshlet_bounds, - ffi::{meshopt_Bounds, meshopt_Meshlet}, - simplify, Meshlets, SimplifyOptions, VertexDataAdapter, + build_meshlets, ffi::meshopt_Meshlet, simplify, Meshlets, SimplifyOptions, VertexDataAdapter, }; use metis::Graph; use smallvec::SmallVec; @@ -69,19 +70,21 @@ impl MeshletMesh { let mut bounding_spheres = meshlets .iter() .map(|meshlet| compute_meshlet_bounds(meshlet, &vertices)) - .map(convert_meshlet_bounds) .map(|bounding_sphere| MeshletBoundingSpheres { - self_culling: bounding_sphere, - self_lod: MeshletBoundingSphere { - center: bounding_sphere.center, + culling_sphere: bounding_sphere, + lod_group_sphere: bounding_sphere, + lod_parent_group_sphere: MeshletBoundingSphere { + center: Vec3::ZERO, radius: 0.0, }, - parent_lod: MeshletBoundingSphere { - center: bounding_sphere.center, - radius: f32::MAX, - }, }) .collect::>(); + let mut simplification_errors = iter::repeat(MeshletSimplificationError { + group_error: f16::ZERO, + parent_group_error: f16::MAX, + }) + .take(meshlets.len()) + .collect::>(); // Build further LODs let mut simplification_queue = 0..meshlets.len(); @@ -107,22 +110,13 @@ impl MeshletMesh { continue; }; - // Force parent error to be >= child error (we're currently building the parent from its children) - group_error = group_meshlets.iter().fold(group_error, |acc, meshlet_id| { - acc.max(bounding_spheres[*meshlet_id].self_lod.radius) - }); - - // Build a new LOD bounding sphere for the simplified group as a whole - 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; - } + // Compute LOD data for the group + let group_bounding_sphere = compute_lod_group_data( + &group_meshlets, + &mut group_error, + &mut bounding_spheres, + &mut simplification_errors, + ); // Build new meshlets using the simplified group let new_meshlets_count = split_simplified_group_into_new_meshlets( @@ -131,22 +125,24 @@ impl MeshletMesh { &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(); - bounding_spheres.extend( - new_meshlet_ids - .map(|meshlet_id| { - compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices) - }) - .map(convert_meshlet_bounds) - .map(|bounding_sphere| MeshletBoundingSpheres { - self_culling: bounding_sphere, - self_lod: group_bounding_sphere, - parent_lod: MeshletBoundingSphere { - center: group_bounding_sphere.center, - radius: f32::MAX, - }, - }), + bounding_spheres.extend(new_meshlet_ids.clone().map(|meshlet_id| { + MeshletBoundingSpheres { + culling_sphere: compute_meshlet_bounds(meshlets.get(meshlet_id), &vertices), + lod_group_sphere: group_bounding_sphere, + lod_parent_group_sphere: MeshletBoundingSphere { + center: Vec3::ZERO, + radius: 0.0, + }, + } + })); + simplification_errors.extend( + 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(), meshlets: bevy_meshlets.into(), meshlet_bounding_spheres: bounding_spheres.into(), + meshlet_simplification_errors: simplification_errors.into(), }) } } @@ -303,7 +300,7 @@ fn simplify_meshlet_group( group_meshlets: &[usize], meshlets: &Meshlets, vertices: &VertexDataAdapter<'_>, -) -> Option<(Vec, f32)> { +) -> 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(); for meshlet_id in group_meshlets { @@ -330,10 +327,55 @@ fn simplify_meshlet_group( return None; } - // Convert error from diameter to radius - error *= 0.5; + Some((simplified_group_indices, f16::from_f32(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; + } - Some((simplified_group_indices, error)) + group_bounding_sphere } 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 { center: bounds.center.into(), radius: bounds.radius, diff --git a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl index f870758353fdf..42b322d731a80 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl @@ -25,9 +25,9 @@ fn get_meshlet_triangle_count(meshlet: ptr) -> u32 { } struct MeshletBoundingSpheres { - self_culling: MeshletBoundingSphere, - self_lod: MeshletBoundingSphere, - parent_lod: MeshletBoundingSphere, + culling_sphere: MeshletBoundingSphere, + lod_group_sphere: MeshletBoundingSphere, + lod_parent_group_sphere: MeshletBoundingSphere, } struct MeshletBoundingSphere { @@ -62,16 +62,17 @@ var cluster_count: u32; var meshlet_raster_cluster_rightmost_slot: u32; @group(0) @binding(0) var meshlet_cluster_meshlet_ids: array; // Per cluster @group(0) @binding(1) var meshlet_bounding_spheres: array; // Per meshlet -@group(0) @binding(2) var meshlet_cluster_instance_ids: array; // Per cluster -@group(0) @binding(3) var meshlet_instance_uniforms: array; // Per entity instance -@group(0) @binding(4) var meshlet_view_instance_visibility: array; // 1 bit per entity instance, packed as a bitmask -@group(0) @binding(5) var meshlet_second_pass_candidates: array>; // 1 bit per cluster , packed as a bitmask -@group(0) @binding(6) var meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles -@group(0) @binding(7) var meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles -@group(0) @binding(8) var meshlet_raster_clusters: array; // Single object shared between all workgroups/clusters/triangles -@group(0) @binding(9) var depth_pyramid: texture_2d; // 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(10) var view: View; -@group(0) @binding(11) var previous_view: PreviousViewUniforms; +@group(0) @binding(2) var meshlet_simplification_errors: array; // Per meshlet +@group(0) @binding(3) var meshlet_cluster_instance_ids: array; // Per cluster +@group(0) @binding(4) var meshlet_instance_uniforms: array; // Per entity instance +@group(0) @binding(5) var meshlet_view_instance_visibility: array; // 1 bit per entity instance, packed as a bitmask +@group(0) @binding(6) var meshlet_second_pass_candidates: array>; // 1 bit per cluster , packed as a bitmask +@group(0) @binding(7) var meshlet_software_raster_indirect_args: DispatchIndirectArgs; // Single object shared between all workgroups/clusters/triangles +@group(0) @binding(8) var meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles +@group(0) @binding(9) var meshlet_raster_clusters: array; // Single object shared between all workgroups/clusters/triangles +@group(0) @binding(10) var depth_pyramid: texture_2d; // 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 view: View; +@group(0) @binding(12) var previous_view: PreviousViewUniforms; fn should_cull_instance(instance_id: u32) -> bool { let bit_offset = instance_id % 32u; diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs index 831fe11fb353b..4170ac6279446 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs @@ -1,5 +1,5 @@ use super::{ - asset::{Meshlet, MeshletBoundingSpheres}, + asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError}, persistent_buffer::PersistentGpuBuffer, MeshletMesh, }; @@ -26,7 +26,8 @@ pub struct MeshletMeshManager { pub indices: PersistentGpuBuffer>, pub meshlets: PersistentGpuBuffer>, pub meshlet_bounding_spheres: PersistentGpuBuffer>, - meshlet_mesh_slices: HashMap, [Range; 6]>, + pub meshlet_simplification_errors: PersistentGpuBuffer>, + meshlet_mesh_slices: HashMap, [Range; 7]>, } impl FromWorld for MeshletMeshManager { @@ -42,6 +43,10 @@ impl FromWorld for MeshletMeshManager { "meshlet_bounding_spheres", render_device, ), + meshlet_simplification_errors: PersistentGpuBuffer::new( + "meshlet_simplification_errors", + render_device, + ), meshlet_mesh_slices: HashMap::new(), } } @@ -81,6 +86,9 @@ impl MeshletMeshManager { let meshlet_bounding_spheres_slice = self .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, @@ -89,11 +97,12 @@ impl MeshletMeshManager { indices_slice, meshlets_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 - let [_, _, _, _, meshlets_slice, _] = self + let [_, _, _, _, meshlets_slice, _, _] = self .meshlet_mesh_slices .entry(asset_id) .or_insert_with_key(queue_meshlet_mesh) @@ -106,7 +115,7 @@ impl MeshletMeshManager { pub fn remove(&mut self, asset_id: &AssetId) { 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.vertex_positions @@ -117,6 +126,8 @@ impl MeshletMeshManager { self.meshlets.mark_slice_unused(meshlets_slice); self.meshlet_bounding_spheres .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_bounding_spheres .perform_writes(&render_queue, &render_device); + meshlet_mesh_manager + .meshlet_simplification_errors + .perform_writes(&render_queue, &render_device); } diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 80d5f7aa59bfc..1deb4ae85c762 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -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)] #[reflect(Component, Default)] #[require(Transform, Visibility)] diff --git a/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs b/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs index 9fa7058772a90..9c2667d3f3d4d 100644 --- a/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs +++ b/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs @@ -1,10 +1,42 @@ use super::{ - asset::{Meshlet, MeshletBoundingSpheres}, + asset::{Meshlet, MeshletBoundingSpheres, MeshletSimplificationError}, persistent_buffer::PersistentGpuBufferable, }; use alloc::sync::Arc; use bevy_math::Vec2; +impl PersistentGpuBufferable for Arc<[Meshlet]> { + type Metadata = (u64, u64, u64); + + fn size_in_bytes(&self) -> usize { + self.len() * size_of::() + } + + 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::()) as u32; + let index_offset = index_offset as u32; + + for (i, meshlet) in self.iter().enumerate() { + let size = size_of::(); + let i = i * size; + let bytes = bytemuck::cast::<_, [u8; size_of::()]>(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]> { type Metadata = (); @@ -41,43 +73,23 @@ impl PersistentGpuBufferable for Arc<[Vec2]> { } } -impl PersistentGpuBufferable for Arc<[Meshlet]> { - type Metadata = (u64, u64, u64); +impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> { + type Metadata = (); fn size_in_bytes(&self) -> usize { - self.len() * size_of::() + self.len() * size_of::() } - 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::()) as u32; - let index_offset = index_offset as u32; - - for (i, meshlet) in self.iter().enumerate() { - let size = size_of::(); - let i = i * size; - let bytes = bytemuck::cast::<_, [u8; size_of::()]>(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); - } + fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) { + buffer_slice.clone_from_slice(bytemuck::cast_slice(self)); } } -impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> { +impl PersistentGpuBufferable for Arc<[MeshletSimplificationError]> { type Metadata = (); fn size_in_bytes(&self) -> usize { - self.len() * size_of::() + self.len() * size_of::() } fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) { diff --git a/crates/bevy_pbr/src/meshlet/resource_manager.rs b/crates/bevy_pbr/src/meshlet/resource_manager.rs index fd95f45b3f26e..edfc7ba5f3da6 100644 --- a/crates/bevy_pbr/src/meshlet/resource_manager.rs +++ b/crates/bevy_pbr/src/meshlet/resource_manager.rs @@ -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_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(( cluster_meshlet_ids.as_entire_binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(), + meshlet_mesh_manager.meshlet_simplification_errors.binding(), cluster_instance_ids.as_entire_binding(), instance_manager.instance_uniforms.binding().unwrap(), view_resources.instance_visibility.as_entire_binding(), @@ -652,6 +654,7 @@ pub fn prepare_meshlet_view_bind_groups( let entries = BindGroupEntries::sequential(( cluster_meshlet_ids.as_entire_binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(), + meshlet_mesh_manager.meshlet_simplification_errors.binding(), cluster_instance_ids.as_entire_binding(), instance_manager.instance_uniforms.binding().unwrap(), view_resources.instance_visibility.as_entire_binding(), diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index 72da8c968b4a5..7f425b40eb8ae 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/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh"; + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/167cdaf0b08f89fb747b83b94c27755f116cd408/bunny.meshlet_mesh"; fn main() -> ExitCode { if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {