mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
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:
parent
9930df83ed
commit
9d54fe0370
11 changed files with 233 additions and 137 deletions
|
@ -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",
|
||||
],
|
||||
]
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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<f32>, 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<f32>, 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
|
||||
|
|
|
@ -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::<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
|
||||
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<u32>, f32)> {
|
||||
) -> 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();
|
||||
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)))
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -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,
|
||||
|
|
|
@ -25,9 +25,9 @@ fn get_meshlet_triangle_count(meshlet: ptr<function, Meshlet>) -> 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<push_constant> cluster_count: 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(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(3) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
||||
@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(5) var<storage, read_write> meshlet_second_pass_candidates: array<atomic<u32>>; // 1 bit per cluster , 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(7) var<storage, read_write> meshlet_hardware_raster_indirect_args: DrawIndirectArgs; // 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(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(10) var<uniform> view: View;
|
||||
@group(0) @binding(11) var<uniform> previous_view: PreviousViewUniforms;
|
||||
@group(0) @binding(2) var<storage, read> meshlet_simplification_errors: array<u32>; // Per meshlet
|
||||
@group(0) @binding(3) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
||||
@group(0) @binding(4) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
||||
@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_second_pass_candidates: array<atomic<u32>>; // 1 bit per cluster , packed as a bitmask
|
||||
@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_hardware_raster_indirect_args: DrawIndirectArgs; // Single object shared between all workgroups/clusters/triangles
|
||||
@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 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> view: View;
|
||||
@group(0) @binding(12) var<uniform> previous_view: PreviousViewUniforms;
|
||||
|
||||
fn should_cull_instance(instance_id: u32) -> bool {
|
||||
let bit_offset = instance_id % 32u;
|
||||
|
|
|
@ -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<Arc<[u8]>>,
|
||||
pub meshlets: PersistentGpuBuffer<Arc<[Meshlet]>>,
|
||||
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 {
|
||||
|
@ -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<MeshletMesh>) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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::<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]> {
|
||||
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]> {
|
||||
type Metadata = ();
|
||||
|
||||
|
@ -84,3 +84,15 @@ impl PersistentGpuBufferable for Arc<[MeshletBoundingSpheres]> {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue