Per-meshlet compressed vertex data (#15643)
# Objective - Prepare for streaming by storing vertex data per-meshlet, rather than per-mesh (this means duplicating vertices per-meshlet) - Compress vertex data to reduce the cost of this ## Solution The important parts are in from_mesh.rs, the changes to the Meshlet type in asset.rs, and the changes in meshlet_bindings.wgsl. Everything else is pretty secondary/boilerplate/straightforward changes. - Positions are quantized in centimeters with a user-provided power of 2 factor (ideally auto-determined, but that's a TODO for the future), encoded as an offset relative to the minimum value within the meshlet, and then stored as a packed list of bits using the minimum number of bits needed for each vertex position channel for that meshlet - E.g. quantize positions (lossly, throws away precision that's not needed leading to using less bits in the bitstream encoding) - Get the min/max quantized value of each X/Y/Z channel of the quantized positions within a meshlet - Encode values relative to the min value of the meshlet. E.g. convert from [min, max] to [0, max - min] - The new max value in the meshlet is (max - min), which only takes N bits, so we only need N bits to store each channel within the meshlet (lossless) - We can store the min value and that it takes N bits per channel in the meshlet metadata, and reconstruct the position from the bitstream - Normals are octahedral encoded and than snorm2x16 packed and stored as a single u32. - Would be better to implement the precise variant of octhedral encoding for extra precision (no extra decode cost), but decided to keep it simple for now and leave that as a followup - Tried doing a quantizing and bitstream encoding scheme like I did for positions, but struggled to get it smaller. Decided to go with this for simplicity for now - UVs are uncompressed and take a full 64bits per vertex which is expensive - In the future this should be improved - Tangents, as of the previous PR, are not explicitly stored and are instead derived from screen space gradients - While I'm here, split up MeshletMeshSaverLoader into two separate types Other future changes include implementing a smaller encoding of triangle data (3 u8 indices = 24 bits per triangle currently), and more disk-oriented compression schemes. References: * "A Deep Dive into UE5's Nanite Virtualized Geometry" https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf#page=128 (also available on youtube) * "Towards Practical Meshlet Compression" https://arxiv.org/pdf/2404.06359 * "Vertex quantization in Omniforce Game Engine" https://daniilvinn.github.io/2024/05/04/omniforce-vertex-quantization.html ## Testing - Did you test these changes? If so, how? - Converted the stanford bunny, and rendered it with a debug material showing normals, and confirmed that it's identical to what's on main. EDIT: See additional testing in the comments below. - Are there any parts that need more testing? - Could use some more size comparisons on various meshes, and testing different quantization factors. Not sure if 4 is a good default. EDIT: See additional testing in the comments below. - Also did not test runtime performance of the shaders. EDIT: See additional testing in the comments below. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Use my unholy script, replacing the meshlet example https://paste.rs/7xQHk.rs (must make MeshletMesh fields pub instead of pub crate, must add lz4_flex as a dev-dependency) (must compile with meshlet and meshlet_processor features, mesh must have only positions, normals, and UVs, no vertex colors or tangents) --- ## Migration Guide - TBD by JMS55 at the end of the release
This commit is contained in:
parent
f6cd6a4874
commit
aa626e4f0b
@ -1209,7 +1209,7 @@ setup = [
|
|||||||
"curl",
|
"curl",
|
||||||
"-o",
|
"-o",
|
||||||
"assets/models/bunny.meshlet_mesh",
|
"assets/models/bunny.meshlet_mesh",
|
||||||
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/854eb98353ad94aea1104f355fc24dbe4fda679d/bunny.meshlet_mesh",
|
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,13 @@ 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:thiserror", "dep:range-alloc", "dep:bevy_tasks"]
|
meshlet = ["dep:lz4_flex", "dep:thiserror", "dep:range-alloc", "dep:bevy_tasks"]
|
||||||
# Enables processing meshes into meshlet meshes
|
# Enables processing meshes into meshlet meshes
|
||||||
meshlet_processor = ["meshlet", "dep:meshopt", "dep:metis", "dep:itertools"]
|
meshlet_processor = [
|
||||||
|
"meshlet",
|
||||||
|
"dep:meshopt",
|
||||||
|
"dep:metis",
|
||||||
|
"dep:itertools",
|
||||||
|
"dep:bitvec",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
@ -53,6 +59,7 @@ range-alloc = { version = "0.1.3", 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 }
|
||||||
|
bitvec = { version = "1", optional = true }
|
||||||
# direct dependency required for derive macro
|
# direct dependency required for derive macro
|
||||||
bytemuck = { version = "1", features = ["derive", "must_cast"] }
|
bytemuck = { version = "1", features = ["derive", "must_cast"] }
|
||||||
radsort = "0.1"
|
radsort = "0.1"
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use bevy_asset::{
|
|||||||
saver::{AssetSaver, SavedAsset},
|
saver::{AssetSaver, SavedAsset},
|
||||||
Asset, AssetLoader, AsyncReadExt, AsyncWriteExt, LoadContext,
|
Asset, AssetLoader, AsyncReadExt, AsyncWriteExt, LoadContext,
|
||||||
};
|
};
|
||||||
use bevy_math::Vec3;
|
use bevy_math::{Vec2, Vec3};
|
||||||
use bevy_reflect::TypePath;
|
use bevy_reflect::TypePath;
|
||||||
use bevy_tasks::block_on;
|
use bevy_tasks::block_on;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
@ -38,30 +38,51 @@ pub const MESHLET_MESH_ASSET_VERSION: u64 = 1;
|
|||||||
/// See also [`super::MaterialMeshletMeshBundle`] and [`super::MeshletPlugin`].
|
/// See also [`super::MaterialMeshletMeshBundle`] and [`super::MeshletPlugin`].
|
||||||
#[derive(Asset, TypePath, Clone)]
|
#[derive(Asset, TypePath, Clone)]
|
||||||
pub struct MeshletMesh {
|
pub struct MeshletMesh {
|
||||||
/// Raw vertex data bytes for the overall mesh.
|
/// Quantized and bitstream-packed vertex positions for meshlet vertices.
|
||||||
pub(crate) vertex_data: Arc<[u8]>,
|
pub(crate) vertex_positions: Arc<[u32]>,
|
||||||
/// Indices into `vertex_data`.
|
/// Octahedral-encoded and 2x16snorm packed normals for meshlet vertices.
|
||||||
pub(crate) vertex_ids: Arc<[u32]>,
|
pub(crate) vertex_normals: Arc<[u32]>,
|
||||||
/// Indices into `vertex_ids`.
|
/// Uncompressed vertex texture coordinates for meshlet vertices.
|
||||||
|
pub(crate) vertex_uvs: Arc<[Vec2]>,
|
||||||
|
/// Triangle indices for meshlets.
|
||||||
pub(crate) indices: Arc<[u8]>,
|
pub(crate) indices: Arc<[u8]>,
|
||||||
/// The list of meshlets making up this mesh.
|
/// The list of meshlets making up this mesh.
|
||||||
pub(crate) meshlets: Arc<[Meshlet]>,
|
pub(crate) meshlets: Arc<[Meshlet]>,
|
||||||
/// Spherical bounding volumes.
|
/// Spherical bounding volumes.
|
||||||
pub(crate) bounding_spheres: Arc<[MeshletBoundingSpheres]>,
|
pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single meshlet within a [`MeshletMesh`].
|
/// A single meshlet within a [`MeshletMesh`].
|
||||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Meshlet {
|
pub struct Meshlet {
|
||||||
/// The offset within the parent mesh's [`MeshletMesh::vertex_ids`] buffer where the indices for this meshlet begin.
|
/// The bit offset within the parent mesh's [`MeshletMesh::vertex_positions`] buffer where the vertex positions for this meshlet begin.
|
||||||
pub start_vertex_id: u32,
|
pub start_vertex_position_bit: u32,
|
||||||
|
/// The offset within the parent mesh's [`MeshletMesh::vertex_normals`] and [`MeshletMesh::vertex_uvs`] buffers
|
||||||
|
/// where non-position vertex attributes for this meshlet begin.
|
||||||
|
pub start_vertex_attribute_id: u32,
|
||||||
/// The offset within the parent mesh's [`MeshletMesh::indices`] buffer where the indices for this meshlet begin.
|
/// The offset within the parent mesh's [`MeshletMesh::indices`] buffer where the indices for this meshlet begin.
|
||||||
pub start_index_id: u32,
|
pub start_index_id: u32,
|
||||||
/// The amount of vertices in this meshlet.
|
/// The amount of vertices in this meshlet.
|
||||||
pub vertex_count: u32,
|
pub vertex_count: u8,
|
||||||
/// The amount of triangles in this meshlet.
|
/// The amount of triangles in this meshlet.
|
||||||
pub triangle_count: u32,
|
pub triangle_count: u8,
|
||||||
|
/// Unused.
|
||||||
|
pub padding: u16,
|
||||||
|
/// Number of bits used to to store the X channel of vertex positions within this meshlet.
|
||||||
|
pub bits_per_vertex_position_channel_x: u8,
|
||||||
|
/// Number of bits used to to store the Y channel of vertex positions within this meshlet.
|
||||||
|
pub bits_per_vertex_position_channel_y: u8,
|
||||||
|
/// Number of bits used to to store the Z channel of vertex positions within this meshlet.
|
||||||
|
pub bits_per_vertex_position_channel_z: u8,
|
||||||
|
/// Power of 2 factor used to quantize vertex positions within this meshlet.
|
||||||
|
pub vertex_position_quantization_factor: u8,
|
||||||
|
/// Minimum quantized X channel value of vertex positions within this meshlet.
|
||||||
|
pub min_vertex_position_channel_x: f32,
|
||||||
|
/// Minimum quantized Y channel value of vertex positions within this meshlet.
|
||||||
|
pub min_vertex_position_channel_y: f32,
|
||||||
|
/// Minimum quantized Z channel value of vertex positions within this meshlet.
|
||||||
|
pub min_vertex_position_channel_z: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bounding spheres used for culling and choosing level of detail for a [`Meshlet`].
|
/// Bounding spheres used for culling and choosing level of detail for a [`Meshlet`].
|
||||||
@ -84,13 +105,13 @@ pub struct MeshletBoundingSphere {
|
|||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`AssetLoader`] and [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
|
/// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
|
||||||
pub struct MeshletMeshSaverLoader;
|
pub struct MeshletMeshSaver;
|
||||||
|
|
||||||
impl AssetSaver for MeshletMeshSaverLoader {
|
impl AssetSaver for MeshletMeshSaver {
|
||||||
type Asset = MeshletMesh;
|
type Asset = MeshletMesh;
|
||||||
type Settings = ();
|
type Settings = ();
|
||||||
type OutputLoader = Self;
|
type OutputLoader = MeshletMeshLoader;
|
||||||
type Error = MeshletMeshSaveOrLoadError;
|
type Error = MeshletMeshSaveOrLoadError;
|
||||||
|
|
||||||
async fn save(
|
async fn save(
|
||||||
@ -111,18 +132,22 @@ impl AssetSaver for MeshletMeshSaverLoader {
|
|||||||
|
|
||||||
// Compress and write asset data
|
// Compress and write asset data
|
||||||
let mut writer = FrameEncoder::new(AsyncWriteSyncAdapter(writer));
|
let mut writer = FrameEncoder::new(AsyncWriteSyncAdapter(writer));
|
||||||
write_slice(&asset.vertex_data, &mut writer)?;
|
write_slice(&asset.vertex_positions, &mut writer)?;
|
||||||
write_slice(&asset.vertex_ids, &mut writer)?;
|
write_slice(&asset.vertex_normals, &mut writer)?;
|
||||||
|
write_slice(&asset.vertex_uvs, &mut writer)?;
|
||||||
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.bounding_spheres, &mut writer)?;
|
write_slice(&asset.meshlet_bounding_spheres, &mut writer)?;
|
||||||
writer.finish()?;
|
writer.finish()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetLoader for MeshletMeshSaverLoader {
|
/// An [`AssetLoader`] for `.meshlet_mesh` [`MeshletMesh`] assets.
|
||||||
|
pub struct MeshletMeshLoader;
|
||||||
|
|
||||||
|
impl AssetLoader for MeshletMeshLoader {
|
||||||
type Asset = MeshletMesh;
|
type Asset = MeshletMesh;
|
||||||
type Settings = ();
|
type Settings = ();
|
||||||
type Error = MeshletMeshSaveOrLoadError;
|
type Error = MeshletMeshSaveOrLoadError;
|
||||||
@ -147,18 +172,20 @@ impl AssetLoader for MeshletMeshSaverLoader {
|
|||||||
|
|
||||||
// Load and decompress asset data
|
// Load and decompress asset data
|
||||||
let reader = &mut FrameDecoder::new(AsyncReadSyncAdapter(reader));
|
let reader = &mut FrameDecoder::new(AsyncReadSyncAdapter(reader));
|
||||||
let vertex_data = read_slice(reader)?;
|
let vertex_positions = read_slice(reader)?;
|
||||||
let vertex_ids = read_slice(reader)?;
|
let vertex_normals = read_slice(reader)?;
|
||||||
|
let vertex_uvs = read_slice(reader)?;
|
||||||
let indices = read_slice(reader)?;
|
let indices = read_slice(reader)?;
|
||||||
let meshlets = read_slice(reader)?;
|
let meshlets = read_slice(reader)?;
|
||||||
let bounding_spheres = read_slice(reader)?;
|
let meshlet_bounding_spheres = read_slice(reader)?;
|
||||||
|
|
||||||
Ok(MeshletMesh {
|
Ok(MeshletMesh {
|
||||||
vertex_data,
|
vertex_positions,
|
||||||
vertex_ids,
|
vertex_normals,
|
||||||
|
vertex_uvs,
|
||||||
indices,
|
indices,
|
||||||
meshlets,
|
meshlets,
|
||||||
bounding_spheres,
|
meshlet_bounding_spheres,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,31 +1,62 @@
|
|||||||
use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh};
|
use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh};
|
||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
|
use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
mesh::{Indices, Mesh},
|
mesh::{Indices, Mesh},
|
||||||
render_resource::PrimitiveTopology,
|
render_resource::PrimitiveTopology,
|
||||||
};
|
};
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use meshopt::{
|
use meshopt::{
|
||||||
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds, ffi::meshopt_Bounds, simplify,
|
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds,
|
||||||
Meshlets, SimplifyOptions, VertexDataAdapter,
|
ffi::{meshopt_Bounds, meshopt_Meshlet},
|
||||||
|
simplify, Meshlets, SimplifyOptions, VertexDataAdapter,
|
||||||
};
|
};
|
||||||
use metis::Graph;
|
use metis::Graph;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
/// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`].
|
||||||
|
///
|
||||||
|
/// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4).
|
||||||
|
pub const DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4;
|
||||||
|
|
||||||
|
const MESHLET_VERTEX_SIZE_IN_BYTES: usize = 32;
|
||||||
|
const CENTIMETERS_PER_METER: f32 = 100.0;
|
||||||
|
|
||||||
impl MeshletMesh {
|
impl MeshletMesh {
|
||||||
/// Process a [`Mesh`] to generate a [`MeshletMesh`].
|
/// Process a [`Mesh`] to generate a [`MeshletMesh`].
|
||||||
///
|
///
|
||||||
/// This process is very slow, and should be done ahead of time, and not at runtime.
|
/// This process is very slow, and should be done ahead of time, and not at runtime.
|
||||||
///
|
///
|
||||||
|
/// # Requirements
|
||||||
|
///
|
||||||
/// This function requires the `meshlet_processor` cargo feature.
|
/// This function requires the `meshlet_processor` cargo feature.
|
||||||
///
|
///
|
||||||
/// The input mesh must:
|
/// The input mesh must:
|
||||||
/// 1. Use [`PrimitiveTopology::TriangleList`]
|
/// 1. Use [`PrimitiveTopology::TriangleList`]
|
||||||
/// 2. Use indices
|
/// 2. Use indices
|
||||||
/// 3. Have the exact following set of vertex attributes: `{POSITION, NORMAL, UV_0}` (tangents can be used in material shaders, but are calculated at runtime and are not stored in the mesh)
|
/// 3. Have the exact following set of vertex attributes: `{POSITION, NORMAL, UV_0}` (tangents can be used in material shaders, but are calculated at runtime and are not stored in the mesh)
|
||||||
pub fn from_mesh(mesh: &Mesh) -> Result<Self, MeshToMeshletMeshConversionError> {
|
///
|
||||||
|
/// # Vertex precision
|
||||||
|
///
|
||||||
|
/// `vertex_position_quantization_factor` is the amount of precision to to use when quantizing vertex positions.
|
||||||
|
///
|
||||||
|
/// Vertices are snapped to the nearest (1/2^x)th of a centimeter, where x = `vertex_position_quantization_factor`.
|
||||||
|
/// E.g. if x = 4, then vertices are snapped to the nearest 1/2^4 = 1/16th of a centimeter.
|
||||||
|
///
|
||||||
|
/// Use [`DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR`] as a default, adjusting lower to save memory and disk space, and higher to prevent artifacts if needed.
|
||||||
|
///
|
||||||
|
/// To ensure that two different meshes do not have cracks between them when placed directly next to each other:
|
||||||
|
/// * Use the same quantization factor when converting each mesh to a meshlet mesh
|
||||||
|
/// * Ensure that their [`bevy_transform::components::Transform::translation`]s are a multiple of 1/2^x centimeters (note that translations are in meters)
|
||||||
|
/// * Ensure that their [`bevy_transform::components::Transform::scale`]s are the same
|
||||||
|
/// * Ensure that their [`bevy_transform::components::Transform::rotation`]s are a multiple of 90 degrees
|
||||||
|
pub fn from_mesh(
|
||||||
|
mesh: &Mesh,
|
||||||
|
vertex_position_quantization_factor: u8,
|
||||||
|
) -> Result<Self, MeshToMeshletMeshConversionError> {
|
||||||
// Validate mesh format
|
// Validate mesh format
|
||||||
let indices = validate_input_mesh(mesh)?;
|
let indices = validate_input_mesh(mesh)?;
|
||||||
|
|
||||||
@ -121,24 +152,32 @@ impl MeshletMesh {
|
|||||||
simplification_queue = next_lod_start..meshlets.len();
|
simplification_queue = next_lod_start..meshlets.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert meshopt_Meshlet data to a custom format
|
// Copy vertex attributes per meshlet and compress
|
||||||
let bevy_meshlets = meshlets
|
let mut vertex_positions = BitVec::<u32, Lsb0>::new();
|
||||||
.meshlets
|
let mut vertex_normals = Vec::new();
|
||||||
.into_iter()
|
let mut vertex_uvs = Vec::new();
|
||||||
.map(|m| Meshlet {
|
let mut bevy_meshlets = Vec::with_capacity(meshlets.len());
|
||||||
start_vertex_id: m.vertex_offset,
|
for (i, meshlet) in meshlets.meshlets.iter().enumerate() {
|
||||||
start_index_id: m.triangle_offset,
|
build_and_compress_meshlet_vertex_data(
|
||||||
vertex_count: m.vertex_count,
|
meshlet,
|
||||||
triangle_count: m.triangle_count,
|
meshlets.get(i).vertices,
|
||||||
})
|
&vertex_buffer,
|
||||||
.collect();
|
&mut vertex_positions,
|
||||||
|
&mut vertex_normals,
|
||||||
|
&mut vertex_uvs,
|
||||||
|
&mut bevy_meshlets,
|
||||||
|
vertex_position_quantization_factor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
vertex_positions.set_uninitialized(false);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
vertex_data: vertex_buffer.into(),
|
vertex_positions: vertex_positions.into_vec().into(),
|
||||||
vertex_ids: meshlets.vertices.into(),
|
vertex_normals: vertex_normals.into(),
|
||||||
|
vertex_uvs: vertex_uvs.into(),
|
||||||
indices: meshlets.triangles.into(),
|
indices: meshlets.triangles.into(),
|
||||||
meshlets: bevy_meshlets,
|
meshlets: bevy_meshlets.into(),
|
||||||
bounding_spheres: bounding_spheres.into(),
|
meshlet_bounding_spheres: bounding_spheres.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,6 +362,92 @@ fn split_simplified_group_into_new_meshlets(
|
|||||||
new_meshlets_count
|
new_meshlets_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn build_and_compress_meshlet_vertex_data(
|
||||||
|
meshlet: &meshopt_Meshlet,
|
||||||
|
meshlet_vertex_ids: &[u32],
|
||||||
|
vertex_buffer: &[u8],
|
||||||
|
vertex_positions: &mut BitVec<u32, Lsb0>,
|
||||||
|
vertex_normals: &mut Vec<u32>,
|
||||||
|
vertex_uvs: &mut Vec<Vec2>,
|
||||||
|
meshlets: &mut Vec<Meshlet>,
|
||||||
|
vertex_position_quantization_factor: u8,
|
||||||
|
) {
|
||||||
|
let start_vertex_position_bit = vertex_positions.len() as u32;
|
||||||
|
let start_vertex_attribute_id = vertex_normals.len() as u32;
|
||||||
|
|
||||||
|
let quantization_factor =
|
||||||
|
(1 << vertex_position_quantization_factor) as f32 * CENTIMETERS_PER_METER;
|
||||||
|
|
||||||
|
let mut min_quantized_position_channels = IVec3::MAX;
|
||||||
|
let mut max_quantized_position_channels = IVec3::MIN;
|
||||||
|
|
||||||
|
// Lossy vertex compression
|
||||||
|
let mut quantized_positions = [IVec3::ZERO; 255];
|
||||||
|
for (i, vertex_id) in meshlet_vertex_ids.iter().enumerate() {
|
||||||
|
// Load source vertex attributes
|
||||||
|
let vertex_id_byte = *vertex_id as usize * MESHLET_VERTEX_SIZE_IN_BYTES;
|
||||||
|
let vertex_data =
|
||||||
|
&vertex_buffer[vertex_id_byte..(vertex_id_byte + MESHLET_VERTEX_SIZE_IN_BYTES)];
|
||||||
|
let position = Vec3::from_slice(bytemuck::cast_slice(&vertex_data[0..12]));
|
||||||
|
let normal = Vec3::from_slice(bytemuck::cast_slice(&vertex_data[12..24]));
|
||||||
|
let uv = Vec2::from_slice(bytemuck::cast_slice(&vertex_data[24..32]));
|
||||||
|
|
||||||
|
// Copy uncompressed UV
|
||||||
|
vertex_uvs.push(uv);
|
||||||
|
|
||||||
|
// Compress normal
|
||||||
|
vertex_normals.push(pack2x16snorm(octahedral_encode(normal)));
|
||||||
|
|
||||||
|
// Quantize position to a fixed-point IVec3
|
||||||
|
let quantized_position = (position * quantization_factor + 0.5).as_ivec3();
|
||||||
|
quantized_positions[i] = quantized_position;
|
||||||
|
|
||||||
|
// Compute per X/Y/Z-channel quantized position min/max for this meshlet
|
||||||
|
min_quantized_position_channels = min_quantized_position_channels.min(quantized_position);
|
||||||
|
max_quantized_position_channels = max_quantized_position_channels.max(quantized_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate bits needed to encode each quantized vertex position channel based on the range of each channel
|
||||||
|
let range = max_quantized_position_channels - min_quantized_position_channels + 1;
|
||||||
|
let bits_per_vertex_position_channel_x = log2(range.x as f32).ceil() as u8;
|
||||||
|
let bits_per_vertex_position_channel_y = log2(range.y as f32).ceil() as u8;
|
||||||
|
let bits_per_vertex_position_channel_z = log2(range.z as f32).ceil() as u8;
|
||||||
|
|
||||||
|
// Lossless encoding of vertex positions in the minimum number of bits per channel
|
||||||
|
for quantized_position in quantized_positions.iter().take(meshlet_vertex_ids.len()) {
|
||||||
|
// Remap [range_min, range_max] IVec3 to [0, range_max - range_min] UVec3
|
||||||
|
let position = (quantized_position - min_quantized_position_channels).as_uvec3();
|
||||||
|
|
||||||
|
// Store as a packed bitstream
|
||||||
|
vertex_positions.extend_from_bitslice(
|
||||||
|
&position.x.view_bits::<Lsb0>()[..bits_per_vertex_position_channel_x as usize],
|
||||||
|
);
|
||||||
|
vertex_positions.extend_from_bitslice(
|
||||||
|
&position.y.view_bits::<Lsb0>()[..bits_per_vertex_position_channel_y as usize],
|
||||||
|
);
|
||||||
|
vertex_positions.extend_from_bitslice(
|
||||||
|
&position.z.view_bits::<Lsb0>()[..bits_per_vertex_position_channel_z as usize],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
meshlets.push(Meshlet {
|
||||||
|
start_vertex_position_bit,
|
||||||
|
start_vertex_attribute_id,
|
||||||
|
start_index_id: meshlet.triangle_offset,
|
||||||
|
vertex_count: meshlet.vertex_count as u8,
|
||||||
|
triangle_count: meshlet.triangle_count as u8,
|
||||||
|
padding: 0,
|
||||||
|
bits_per_vertex_position_channel_x,
|
||||||
|
bits_per_vertex_position_channel_y,
|
||||||
|
bits_per_vertex_position_channel_z,
|
||||||
|
vertex_position_quantization_factor,
|
||||||
|
min_vertex_position_channel_x: min_quantized_position_channels.x as f32,
|
||||||
|
min_vertex_position_channel_y: min_quantized_position_channels.y as f32,
|
||||||
|
min_vertex_position_channel_z: min_quantized_position_channels.z as f32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere {
|
fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere {
|
||||||
MeshletBoundingSphere {
|
MeshletBoundingSphere {
|
||||||
center: bounds.center.into(),
|
center: bounds.center.into(),
|
||||||
@ -330,6 +455,28 @@ fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Precise encode variant
|
||||||
|
fn octahedral_encode(v: Vec3) -> Vec2 {
|
||||||
|
let n = v / (v.x.abs() + v.y.abs() + v.z.abs());
|
||||||
|
let octahedral_wrap = (1.0 - n.yx().abs())
|
||||||
|
* Vec2::new(
|
||||||
|
if n.x >= 0.0 { 1.0 } else { -1.0 },
|
||||||
|
if n.y >= 0.0 { 1.0 } else { -1.0 },
|
||||||
|
);
|
||||||
|
if n.z >= 0.0 {
|
||||||
|
n.xy()
|
||||||
|
} else {
|
||||||
|
octahedral_wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/WGSL/#pack2x16snorm-builtin
|
||||||
|
fn pack2x16snorm(v: Vec2) -> u32 {
|
||||||
|
let v = v.clamp(Vec2::NEG_ONE, Vec2::ONE);
|
||||||
|
let v = (v * 32767.0 + 0.5).floor().as_i16vec2();
|
||||||
|
bytemuck::cast(v)
|
||||||
|
}
|
||||||
|
|
||||||
/// An error produced by [`MeshletMesh::from_mesh`].
|
/// An error produced by [`MeshletMesh::from_mesh`].
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum MeshToMeshletMeshConversionError {
|
pub enum MeshToMeshletMeshConversionError {
|
||||||
|
|||||||
@ -3,31 +3,25 @@
|
|||||||
#import bevy_pbr::mesh_types::Mesh
|
#import bevy_pbr::mesh_types::Mesh
|
||||||
#import bevy_render::view::View
|
#import bevy_render::view::View
|
||||||
#import bevy_pbr::prepass_bindings::PreviousViewUniforms
|
#import bevy_pbr::prepass_bindings::PreviousViewUniforms
|
||||||
|
#import bevy_pbr::utils::octahedral_decode_signed
|
||||||
struct PackedMeshletVertex {
|
|
||||||
a: vec4<f32>,
|
|
||||||
b: vec4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MeshletVertex {
|
|
||||||
position: vec3<f32>,
|
|
||||||
normal: vec3<f32>,
|
|
||||||
uv: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unpack_meshlet_vertex(packed: PackedMeshletVertex) -> MeshletVertex {
|
|
||||||
var vertex: MeshletVertex;
|
|
||||||
vertex.position = packed.a.xyz;
|
|
||||||
vertex.normal = vec3(packed.a.w, packed.b.xy);
|
|
||||||
vertex.uv = packed.b.zw;
|
|
||||||
return vertex;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Meshlet {
|
struct Meshlet {
|
||||||
start_vertex_id: u32,
|
start_vertex_position_bit: u32,
|
||||||
|
start_vertex_attribute_id: u32,
|
||||||
start_index_id: u32,
|
start_index_id: u32,
|
||||||
vertex_count: u32,
|
packed_a: u32,
|
||||||
triangle_count: u32,
|
packed_b: u32,
|
||||||
|
min_vertex_position_channel_x: f32,
|
||||||
|
min_vertex_position_channel_y: f32,
|
||||||
|
min_vertex_position_channel_z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_meshlet_vertex_count(meshlet: ptr<function, Meshlet>) -> u32 {
|
||||||
|
return extractBits((*meshlet).packed_a, 0u, 8u);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_meshlet_triangle_count(meshlet: ptr<function, Meshlet>) -> u32 {
|
||||||
|
return extractBits((*meshlet).packed_a, 8u, 8u);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MeshletBoundingSpheres {
|
struct MeshletBoundingSpheres {
|
||||||
@ -54,6 +48,8 @@ struct DrawIndirectArgs {
|
|||||||
first_instance: u32,
|
first_instance: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CENTIMETERS_PER_METER = 100.0;
|
||||||
|
|
||||||
#ifdef MESHLET_FILL_CLUSTER_BUFFERS_PASS
|
#ifdef MESHLET_FILL_CLUSTER_BUFFERS_PASS
|
||||||
var<push_constant> cluster_count: u32;
|
var<push_constant> cluster_count: u32;
|
||||||
@group(0) @binding(0) var<storage, read> meshlet_instance_meshlet_counts_prefix_sum: array<u32>; // Per entity instance
|
@group(0) @binding(0) var<storage, read> meshlet_instance_meshlet_counts_prefix_sum: array<u32>; // Per entity instance
|
||||||
@ -95,25 +91,58 @@ fn cluster_is_second_pass_candidate(cluster_id: u32) -> bool {
|
|||||||
@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> meshlets: array<Meshlet>; // Per meshlet
|
@group(0) @binding(1) var<storage, read> meshlets: array<Meshlet>; // Per meshlet
|
||||||
@group(0) @binding(2) var<storage, read> meshlet_indices: array<u32>; // Many per meshlet
|
@group(0) @binding(2) var<storage, read> meshlet_indices: array<u32>; // Many per meshlet
|
||||||
@group(0) @binding(3) var<storage, read> meshlet_vertex_ids: array<u32>; // Many per meshlet
|
@group(0) @binding(3) var<storage, read> meshlet_vertex_positions: array<u32>; // Many per meshlet
|
||||||
@group(0) @binding(4) var<storage, read> meshlet_vertex_data: array<PackedMeshletVertex>; // Many per meshlet
|
@group(0) @binding(4) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
||||||
@group(0) @binding(5) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
@group(0) @binding(5) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
||||||
@group(0) @binding(6) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
@group(0) @binding(6) var<storage, read> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles
|
||||||
@group(0) @binding(7) var<storage, read> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles
|
@group(0) @binding(7) var<storage, read> meshlet_software_raster_cluster_count: u32;
|
||||||
@group(0) @binding(8) var<storage, read> meshlet_software_raster_cluster_count: u32;
|
|
||||||
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
|
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
|
||||||
@group(0) @binding(9) var<storage, read_write> meshlet_visibility_buffer: array<atomic<u64>>; // Per pixel
|
@group(0) @binding(8) var<storage, read_write> meshlet_visibility_buffer: array<atomic<u64>>; // Per pixel
|
||||||
#else
|
#else
|
||||||
@group(0) @binding(9) var<storage, read_write> meshlet_visibility_buffer: array<atomic<u32>>; // Per pixel
|
@group(0) @binding(8) var<storage, read_write> meshlet_visibility_buffer: array<atomic<u32>>; // Per pixel
|
||||||
#endif
|
#endif
|
||||||
@group(0) @binding(10) var<uniform> view: View;
|
@group(0) @binding(9) var<uniform> view: View;
|
||||||
|
|
||||||
// TODO: Load only twice, instead of 3x in cases where you load 3 indices per thread?
|
// TODO: Load only twice, instead of 3x in cases where you load 3 indices per thread?
|
||||||
fn get_meshlet_index(index_id: u32) -> u32 {
|
fn get_meshlet_vertex_id(index_id: u32) -> u32 {
|
||||||
let packed_index = meshlet_indices[index_id / 4u];
|
let packed_index = meshlet_indices[index_id / 4u];
|
||||||
let bit_offset = (index_id % 4u) * 8u;
|
let bit_offset = (index_id % 4u) * 8u;
|
||||||
return extractBits(packed_index, bit_offset, 8u);
|
return extractBits(packed_index, bit_offset, 8u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_meshlet_vertex_position(meshlet: ptr<function, Meshlet>, vertex_id: u32) -> vec3<f32> {
|
||||||
|
// Get bitstream start for the vertex
|
||||||
|
let unpacked = unpack4xU8((*meshlet).packed_b);
|
||||||
|
let bits_per_channel = unpacked.xyz;
|
||||||
|
let bits_per_vertex = bits_per_channel.x + bits_per_channel.y + bits_per_channel.z;
|
||||||
|
var start_bit = (*meshlet).start_vertex_position_bit + (vertex_id * bits_per_vertex);
|
||||||
|
|
||||||
|
// Read each vertex channel from the bitstream
|
||||||
|
var vertex_position_packed = vec3(0u);
|
||||||
|
for (var i = 0u; i < 3u; i++) {
|
||||||
|
let lower_word_index = start_bit / 32u;
|
||||||
|
let lower_word_bit_offset = start_bit & 31u;
|
||||||
|
var next_32_bits = meshlet_vertex_positions[lower_word_index] >> lower_word_bit_offset;
|
||||||
|
if lower_word_bit_offset + bits_per_channel[i] > 32u {
|
||||||
|
next_32_bits |= meshlet_vertex_positions[lower_word_index + 1u] << (32u - lower_word_bit_offset);
|
||||||
|
}
|
||||||
|
vertex_position_packed[i] = extractBits(next_32_bits, 0u, bits_per_channel[i]);
|
||||||
|
start_bit += bits_per_channel[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap [0, range_max - range_min] vec3<u32> to [range_min, range_max] vec3<f32>
|
||||||
|
var vertex_position = vec3<f32>(vertex_position_packed) + vec3(
|
||||||
|
(*meshlet).min_vertex_position_channel_x,
|
||||||
|
(*meshlet).min_vertex_position_channel_y,
|
||||||
|
(*meshlet).min_vertex_position_channel_z,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reverse vertex quantization
|
||||||
|
let vertex_position_quantization_factor = unpacked.w;
|
||||||
|
vertex_position /= f32(1u << vertex_position_quantization_factor) * CENTIMETERS_PER_METER;
|
||||||
|
|
||||||
|
return vertex_position;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MESHLET_MESH_MATERIAL_PASS
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||||
@ -121,15 +150,59 @@ fn get_meshlet_index(index_id: u32) -> u32 {
|
|||||||
@group(1) @binding(1) var<storage, read> meshlet_cluster_meshlet_ids: array<u32>; // Per cluster
|
@group(1) @binding(1) var<storage, read> meshlet_cluster_meshlet_ids: array<u32>; // Per cluster
|
||||||
@group(1) @binding(2) var<storage, read> meshlets: array<Meshlet>; // Per meshlet
|
@group(1) @binding(2) var<storage, read> meshlets: array<Meshlet>; // Per meshlet
|
||||||
@group(1) @binding(3) var<storage, read> meshlet_indices: array<u32>; // Many per meshlet
|
@group(1) @binding(3) var<storage, read> meshlet_indices: array<u32>; // Many per meshlet
|
||||||
@group(1) @binding(4) var<storage, read> meshlet_vertex_ids: array<u32>; // Many per meshlet
|
@group(1) @binding(4) var<storage, read> meshlet_vertex_positions: array<u32>; // Many per meshlet
|
||||||
@group(1) @binding(5) var<storage, read> meshlet_vertex_data: array<PackedMeshletVertex>; // Many per meshlet
|
@group(1) @binding(5) var<storage, read> meshlet_vertex_normals: array<u32>; // Many per meshlet
|
||||||
@group(1) @binding(6) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
@group(1) @binding(6) var<storage, read> meshlet_vertex_uvs: array<vec2<f32>>; // Many per meshlet
|
||||||
@group(1) @binding(7) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
@group(1) @binding(7) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
||||||
|
@group(1) @binding(8) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
||||||
|
|
||||||
// TODO: Load only twice, instead of 3x in cases where you load 3 indices per thread?
|
// TODO: Load only twice, instead of 3x in cases where you load 3 indices per thread?
|
||||||
fn get_meshlet_index(index_id: u32) -> u32 {
|
fn get_meshlet_vertex_id(index_id: u32) -> u32 {
|
||||||
let packed_index = meshlet_indices[index_id / 4u];
|
let packed_index = meshlet_indices[index_id / 4u];
|
||||||
let bit_offset = (index_id % 4u) * 8u;
|
let bit_offset = (index_id % 4u) * 8u;
|
||||||
return extractBits(packed_index, bit_offset, 8u);
|
return extractBits(packed_index, bit_offset, 8u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_meshlet_vertex_position(meshlet: ptr<function, Meshlet>, vertex_id: u32) -> vec3<f32> {
|
||||||
|
// Get bitstream start for the vertex
|
||||||
|
let unpacked = unpack4xU8((*meshlet).packed_b);
|
||||||
|
let bits_per_channel = unpacked.xyz;
|
||||||
|
let bits_per_vertex = bits_per_channel.x + bits_per_channel.y + bits_per_channel.z;
|
||||||
|
var start_bit = (*meshlet).start_vertex_position_bit + (vertex_id * bits_per_vertex);
|
||||||
|
|
||||||
|
// Read each vertex channel from the bitstream
|
||||||
|
var vertex_position_packed = vec3(0u);
|
||||||
|
for (var i = 0u; i < 3u; i++) {
|
||||||
|
let lower_word_index = start_bit / 32u;
|
||||||
|
let lower_word_bit_offset = start_bit & 31u;
|
||||||
|
var next_32_bits = meshlet_vertex_positions[lower_word_index] >> lower_word_bit_offset;
|
||||||
|
if lower_word_bit_offset + bits_per_channel[i] > 32u {
|
||||||
|
next_32_bits |= meshlet_vertex_positions[lower_word_index + 1u] << (32u - lower_word_bit_offset);
|
||||||
|
}
|
||||||
|
vertex_position_packed[i] = extractBits(next_32_bits, 0u, bits_per_channel[i]);
|
||||||
|
start_bit += bits_per_channel[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap [0, range_max - range_min] vec3<u32> to [range_min, range_max] vec3<f32>
|
||||||
|
var vertex_position = vec3<f32>(vertex_position_packed) + vec3(
|
||||||
|
(*meshlet).min_vertex_position_channel_x,
|
||||||
|
(*meshlet).min_vertex_position_channel_y,
|
||||||
|
(*meshlet).min_vertex_position_channel_z,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reverse vertex quantization
|
||||||
|
let vertex_position_quantization_factor = unpacked.w;
|
||||||
|
vertex_position /= f32(1u << vertex_position_quantization_factor) * CENTIMETERS_PER_METER;
|
||||||
|
|
||||||
|
return vertex_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_meshlet_vertex_normal(meshlet: ptr<function, Meshlet>, vertex_id: u32) -> vec3<f32> {
|
||||||
|
let packed_normal = meshlet_vertex_normals[(*meshlet).start_vertex_attribute_id + vertex_id];
|
||||||
|
return octahedral_decode_signed(unpack2x16snorm(packed_normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_meshlet_vertex_uv(meshlet: ptr<function, Meshlet>, vertex_id: u32) -> vec2<f32> {
|
||||||
|
return meshlet_vertex_uvs[(*meshlet).start_vertex_attribute_id + vertex_id];
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use bevy_ecs::{
|
|||||||
system::{Res, ResMut, Resource},
|
system::{Res, ResMut, Resource},
|
||||||
world::{FromWorld, World},
|
world::{FromWorld, World},
|
||||||
};
|
};
|
||||||
|
use bevy_math::Vec2;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_resource::BufferAddress,
|
render_resource::BufferAddress,
|
||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
@ -19,20 +20,22 @@ use core::ops::Range;
|
|||||||
/// Manages uploading [`MeshletMesh`] asset data to the GPU.
|
/// Manages uploading [`MeshletMesh`] asset data to the GPU.
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct MeshletMeshManager {
|
pub struct MeshletMeshManager {
|
||||||
pub vertex_data: PersistentGpuBuffer<Arc<[u8]>>,
|
pub vertex_positions: PersistentGpuBuffer<Arc<[u32]>>,
|
||||||
pub vertex_ids: PersistentGpuBuffer<Arc<[u32]>>,
|
pub vertex_normals: PersistentGpuBuffer<Arc<[u32]>>,
|
||||||
|
pub vertex_uvs: PersistentGpuBuffer<Arc<[Vec2]>>,
|
||||||
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>; 5]>,
|
meshlet_mesh_slices: HashMap<AssetId<MeshletMesh>, [Range<BufferAddress>; 6]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromWorld for MeshletMeshManager {
|
impl FromWorld for MeshletMeshManager {
|
||||||
fn from_world(world: &mut World) -> Self {
|
fn from_world(world: &mut World) -> Self {
|
||||||
let render_device = world.resource::<RenderDevice>();
|
let render_device = world.resource::<RenderDevice>();
|
||||||
Self {
|
Self {
|
||||||
vertex_data: PersistentGpuBuffer::new("meshlet_vertex_data", render_device),
|
vertex_positions: PersistentGpuBuffer::new("meshlet_vertex_positions", render_device),
|
||||||
vertex_ids: PersistentGpuBuffer::new("meshlet_vertex_ids", render_device),
|
vertex_normals: PersistentGpuBuffer::new("meshlet_vertex_normals", render_device),
|
||||||
|
vertex_uvs: PersistentGpuBuffer::new("meshlet_vertex_uvs", render_device),
|
||||||
indices: PersistentGpuBuffer::new("meshlet_indices", render_device),
|
indices: PersistentGpuBuffer::new("meshlet_indices", render_device),
|
||||||
meshlets: PersistentGpuBuffer::new("meshlets", render_device),
|
meshlets: PersistentGpuBuffer::new("meshlets", render_device),
|
||||||
meshlet_bounding_spheres: PersistentGpuBuffer::new(
|
meshlet_bounding_spheres: PersistentGpuBuffer::new(
|
||||||
@ -55,27 +58,34 @@ impl MeshletMeshManager {
|
|||||||
"MeshletMesh asset was already unloaded but is not registered with MeshletMeshManager",
|
"MeshletMesh asset was already unloaded but is not registered with MeshletMeshManager",
|
||||||
);
|
);
|
||||||
|
|
||||||
let vertex_data_slice = self
|
let vertex_positions_slice = self
|
||||||
.vertex_data
|
.vertex_positions
|
||||||
.queue_write(Arc::clone(&meshlet_mesh.vertex_data), ());
|
.queue_write(Arc::clone(&meshlet_mesh.vertex_positions), ());
|
||||||
let vertex_ids_slice = self.vertex_ids.queue_write(
|
let vertex_normals_slice = self
|
||||||
Arc::clone(&meshlet_mesh.vertex_ids),
|
.vertex_normals
|
||||||
vertex_data_slice.start,
|
.queue_write(Arc::clone(&meshlet_mesh.vertex_normals), ());
|
||||||
);
|
let vertex_uvs_slice = self
|
||||||
|
.vertex_uvs
|
||||||
|
.queue_write(Arc::clone(&meshlet_mesh.vertex_uvs), ());
|
||||||
let indices_slice = self
|
let indices_slice = self
|
||||||
.indices
|
.indices
|
||||||
.queue_write(Arc::clone(&meshlet_mesh.indices), ());
|
.queue_write(Arc::clone(&meshlet_mesh.indices), ());
|
||||||
let meshlets_slice = self.meshlets.queue_write(
|
let meshlets_slice = self.meshlets.queue_write(
|
||||||
Arc::clone(&meshlet_mesh.meshlets),
|
Arc::clone(&meshlet_mesh.meshlets),
|
||||||
(vertex_ids_slice.start, indices_slice.start),
|
(
|
||||||
|
vertex_positions_slice.start,
|
||||||
|
vertex_normals_slice.start,
|
||||||
|
indices_slice.start,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let meshlet_bounding_spheres_slice = self
|
let meshlet_bounding_spheres_slice = self
|
||||||
.meshlet_bounding_spheres
|
.meshlet_bounding_spheres
|
||||||
.queue_write(Arc::clone(&meshlet_mesh.bounding_spheres), ());
|
.queue_write(Arc::clone(&meshlet_mesh.meshlet_bounding_spheres), ());
|
||||||
|
|
||||||
[
|
[
|
||||||
vertex_data_slice,
|
vertex_positions_slice,
|
||||||
vertex_ids_slice,
|
vertex_normals_slice,
|
||||||
|
vertex_uvs_slice,
|
||||||
indices_slice,
|
indices_slice,
|
||||||
meshlets_slice,
|
meshlets_slice,
|
||||||
meshlet_bounding_spheres_slice,
|
meshlet_bounding_spheres_slice,
|
||||||
@ -83,7 +93,7 @@ impl MeshletMeshManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 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)
|
||||||
@ -96,11 +106,13 @@ 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_data_slice, vertex_ids_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],
|
||||||
) = self.meshlet_mesh_slices.remove(asset_id)
|
) = self.meshlet_mesh_slices.remove(asset_id)
|
||||||
{
|
{
|
||||||
self.vertex_data.mark_slice_unused(vertex_data_slice);
|
self.vertex_positions
|
||||||
self.vertex_ids.mark_slice_unused(vertex_ids_slice);
|
.mark_slice_unused(vertex_positions_slice);
|
||||||
|
self.vertex_normals.mark_slice_unused(vertex_normals_slice);
|
||||||
|
self.vertex_uvs.mark_slice_unused(vertex_uvs_slice);
|
||||||
self.indices.mark_slice_unused(indices_slice);
|
self.indices.mark_slice_unused(indices_slice);
|
||||||
self.meshlets.mark_slice_unused(meshlets_slice);
|
self.meshlets.mark_slice_unused(meshlets_slice);
|
||||||
self.meshlet_bounding_spheres
|
self.meshlet_bounding_spheres
|
||||||
@ -116,10 +128,13 @@ pub fn perform_pending_meshlet_mesh_writes(
|
|||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
) {
|
) {
|
||||||
meshlet_mesh_manager
|
meshlet_mesh_manager
|
||||||
.vertex_data
|
.vertex_positions
|
||||||
.perform_writes(&render_queue, &render_device);
|
.perform_writes(&render_queue, &render_device);
|
||||||
meshlet_mesh_manager
|
meshlet_mesh_manager
|
||||||
.vertex_ids
|
.vertex_normals
|
||||||
|
.perform_writes(&render_queue, &render_device);
|
||||||
|
meshlet_mesh_manager
|
||||||
|
.vertex_uvs
|
||||||
.perform_writes(&render_queue, &render_device);
|
.perform_writes(&render_queue, &render_device);
|
||||||
meshlet_mesh_manager
|
meshlet_mesh_manager
|
||||||
.indices
|
.indices
|
||||||
|
|||||||
@ -32,9 +32,11 @@ pub(crate) use self::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::asset::{MeshletMesh, MeshletMeshSaverLoader};
|
pub use self::asset::{MeshletMesh, MeshletMeshLoader, MeshletMeshSaver};
|
||||||
#[cfg(feature = "meshlet_processor")]
|
#[cfg(feature = "meshlet_processor")]
|
||||||
pub use self::from_mesh::MeshToMeshletMeshConversionError;
|
pub use self::from_mesh::{
|
||||||
|
MeshToMeshletMeshConversionError, DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
|
||||||
|
};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
graph::NodeMeshlet,
|
graph::NodeMeshlet,
|
||||||
@ -201,7 +203,7 @@ impl Plugin for MeshletPlugin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
app.init_asset::<MeshletMesh>()
|
app.init_asset::<MeshletMesh>()
|
||||||
.register_asset_loader(MeshletMeshSaverLoader)
|
.register_asset_loader(MeshletMeshLoader)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
check_visibility::<WithMeshletMesh>.in_set(VisibilitySystems::CheckVisibility),
|
check_visibility::<WithMeshletMesh>.in_set(VisibilitySystems::CheckVisibility),
|
||||||
|
|||||||
@ -3,8 +3,7 @@ use super::{
|
|||||||
persistent_buffer::PersistentGpuBufferable,
|
persistent_buffer::PersistentGpuBufferable,
|
||||||
};
|
};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
|
use bevy_math::Vec2;
|
||||||
const MESHLET_VERTEX_SIZE_IN_BYTES: u32 = 32;
|
|
||||||
|
|
||||||
impl PersistentGpuBufferable for Arc<[u8]> {
|
impl PersistentGpuBufferable for Arc<[u8]> {
|
||||||
type Metadata = ();
|
type Metadata = ();
|
||||||
@ -19,26 +18,31 @@ impl PersistentGpuBufferable for Arc<[u8]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PersistentGpuBufferable for Arc<[u32]> {
|
impl PersistentGpuBufferable for Arc<[u32]> {
|
||||||
type Metadata = u64;
|
type Metadata = ();
|
||||||
|
|
||||||
fn size_in_bytes(&self) -> usize {
|
fn size_in_bytes(&self) -> usize {
|
||||||
self.len() * size_of::<u32>()
|
self.len() * size_of::<u32>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_bytes_le(&self, offset: Self::Metadata, buffer_slice: &mut [u8]) {
|
fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) {
|
||||||
let offset = offset as u32 / MESHLET_VERTEX_SIZE_IN_BYTES;
|
buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i, index) in self.iter().enumerate() {
|
impl PersistentGpuBufferable for Arc<[Vec2]> {
|
||||||
let size = size_of::<u32>();
|
type Metadata = ();
|
||||||
let i = i * size;
|
|
||||||
let bytes = (*index + offset).to_le_bytes();
|
fn size_in_bytes(&self) -> usize {
|
||||||
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
|
self.len() * size_of::<Vec2>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) {
|
||||||
|
buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PersistentGpuBufferable for Arc<[Meshlet]> {
|
impl PersistentGpuBufferable for Arc<[Meshlet]> {
|
||||||
type Metadata = (u64, u64);
|
type Metadata = (u64, u64, u64);
|
||||||
|
|
||||||
fn size_in_bytes(&self) -> usize {
|
fn size_in_bytes(&self) -> usize {
|
||||||
self.len() * size_of::<Meshlet>()
|
self.len() * size_of::<Meshlet>()
|
||||||
@ -46,20 +50,23 @@ impl PersistentGpuBufferable for Arc<[Meshlet]> {
|
|||||||
|
|
||||||
fn write_bytes_le(
|
fn write_bytes_le(
|
||||||
&self,
|
&self,
|
||||||
(vertex_offset, index_offset): Self::Metadata,
|
(vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata,
|
||||||
buffer_slice: &mut [u8],
|
buffer_slice: &mut [u8],
|
||||||
) {
|
) {
|
||||||
let vertex_offset = (vertex_offset as usize / size_of::<u32>()) as u32;
|
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;
|
let index_offset = index_offset as u32;
|
||||||
|
|
||||||
for (i, meshlet) in self.iter().enumerate() {
|
for (i, meshlet) in self.iter().enumerate() {
|
||||||
let size = size_of::<Meshlet>();
|
let size = size_of::<Meshlet>();
|
||||||
let i = i * size;
|
let i = i * size;
|
||||||
let bytes = bytemuck::cast::<_, [u8; size_of::<Meshlet>()]>(Meshlet {
|
let bytes = bytemuck::cast::<_, [u8; size_of::<Meshlet>()]>(Meshlet {
|
||||||
start_vertex_id: meshlet.start_vertex_id + vertex_offset,
|
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,
|
start_index_id: meshlet.start_index_id + index_offset,
|
||||||
vertex_count: meshlet.vertex_count,
|
..*meshlet
|
||||||
triangle_count: meshlet.triangle_count,
|
|
||||||
});
|
});
|
||||||
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
|
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,7 +185,6 @@ 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),
|
||||||
uniform_buffer::<ViewUniform>(true),
|
uniform_buffer::<ViewUniform>(true),
|
||||||
),
|
),
|
||||||
@ -222,6 +221,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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -702,8 +702,7 @@ pub fn prepare_meshlet_view_bind_groups(
|
|||||||
cluster_meshlet_ids.as_entire_binding(),
|
cluster_meshlet_ids.as_entire_binding(),
|
||||||
meshlet_mesh_manager.meshlets.binding(),
|
meshlet_mesh_manager.meshlets.binding(),
|
||||||
meshlet_mesh_manager.indices.binding(),
|
meshlet_mesh_manager.indices.binding(),
|
||||||
meshlet_mesh_manager.vertex_ids.binding(),
|
meshlet_mesh_manager.vertex_positions.binding(),
|
||||||
meshlet_mesh_manager.vertex_data.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(),
|
||||||
resource_manager
|
resource_manager
|
||||||
@ -746,8 +745,9 @@ pub fn prepare_meshlet_view_bind_groups(
|
|||||||
cluster_meshlet_ids.as_entire_binding(),
|
cluster_meshlet_ids.as_entire_binding(),
|
||||||
meshlet_mesh_manager.meshlets.binding(),
|
meshlet_mesh_manager.meshlets.binding(),
|
||||||
meshlet_mesh_manager.indices.binding(),
|
meshlet_mesh_manager.indices.binding(),
|
||||||
meshlet_mesh_manager.vertex_ids.binding(),
|
meshlet_mesh_manager.vertex_positions.binding(),
|
||||||
meshlet_mesh_manager.vertex_data.binding(),
|
meshlet_mesh_manager.vertex_normals.binding(),
|
||||||
|
meshlet_mesh_manager.vertex_uvs.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(),
|
||||||
));
|
));
|
||||||
|
|||||||
@ -2,15 +2,14 @@
|
|||||||
meshlet_bindings::{
|
meshlet_bindings::{
|
||||||
meshlet_cluster_meshlet_ids,
|
meshlet_cluster_meshlet_ids,
|
||||||
meshlets,
|
meshlets,
|
||||||
meshlet_vertex_ids,
|
|
||||||
meshlet_vertex_data,
|
|
||||||
meshlet_cluster_instance_ids,
|
meshlet_cluster_instance_ids,
|
||||||
meshlet_instance_uniforms,
|
meshlet_instance_uniforms,
|
||||||
meshlet_raster_clusters,
|
meshlet_raster_clusters,
|
||||||
meshlet_visibility_buffer,
|
meshlet_visibility_buffer,
|
||||||
view,
|
view,
|
||||||
get_meshlet_index,
|
get_meshlet_triangle_count,
|
||||||
unpack_meshlet_vertex,
|
get_meshlet_vertex_id,
|
||||||
|
get_meshlet_vertex_position,
|
||||||
},
|
},
|
||||||
mesh_functions::mesh_position_local_to_world,
|
mesh_functions::mesh_position_local_to_world,
|
||||||
}
|
}
|
||||||
@ -33,20 +32,19 @@ struct VertexOutput {
|
|||||||
fn vertex(@builtin(instance_index) instance_index: u32, @builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
fn vertex(@builtin(instance_index) instance_index: u32, @builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
let cluster_id = meshlet_raster_clusters[meshlet_raster_cluster_rightmost_slot - instance_index];
|
let cluster_id = meshlet_raster_clusters[meshlet_raster_cluster_rightmost_slot - instance_index];
|
||||||
let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id];
|
let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id];
|
||||||
let meshlet = meshlets[meshlet_id];
|
var meshlet = meshlets[meshlet_id];
|
||||||
|
|
||||||
let triangle_id = vertex_index / 3u;
|
let triangle_id = vertex_index / 3u;
|
||||||
if triangle_id >= meshlet.triangle_count { return dummy_vertex(); }
|
if triangle_id >= get_meshlet_triangle_count(&meshlet) { return dummy_vertex(); }
|
||||||
let index_id = (triangle_id * 3u) + (vertex_index % 3u);
|
let index_id = (triangle_id * 3u) + (vertex_index % 3u);
|
||||||
let index = get_meshlet_index(meshlet.start_index_id + index_id);
|
let vertex_id = get_meshlet_vertex_id(meshlet.start_index_id + index_id);
|
||||||
let vertex_id = meshlet_vertex_ids[meshlet.start_vertex_id + index];
|
|
||||||
let vertex = unpack_meshlet_vertex(meshlet_vertex_data[vertex_id]);
|
|
||||||
|
|
||||||
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
||||||
let instance_uniform = meshlet_instance_uniforms[instance_id];
|
let instance_uniform = meshlet_instance_uniforms[instance_id];
|
||||||
|
|
||||||
|
let vertex_position = get_meshlet_vertex_position(&meshlet, vertex_id);
|
||||||
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_position = mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
|
let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex_position, 1.0));
|
||||||
var clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);
|
var clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);
|
||||||
#ifdef DEPTH_CLAMP_ORTHO
|
#ifdef DEPTH_CLAMP_ORTHO
|
||||||
let unclamped_clip_depth = clip_position.z;
|
let unclamped_clip_depth = clip_position.z;
|
||||||
|
|||||||
@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
#import bevy_pbr::{
|
#import bevy_pbr::{
|
||||||
meshlet_bindings::{
|
meshlet_bindings::{
|
||||||
|
Meshlet,
|
||||||
meshlet_visibility_buffer,
|
meshlet_visibility_buffer,
|
||||||
meshlet_cluster_meshlet_ids,
|
meshlet_cluster_meshlet_ids,
|
||||||
meshlets,
|
meshlets,
|
||||||
meshlet_vertex_ids,
|
|
||||||
meshlet_vertex_data,
|
|
||||||
meshlet_cluster_instance_ids,
|
meshlet_cluster_instance_ids,
|
||||||
meshlet_instance_uniforms,
|
meshlet_instance_uniforms,
|
||||||
get_meshlet_index,
|
get_meshlet_vertex_id,
|
||||||
unpack_meshlet_vertex,
|
get_meshlet_vertex_position,
|
||||||
|
get_meshlet_vertex_normal,
|
||||||
|
get_meshlet_vertex_uv,
|
||||||
},
|
},
|
||||||
mesh_view_bindings::view,
|
mesh_view_bindings::view,
|
||||||
mesh_functions::mesh_position_local_to_world,
|
mesh_functions::mesh_position_local_to_world,
|
||||||
@ -106,59 +107,58 @@ fn resolve_vertex_output(frag_coord: vec4<f32>) -> VertexOutput {
|
|||||||
let packed_ids = u32(meshlet_visibility_buffer[frag_coord_1d]); // TODO: Might be faster to load the correct u32 directly
|
let packed_ids = u32(meshlet_visibility_buffer[frag_coord_1d]); // TODO: Might be faster to load the correct u32 directly
|
||||||
let cluster_id = packed_ids >> 7u;
|
let cluster_id = packed_ids >> 7u;
|
||||||
let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id];
|
let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id];
|
||||||
let meshlet = meshlets[meshlet_id];
|
var meshlet = meshlets[meshlet_id];
|
||||||
|
|
||||||
let triangle_id = extractBits(packed_ids, 0u, 7u);
|
let triangle_id = extractBits(packed_ids, 0u, 7u);
|
||||||
let index_ids = meshlet.start_index_id + (triangle_id * 3u) + vec3(0u, 1u, 2u);
|
let index_ids = meshlet.start_index_id + (triangle_id * 3u) + vec3(0u, 1u, 2u);
|
||||||
let indices = meshlet.start_vertex_id + vec3(get_meshlet_index(index_ids.x), get_meshlet_index(index_ids.y), get_meshlet_index(index_ids.z));
|
let vertex_ids = vec3(get_meshlet_vertex_id(index_ids[0]), get_meshlet_vertex_id(index_ids[1]), get_meshlet_vertex_id(index_ids[2]));
|
||||||
let vertex_ids = vec3(meshlet_vertex_ids[indices.x], meshlet_vertex_ids[indices.y], meshlet_vertex_ids[indices.z]);
|
let vertex_0 = load_vertex(&meshlet, vertex_ids[0]);
|
||||||
let vertex_1 = unpack_meshlet_vertex(meshlet_vertex_data[vertex_ids.x]);
|
let vertex_1 = load_vertex(&meshlet, vertex_ids[1]);
|
||||||
let vertex_2 = unpack_meshlet_vertex(meshlet_vertex_data[vertex_ids.y]);
|
let vertex_2 = load_vertex(&meshlet, vertex_ids[2]);
|
||||||
let vertex_3 = unpack_meshlet_vertex(meshlet_vertex_data[vertex_ids.z]);
|
|
||||||
|
|
||||||
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
||||||
var instance_uniform = meshlet_instance_uniforms[instance_id];
|
var instance_uniform = meshlet_instance_uniforms[instance_id];
|
||||||
|
|
||||||
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_position_0 = mesh_position_local_to_world(world_from_local, vec4(vertex_0.position, 1.0));
|
||||||
let world_position_1 = mesh_position_local_to_world(world_from_local, vec4(vertex_1.position, 1.0));
|
let world_position_1 = mesh_position_local_to_world(world_from_local, vec4(vertex_1.position, 1.0));
|
||||||
let world_position_2 = mesh_position_local_to_world(world_from_local, vec4(vertex_2.position, 1.0));
|
let world_position_2 = mesh_position_local_to_world(world_from_local, vec4(vertex_2.position, 1.0));
|
||||||
let world_position_3 = mesh_position_local_to_world(world_from_local, vec4(vertex_3.position, 1.0));
|
|
||||||
|
|
||||||
let frag_coord_ndc = frag_coord_to_ndc(frag_coord).xy;
|
let frag_coord_ndc = frag_coord_to_ndc(frag_coord).xy;
|
||||||
let partial_derivatives = compute_partial_derivatives(
|
let partial_derivatives = compute_partial_derivatives(
|
||||||
array(world_position_1, world_position_2, world_position_3),
|
array(world_position_0, world_position_1, world_position_2),
|
||||||
frag_coord_ndc,
|
frag_coord_ndc,
|
||||||
view.viewport.zw / 2.0,
|
view.viewport.zw / 2.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
let world_position = mat3x4(world_position_1, world_position_2, world_position_3) * partial_derivatives.barycentrics;
|
let world_position = mat3x4(world_position_0, world_position_1, world_position_2) * partial_derivatives.barycentrics;
|
||||||
let world_positions_camera_relative = mat3x3(
|
let world_positions_camera_relative = mat3x3(
|
||||||
|
world_position_0.xyz - view.world_position,
|
||||||
world_position_1.xyz - view.world_position,
|
world_position_1.xyz - view.world_position,
|
||||||
world_position_2.xyz - view.world_position,
|
world_position_2.xyz - view.world_position,
|
||||||
world_position_3.xyz - view.world_position,
|
|
||||||
);
|
);
|
||||||
let ddx_world_position = world_positions_camera_relative * partial_derivatives.ddx;
|
let ddx_world_position = world_positions_camera_relative * partial_derivatives.ddx;
|
||||||
let ddy_world_position = world_positions_camera_relative * partial_derivatives.ddy;
|
let ddy_world_position = world_positions_camera_relative * partial_derivatives.ddy;
|
||||||
|
|
||||||
let world_normal = mat3x3(
|
let world_normal = mat3x3(
|
||||||
|
normal_local_to_world(vertex_0.normal, &instance_uniform),
|
||||||
normal_local_to_world(vertex_1.normal, &instance_uniform),
|
normal_local_to_world(vertex_1.normal, &instance_uniform),
|
||||||
normal_local_to_world(vertex_2.normal, &instance_uniform),
|
normal_local_to_world(vertex_2.normal, &instance_uniform),
|
||||||
normal_local_to_world(vertex_3.normal, &instance_uniform),
|
|
||||||
) * partial_derivatives.barycentrics;
|
) * partial_derivatives.barycentrics;
|
||||||
|
|
||||||
let uv = mat3x2(vertex_1.uv, vertex_2.uv, vertex_3.uv) * partial_derivatives.barycentrics;
|
let uv = mat3x2(vertex_0.uv, vertex_1.uv, vertex_2.uv) * partial_derivatives.barycentrics;
|
||||||
let ddx_uv = mat3x2(vertex_1.uv, vertex_2.uv, vertex_3.uv) * partial_derivatives.ddx;
|
let ddx_uv = mat3x2(vertex_0.uv, vertex_1.uv, vertex_2.uv) * partial_derivatives.ddx;
|
||||||
let ddy_uv = mat3x2(vertex_1.uv, vertex_2.uv, vertex_3.uv) * partial_derivatives.ddy;
|
let ddy_uv = mat3x2(vertex_0.uv, vertex_1.uv, vertex_2.uv) * partial_derivatives.ddy;
|
||||||
|
|
||||||
let world_tangent = calculate_world_tangent(world_normal, ddx_world_position, ddy_world_position, ddx_uv, ddy_uv);
|
let world_tangent = calculate_world_tangent(world_normal, ddx_world_position, ddy_world_position, ddx_uv, ddy_uv);
|
||||||
|
|
||||||
#ifdef PREPASS_FRAGMENT
|
#ifdef PREPASS_FRAGMENT
|
||||||
#ifdef MOTION_VECTOR_PREPASS
|
#ifdef MOTION_VECTOR_PREPASS
|
||||||
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_position_0 = mesh_position_local_to_world(previous_world_from_local, vec4(vertex_0.position, 1.0));
|
||||||
let previous_world_position_1 = mesh_position_local_to_world(previous_world_from_local, vec4(vertex_1.position, 1.0));
|
let previous_world_position_1 = mesh_position_local_to_world(previous_world_from_local, vec4(vertex_1.position, 1.0));
|
||||||
let previous_world_position_2 = mesh_position_local_to_world(previous_world_from_local, vec4(vertex_2.position, 1.0));
|
let previous_world_position_2 = mesh_position_local_to_world(previous_world_from_local, vec4(vertex_2.position, 1.0));
|
||||||
let previous_world_position_3 = mesh_position_local_to_world(previous_world_from_local, vec4(vertex_3.position, 1.0));
|
let previous_world_position = mat3x4(previous_world_position_0, previous_world_position_1, previous_world_position_2) * partial_derivatives.barycentrics;
|
||||||
let previous_world_position = mat3x4(previous_world_position_1, previous_world_position_2, previous_world_position_3) * partial_derivatives.barycentrics;
|
|
||||||
let motion_vector = calculate_motion_vector(world_position, previous_world_position);
|
let motion_vector = calculate_motion_vector(world_position, previous_world_position);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@ -181,6 +181,20 @@ fn resolve_vertex_output(frag_coord: vec4<f32>) -> VertexOutput {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MeshletVertex {
|
||||||
|
position: vec3<f32>,
|
||||||
|
normal: vec3<f32>,
|
||||||
|
uv: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_vertex(meshlet: ptr<function, Meshlet>, vertex_id: u32) -> MeshletVertex {
|
||||||
|
return MeshletVertex(
|
||||||
|
get_meshlet_vertex_position(meshlet, vertex_id),
|
||||||
|
get_meshlet_vertex_normal(meshlet, vertex_id),
|
||||||
|
get_meshlet_vertex_uv(meshlet, vertex_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn normal_local_to_world(vertex_normal: vec3<f32>, instance_uniform: ptr<function, Mesh>) -> vec3<f32> {
|
fn normal_local_to_world(vertex_normal: vec3<f32>, instance_uniform: ptr<function, Mesh>) -> vec3<f32> {
|
||||||
if any(vertex_normal != vec3<f32>(0.0)) {
|
if any(vertex_normal != vec3<f32>(0.0)) {
|
||||||
return normalize(
|
return normalize(
|
||||||
|
|||||||
@ -2,16 +2,16 @@
|
|||||||
meshlet_bindings::{
|
meshlet_bindings::{
|
||||||
meshlet_cluster_meshlet_ids,
|
meshlet_cluster_meshlet_ids,
|
||||||
meshlets,
|
meshlets,
|
||||||
meshlet_vertex_ids,
|
|
||||||
meshlet_vertex_data,
|
|
||||||
meshlet_cluster_instance_ids,
|
meshlet_cluster_instance_ids,
|
||||||
meshlet_instance_uniforms,
|
meshlet_instance_uniforms,
|
||||||
meshlet_raster_clusters,
|
meshlet_raster_clusters,
|
||||||
meshlet_software_raster_cluster_count,
|
meshlet_software_raster_cluster_count,
|
||||||
meshlet_visibility_buffer,
|
meshlet_visibility_buffer,
|
||||||
view,
|
view,
|
||||||
get_meshlet_index,
|
get_meshlet_vertex_count,
|
||||||
unpack_meshlet_vertex,
|
get_meshlet_triangle_count,
|
||||||
|
get_meshlet_vertex_id,
|
||||||
|
get_meshlet_vertex_position,
|
||||||
},
|
},
|
||||||
mesh_functions::mesh_position_local_to_world,
|
mesh_functions::mesh_position_local_to_world,
|
||||||
view_transformations::ndc_to_uv,
|
view_transformations::ndc_to_uv,
|
||||||
@ -42,7 +42,7 @@ fn rasterize_cluster(
|
|||||||
|
|
||||||
let cluster_id = meshlet_raster_clusters[workgroup_id_1d];
|
let cluster_id = meshlet_raster_clusters[workgroup_id_1d];
|
||||||
let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id];
|
let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id];
|
||||||
let meshlet = meshlets[meshlet_id];
|
var meshlet = meshlets[meshlet_id];
|
||||||
|
|
||||||
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
||||||
let instance_uniform = meshlet_instance_uniforms[instance_id];
|
let instance_uniform = meshlet_instance_uniforms[instance_id];
|
||||||
@ -51,12 +51,11 @@ fn rasterize_cluster(
|
|||||||
// Load and project 1 vertex per thread, and then again if there are more than 128 vertices in the meshlet
|
// Load and project 1 vertex per thread, and then again if there are more than 128 vertices in the meshlet
|
||||||
for (var i = 0u; i <= 128u; i += 128u) {
|
for (var i = 0u; i <= 128u; i += 128u) {
|
||||||
let vertex_id = local_invocation_index + i;
|
let vertex_id = local_invocation_index + i;
|
||||||
if vertex_id < meshlet.vertex_count {
|
if vertex_id < get_meshlet_vertex_count(&meshlet) {
|
||||||
let meshlet_vertex_id = meshlet_vertex_ids[meshlet.start_vertex_id + vertex_id];
|
let vertex_position = get_meshlet_vertex_position(&meshlet, vertex_id);
|
||||||
let vertex = unpack_meshlet_vertex(meshlet_vertex_data[meshlet_vertex_id]);
|
|
||||||
|
|
||||||
// Project vertex to viewport space
|
// Project vertex to viewport space
|
||||||
let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
|
let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex_position, 1.0));
|
||||||
let clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);
|
let clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0);
|
||||||
var ndc_position = clip_position.xyz / clip_position.w;
|
var ndc_position = clip_position.xyz / clip_position.w;
|
||||||
#ifdef DEPTH_CLAMP_ORTHO
|
#ifdef DEPTH_CLAMP_ORTHO
|
||||||
@ -72,9 +71,9 @@ fn rasterize_cluster(
|
|||||||
|
|
||||||
// Load 1 triangle's worth of vertex data per thread
|
// Load 1 triangle's worth of vertex data per thread
|
||||||
let triangle_id = local_invocation_index;
|
let triangle_id = local_invocation_index;
|
||||||
if triangle_id >= meshlet.triangle_count { return; }
|
if triangle_id >= get_meshlet_triangle_count(&meshlet) { return; }
|
||||||
let index_ids = meshlet.start_index_id + (triangle_id * 3u) + vec3(0u, 1u, 2u);
|
let index_ids = meshlet.start_index_id + (triangle_id * 3u) + vec3(0u, 1u, 2u);
|
||||||
let vertex_ids = vec3(get_meshlet_index(index_ids[0]), get_meshlet_index(index_ids[1]), get_meshlet_index(index_ids[2]));
|
let vertex_ids = vec3(get_meshlet_vertex_id(index_ids[0]), get_meshlet_vertex_id(index_ids[1]), get_meshlet_vertex_id(index_ids[2]));
|
||||||
let vertex_0 = viewport_vertices[vertex_ids[2]];
|
let vertex_0 = viewport_vertices[vertex_ids[2]];
|
||||||
let vertex_1 = viewport_vertices[vertex_ids[1]];
|
let vertex_1 = viewport_vertices[vertex_ids[1]];
|
||||||
let vertex_2 = viewport_vertices[vertex_ids[0]];
|
let vertex_2 = viewport_vertices[vertex_ids[0]];
|
||||||
|
|||||||
@ -55,7 +55,13 @@ fn octahedral_encode(v: vec3<f32>) -> vec2<f32> {
|
|||||||
// For decoding normals or unit direction vectors from octahedral coordinates.
|
// For decoding normals or unit direction vectors from octahedral coordinates.
|
||||||
fn octahedral_decode(v: vec2<f32>) -> vec3<f32> {
|
fn octahedral_decode(v: vec2<f32>) -> vec3<f32> {
|
||||||
let f = v * 2.0 - 1.0;
|
let f = v * 2.0 - 1.0;
|
||||||
var n = vec3(f.xy, 1.0 - abs(f.x) - abs(f.y));
|
var n = octahedral_decode_signed(f);
|
||||||
|
return normalize(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like octahedral_decode, but for input in [-1, 1] instead of [0, 1].
|
||||||
|
fn octahedral_decode_signed(v: vec2<f32>) -> vec3<f32> {
|
||||||
|
var n = vec3(v.xy, 1.0 - abs(v.x) - abs(v.y));
|
||||||
let t = saturate(-n.z);
|
let t = saturate(-n.z);
|
||||||
let w = select(vec2(t), vec2(-t), n.xy >= vec2(0.0));
|
let w = select(vec2(t), vec2(-t), n.xy >= vec2(0.0));
|
||||||
n = vec3(n.xy + w, n.z);
|
n = vec3(n.xy + w, n.z);
|
||||||
|
|||||||
@ -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/854eb98353ad94aea1104f355fc24dbe4fda679d/bunny.meshlet_mesh";
|
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8443bbdee0bf517e6c297dede7f6a46ab712ee4c/bunny.meshlet_mesh";
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {
|
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user