mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
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
14 changed files with 467 additions and 172 deletions
|
@ -1209,7 +1209,7 @@ setup = [
|
|||
"curl",
|
||||
"-o",
|
||||
"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)
|
||||
meshlet = ["dep:lz4_flex", "dep:thiserror", "dep:range-alloc", "dep:bevy_tasks"]
|
||||
# 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]
|
||||
# bevy
|
||||
|
@ -53,6 +59,7 @@ range-alloc = { version = "0.1.3", optional = true }
|
|||
meshopt = { version = "0.3.0", optional = true }
|
||||
metis = { version = "0.2", optional = true }
|
||||
itertools = { version = "0.13", optional = true }
|
||||
bitvec = { version = "1", optional = true }
|
||||
# direct dependency required for derive macro
|
||||
bytemuck = { version = "1", features = ["derive", "must_cast"] }
|
||||
radsort = "0.1"
|
||||
|
|
|
@ -4,7 +4,7 @@ use bevy_asset::{
|
|||
saver::{AssetSaver, SavedAsset},
|
||||
Asset, AssetLoader, AsyncReadExt, AsyncWriteExt, LoadContext,
|
||||
};
|
||||
use bevy_math::Vec3;
|
||||
use bevy_math::{Vec2, Vec3};
|
||||
use bevy_reflect::TypePath;
|
||||
use bevy_tasks::block_on;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
@ -38,30 +38,51 @@ pub const MESHLET_MESH_ASSET_VERSION: u64 = 1;
|
|||
/// See also [`super::MaterialMeshletMeshBundle`] and [`super::MeshletPlugin`].
|
||||
#[derive(Asset, TypePath, Clone)]
|
||||
pub struct MeshletMesh {
|
||||
/// Raw vertex data bytes for the overall mesh.
|
||||
pub(crate) vertex_data: Arc<[u8]>,
|
||||
/// Indices into `vertex_data`.
|
||||
pub(crate) vertex_ids: Arc<[u32]>,
|
||||
/// Indices into `vertex_ids`.
|
||||
/// Quantized and bitstream-packed vertex positions for meshlet vertices.
|
||||
pub(crate) vertex_positions: Arc<[u32]>,
|
||||
/// Octahedral-encoded and 2x16snorm packed normals for meshlet vertices.
|
||||
pub(crate) vertex_normals: Arc<[u32]>,
|
||||
/// Uncompressed vertex texture coordinates for meshlet vertices.
|
||||
pub(crate) vertex_uvs: Arc<[Vec2]>,
|
||||
/// Triangle indices for meshlets.
|
||||
pub(crate) indices: Arc<[u8]>,
|
||||
/// The list of meshlets making up this mesh.
|
||||
pub(crate) meshlets: Arc<[Meshlet]>,
|
||||
/// Spherical bounding volumes.
|
||||
pub(crate) bounding_spheres: Arc<[MeshletBoundingSpheres]>,
|
||||
pub(crate) meshlet_bounding_spheres: Arc<[MeshletBoundingSpheres]>,
|
||||
}
|
||||
|
||||
/// A single meshlet within a [`MeshletMesh`].
|
||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Meshlet {
|
||||
/// The offset within the parent mesh's [`MeshletMesh::vertex_ids`] buffer where the indices for this meshlet begin.
|
||||
pub start_vertex_id: u32,
|
||||
/// The bit offset within the parent mesh's [`MeshletMesh::vertex_positions`] buffer where the vertex positions for this meshlet begin.
|
||||
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.
|
||||
pub start_index_id: u32,
|
||||
/// The amount of vertices in this meshlet.
|
||||
pub vertex_count: u32,
|
||||
pub vertex_count: u8,
|
||||
/// 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`].
|
||||
|
@ -84,13 +105,13 @@ pub struct MeshletBoundingSphere {
|
|||
pub radius: f32,
|
||||
}
|
||||
|
||||
/// An [`AssetLoader`] and [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
|
||||
pub struct MeshletMeshSaverLoader;
|
||||
/// An [`AssetSaver`] for `.meshlet_mesh` [`MeshletMesh`] assets.
|
||||
pub struct MeshletMeshSaver;
|
||||
|
||||
impl AssetSaver for MeshletMeshSaverLoader {
|
||||
impl AssetSaver for MeshletMeshSaver {
|
||||
type Asset = MeshletMesh;
|
||||
type Settings = ();
|
||||
type OutputLoader = Self;
|
||||
type OutputLoader = MeshletMeshLoader;
|
||||
type Error = MeshletMeshSaveOrLoadError;
|
||||
|
||||
async fn save(
|
||||
|
@ -111,18 +132,22 @@ impl AssetSaver for MeshletMeshSaverLoader {
|
|||
|
||||
// Compress and write asset data
|
||||
let mut writer = FrameEncoder::new(AsyncWriteSyncAdapter(writer));
|
||||
write_slice(&asset.vertex_data, &mut writer)?;
|
||||
write_slice(&asset.vertex_ids, &mut writer)?;
|
||||
write_slice(&asset.vertex_positions, &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.meshlets, &mut writer)?;
|
||||
write_slice(&asset.bounding_spheres, &mut writer)?;
|
||||
write_slice(&asset.meshlet_bounding_spheres, &mut writer)?;
|
||||
writer.finish()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetLoader for MeshletMeshSaverLoader {
|
||||
/// An [`AssetLoader`] for `.meshlet_mesh` [`MeshletMesh`] assets.
|
||||
pub struct MeshletMeshLoader;
|
||||
|
||||
impl AssetLoader for MeshletMeshLoader {
|
||||
type Asset = MeshletMesh;
|
||||
type Settings = ();
|
||||
type Error = MeshletMeshSaveOrLoadError;
|
||||
|
@ -147,18 +172,20 @@ impl AssetLoader for MeshletMeshSaverLoader {
|
|||
|
||||
// Load and decompress asset data
|
||||
let reader = &mut FrameDecoder::new(AsyncReadSyncAdapter(reader));
|
||||
let vertex_data = read_slice(reader)?;
|
||||
let vertex_ids = read_slice(reader)?;
|
||||
let vertex_positions = read_slice(reader)?;
|
||||
let vertex_normals = read_slice(reader)?;
|
||||
let vertex_uvs = read_slice(reader)?;
|
||||
let indices = read_slice(reader)?;
|
||||
let meshlets = read_slice(reader)?;
|
||||
let bounding_spheres = read_slice(reader)?;
|
||||
let meshlet_bounding_spheres = read_slice(reader)?;
|
||||
|
||||
Ok(MeshletMesh {
|
||||
vertex_data,
|
||||
vertex_ids,
|
||||
vertex_positions,
|
||||
vertex_normals,
|
||||
vertex_uvs,
|
||||
indices,
|
||||
meshlets,
|
||||
bounding_spheres,
|
||||
meshlet_bounding_spheres,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,62 @@
|
|||
use super::asset::{Meshlet, MeshletBoundingSphere, MeshletBoundingSpheres, MeshletMesh};
|
||||
use alloc::borrow::Cow;
|
||||
use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles};
|
||||
use bevy_render::{
|
||||
mesh::{Indices, Mesh},
|
||||
render_resource::PrimitiveTopology,
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
|
||||
use core::ops::Range;
|
||||
use itertools::Itertools;
|
||||
use meshopt::{
|
||||
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds, ffi::meshopt_Bounds, simplify,
|
||||
Meshlets, SimplifyOptions, VertexDataAdapter,
|
||||
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds,
|
||||
ffi::{meshopt_Bounds, meshopt_Meshlet},
|
||||
simplify, Meshlets, SimplifyOptions, VertexDataAdapter,
|
||||
};
|
||||
use metis::Graph;
|
||||
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 {
|
||||
/// Process a [`Mesh`] to generate a [`MeshletMesh`].
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// The input mesh must:
|
||||
/// 1. Use [`PrimitiveTopology::TriangleList`]
|
||||
/// 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)
|
||||
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
|
||||
let indices = validate_input_mesh(mesh)?;
|
||||
|
||||
|
@ -121,24 +152,32 @@ impl MeshletMesh {
|
|||
simplification_queue = next_lod_start..meshlets.len();
|
||||
}
|
||||
|
||||
// Convert meshopt_Meshlet data to a custom format
|
||||
let bevy_meshlets = meshlets
|
||||
.meshlets
|
||||
.into_iter()
|
||||
.map(|m| Meshlet {
|
||||
start_vertex_id: m.vertex_offset,
|
||||
start_index_id: m.triangle_offset,
|
||||
vertex_count: m.vertex_count,
|
||||
triangle_count: m.triangle_count,
|
||||
})
|
||||
.collect();
|
||||
// Copy vertex attributes per meshlet and compress
|
||||
let mut vertex_positions = BitVec::<u32, Lsb0>::new();
|
||||
let mut vertex_normals = Vec::new();
|
||||
let mut vertex_uvs = Vec::new();
|
||||
let mut bevy_meshlets = Vec::with_capacity(meshlets.len());
|
||||
for (i, meshlet) in meshlets.meshlets.iter().enumerate() {
|
||||
build_and_compress_meshlet_vertex_data(
|
||||
meshlet,
|
||||
meshlets.get(i).vertices,
|
||||
&vertex_buffer,
|
||||
&mut vertex_positions,
|
||||
&mut vertex_normals,
|
||||
&mut vertex_uvs,
|
||||
&mut bevy_meshlets,
|
||||
vertex_position_quantization_factor,
|
||||
);
|
||||
}
|
||||
vertex_positions.set_uninitialized(false);
|
||||
|
||||
Ok(Self {
|
||||
vertex_data: vertex_buffer.into(),
|
||||
vertex_ids: meshlets.vertices.into(),
|
||||
vertex_positions: vertex_positions.into_vec().into(),
|
||||
vertex_normals: vertex_normals.into(),
|
||||
vertex_uvs: vertex_uvs.into(),
|
||||
indices: meshlets.triangles.into(),
|
||||
meshlets: bevy_meshlets,
|
||||
bounding_spheres: bounding_spheres.into(),
|
||||
meshlets: bevy_meshlets.into(),
|
||||
meshlet_bounding_spheres: bounding_spheres.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -323,6 +362,92 @@ fn split_simplified_group_into_new_meshlets(
|
|||
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 {
|
||||
MeshletBoundingSphere {
|
||||
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`].
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MeshToMeshletMeshConversionError {
|
||||
|
|
|
@ -3,31 +3,25 @@
|
|||
#import bevy_pbr::mesh_types::Mesh
|
||||
#import bevy_render::view::View
|
||||
#import bevy_pbr::prepass_bindings::PreviousViewUniforms
|
||||
|
||||
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;
|
||||
}
|
||||
#import bevy_pbr::utils::octahedral_decode_signed
|
||||
|
||||
struct Meshlet {
|
||||
start_vertex_id: u32,
|
||||
start_vertex_position_bit: u32,
|
||||
start_vertex_attribute_id: u32,
|
||||
start_index_id: u32,
|
||||
vertex_count: u32,
|
||||
triangle_count: u32,
|
||||
packed_a: 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 {
|
||||
|
@ -54,6 +48,8 @@ struct DrawIndirectArgs {
|
|||
first_instance: u32,
|
||||
}
|
||||
|
||||
const CENTIMETERS_PER_METER = 100.0;
|
||||
|
||||
#ifdef MESHLET_FILL_CLUSTER_BUFFERS_PASS
|
||||
var<push_constant> cluster_count: u32;
|
||||
@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(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(3) var<storage, read> meshlet_vertex_ids: array<u32>; // Many per meshlet
|
||||
@group(0) @binding(4) var<storage, read> meshlet_vertex_data: array<PackedMeshletVertex>; // Many per meshlet
|
||||
@group(0) @binding(5) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
||||
@group(0) @binding(6) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
||||
@group(0) @binding(7) var<storage, read> meshlet_raster_clusters: array<u32>; // Single object shared between all workgroups/clusters/triangles
|
||||
@group(0) @binding(8) var<storage, read> meshlet_software_raster_cluster_count: u32;
|
||||
@group(0) @binding(3) var<storage, read> meshlet_vertex_positions: array<u32>; // 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_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_software_raster_cluster_count: u32;
|
||||
#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
|
||||
@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
|
||||
@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?
|
||||
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 bit_offset = (index_id % 4u) * 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
|
||||
|
||||
#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(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(4) var<storage, read> meshlet_vertex_ids: array<u32>; // Many per meshlet
|
||||
@group(1) @binding(5) var<storage, read> meshlet_vertex_data: array<PackedMeshletVertex>; // Many per meshlet
|
||||
@group(1) @binding(6) var<storage, read> meshlet_cluster_instance_ids: array<u32>; // Per cluster
|
||||
@group(1) @binding(7) var<storage, read> meshlet_instance_uniforms: array<Mesh>; // Per entity instance
|
||||
@group(1) @binding(4) var<storage, read> meshlet_vertex_positions: array<u32>; // 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_vertex_uvs: array<vec2<f32>>; // Many per meshlet
|
||||
@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?
|
||||
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 bit_offset = (index_id % 4u) * 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
|
||||
|
|
|
@ -9,6 +9,7 @@ use bevy_ecs::{
|
|||
system::{Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_render::{
|
||||
render_resource::BufferAddress,
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
|
@ -19,20 +20,22 @@ use core::ops::Range;
|
|||
/// Manages uploading [`MeshletMesh`] asset data to the GPU.
|
||||
#[derive(Resource)]
|
||||
pub struct MeshletMeshManager {
|
||||
pub vertex_data: PersistentGpuBuffer<Arc<[u8]>>,
|
||||
pub vertex_ids: PersistentGpuBuffer<Arc<[u32]>>,
|
||||
pub vertex_positions: PersistentGpuBuffer<Arc<[u32]>>,
|
||||
pub vertex_normals: PersistentGpuBuffer<Arc<[u32]>>,
|
||||
pub vertex_uvs: PersistentGpuBuffer<Arc<[Vec2]>>,
|
||||
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>; 5]>,
|
||||
meshlet_mesh_slices: HashMap<AssetId<MeshletMesh>, [Range<BufferAddress>; 6]>,
|
||||
}
|
||||
|
||||
impl FromWorld for MeshletMeshManager {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
Self {
|
||||
vertex_data: PersistentGpuBuffer::new("meshlet_vertex_data", render_device),
|
||||
vertex_ids: PersistentGpuBuffer::new("meshlet_vertex_ids", render_device),
|
||||
vertex_positions: PersistentGpuBuffer::new("meshlet_vertex_positions", 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),
|
||||
meshlets: PersistentGpuBuffer::new("meshlets", render_device),
|
||||
meshlet_bounding_spheres: PersistentGpuBuffer::new(
|
||||
|
@ -55,27 +58,34 @@ impl MeshletMeshManager {
|
|||
"MeshletMesh asset was already unloaded but is not registered with MeshletMeshManager",
|
||||
);
|
||||
|
||||
let vertex_data_slice = self
|
||||
.vertex_data
|
||||
.queue_write(Arc::clone(&meshlet_mesh.vertex_data), ());
|
||||
let vertex_ids_slice = self.vertex_ids.queue_write(
|
||||
Arc::clone(&meshlet_mesh.vertex_ids),
|
||||
vertex_data_slice.start,
|
||||
);
|
||||
let vertex_positions_slice = self
|
||||
.vertex_positions
|
||||
.queue_write(Arc::clone(&meshlet_mesh.vertex_positions), ());
|
||||
let vertex_normals_slice = self
|
||||
.vertex_normals
|
||||
.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
|
||||
.indices
|
||||
.queue_write(Arc::clone(&meshlet_mesh.indices), ());
|
||||
let meshlets_slice = self.meshlets.queue_write(
|
||||
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
|
||||
.meshlet_bounding_spheres
|
||||
.queue_write(Arc::clone(&meshlet_mesh.bounding_spheres), ());
|
||||
.queue_write(Arc::clone(&meshlet_mesh.meshlet_bounding_spheres), ());
|
||||
|
||||
[
|
||||
vertex_data_slice,
|
||||
vertex_ids_slice,
|
||||
vertex_positions_slice,
|
||||
vertex_normals_slice,
|
||||
vertex_uvs_slice,
|
||||
indices_slice,
|
||||
meshlets_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
|
||||
let [_, _, _, meshlets_slice, _] = self
|
||||
let [_, _, _, _, meshlets_slice, _] = self
|
||||
.meshlet_mesh_slices
|
||||
.entry(asset_id)
|
||||
.or_insert_with_key(queue_meshlet_mesh)
|
||||
|
@ -96,11 +106,13 @@ impl MeshletMeshManager {
|
|||
|
||||
pub fn remove(&mut self, asset_id: &AssetId<MeshletMesh>) {
|
||||
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.vertex_data.mark_slice_unused(vertex_data_slice);
|
||||
self.vertex_ids.mark_slice_unused(vertex_ids_slice);
|
||||
self.vertex_positions
|
||||
.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.meshlets.mark_slice_unused(meshlets_slice);
|
||||
self.meshlet_bounding_spheres
|
||||
|
@ -116,10 +128,13 @@ pub fn perform_pending_meshlet_mesh_writes(
|
|||
render_device: Res<RenderDevice>,
|
||||
) {
|
||||
meshlet_mesh_manager
|
||||
.vertex_data
|
||||
.vertex_positions
|
||||
.perform_writes(&render_queue, &render_device);
|
||||
meshlet_mesh_manager
|
||||
.vertex_ids
|
||||
.vertex_normals
|
||||
.perform_writes(&render_queue, &render_device);
|
||||
meshlet_mesh_manager
|
||||
.vertex_uvs
|
||||
.perform_writes(&render_queue, &render_device);
|
||||
meshlet_mesh_manager
|
||||
.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")]
|
||||
pub use self::from_mesh::MeshToMeshletMeshConversionError;
|
||||
pub use self::from_mesh::{
|
||||
MeshToMeshletMeshConversionError, DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
|
||||
};
|
||||
|
||||
use self::{
|
||||
graph::NodeMeshlet,
|
||||
|
@ -201,7 +203,7 @@ impl Plugin for MeshletPlugin {
|
|||
);
|
||||
|
||||
app.init_asset::<MeshletMesh>()
|
||||
.register_asset_loader(MeshletMeshSaverLoader)
|
||||
.register_asset_loader(MeshletMeshLoader)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
check_visibility::<WithMeshletMesh>.in_set(VisibilitySystems::CheckVisibility),
|
||||
|
|
|
@ -3,8 +3,7 @@ use super::{
|
|||
persistent_buffer::PersistentGpuBufferable,
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
|
||||
const MESHLET_VERTEX_SIZE_IN_BYTES: u32 = 32;
|
||||
use bevy_math::Vec2;
|
||||
|
||||
impl PersistentGpuBufferable for Arc<[u8]> {
|
||||
type Metadata = ();
|
||||
|
@ -19,26 +18,31 @@ impl PersistentGpuBufferable for Arc<[u8]> {
|
|||
}
|
||||
|
||||
impl PersistentGpuBufferable for Arc<[u32]> {
|
||||
type Metadata = u64;
|
||||
type Metadata = ();
|
||||
|
||||
fn size_in_bytes(&self) -> usize {
|
||||
self.len() * size_of::<u32>()
|
||||
}
|
||||
|
||||
fn write_bytes_le(&self, offset: Self::Metadata, buffer_slice: &mut [u8]) {
|
||||
let offset = offset as u32 / MESHLET_VERTEX_SIZE_IN_BYTES;
|
||||
fn write_bytes_le(&self, _: Self::Metadata, buffer_slice: &mut [u8]) {
|
||||
buffer_slice.clone_from_slice(bytemuck::cast_slice(self));
|
||||
}
|
||||
}
|
||||
|
||||
for (i, index) in self.iter().enumerate() {
|
||||
let size = size_of::<u32>();
|
||||
let i = i * size;
|
||||
let bytes = (*index + offset).to_le_bytes();
|
||||
buffer_slice[i..(i + size)].clone_from_slice(&bytes);
|
||||
}
|
||||
impl PersistentGpuBufferable for Arc<[Vec2]> {
|
||||
type Metadata = ();
|
||||
|
||||
fn size_in_bytes(&self) -> usize {
|
||||
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]> {
|
||||
type Metadata = (u64, u64);
|
||||
type Metadata = (u64, u64, u64);
|
||||
|
||||
fn size_in_bytes(&self) -> usize {
|
||||
self.len() * size_of::<Meshlet>()
|
||||
|
@ -46,20 +50,23 @@ impl PersistentGpuBufferable for Arc<[Meshlet]> {
|
|||
|
||||
fn write_bytes_le(
|
||||
&self,
|
||||
(vertex_offset, index_offset): Self::Metadata,
|
||||
(vertex_position_offset, vertex_attribute_offset, index_offset): Self::Metadata,
|
||||
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;
|
||||
|
||||
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_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,
|
||||
vertex_count: meshlet.vertex_count,
|
||||
triangle_count: meshlet.triangle_count,
|
||||
..*meshlet
|
||||
});
|
||||
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_sized(false, None),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -702,8 +702,7 @@ pub fn prepare_meshlet_view_bind_groups(
|
|||
cluster_meshlet_ids.as_entire_binding(),
|
||||
meshlet_mesh_manager.meshlets.binding(),
|
||||
meshlet_mesh_manager.indices.binding(),
|
||||
meshlet_mesh_manager.vertex_ids.binding(),
|
||||
meshlet_mesh_manager.vertex_data.binding(),
|
||||
meshlet_mesh_manager.vertex_positions.binding(),
|
||||
cluster_instance_ids.as_entire_binding(),
|
||||
instance_manager.instance_uniforms.binding().unwrap(),
|
||||
resource_manager
|
||||
|
@ -746,8 +745,9 @@ pub fn prepare_meshlet_view_bind_groups(
|
|||
cluster_meshlet_ids.as_entire_binding(),
|
||||
meshlet_mesh_manager.meshlets.binding(),
|
||||
meshlet_mesh_manager.indices.binding(),
|
||||
meshlet_mesh_manager.vertex_ids.binding(),
|
||||
meshlet_mesh_manager.vertex_data.binding(),
|
||||
meshlet_mesh_manager.vertex_positions.binding(),
|
||||
meshlet_mesh_manager.vertex_normals.binding(),
|
||||
meshlet_mesh_manager.vertex_uvs.binding(),
|
||||
cluster_instance_ids.as_entire_binding(),
|
||||
instance_manager.instance_uniforms.binding().unwrap(),
|
||||
));
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
meshlet_bindings::{
|
||||
meshlet_cluster_meshlet_ids,
|
||||
meshlets,
|
||||
meshlet_vertex_ids,
|
||||
meshlet_vertex_data,
|
||||
meshlet_cluster_instance_ids,
|
||||
meshlet_instance_uniforms,
|
||||
meshlet_raster_clusters,
|
||||
meshlet_visibility_buffer,
|
||||
view,
|
||||
get_meshlet_index,
|
||||
unpack_meshlet_vertex,
|
||||
get_meshlet_triangle_count,
|
||||
get_meshlet_vertex_id,
|
||||
get_meshlet_vertex_position,
|
||||
},
|
||||
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 {
|
||||
let cluster_id = meshlet_raster_clusters[meshlet_raster_cluster_rightmost_slot - instance_index];
|
||||
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;
|
||||
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 = get_meshlet_index(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 vertex_id = get_meshlet_vertex_id(meshlet.start_index_id + index_id);
|
||||
|
||||
let instance_id = meshlet_cluster_instance_ids[cluster_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_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);
|
||||
#ifdef DEPTH_CLAMP_ORTHO
|
||||
let unclamped_clip_depth = clip_position.z;
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
|
||||
#import bevy_pbr::{
|
||||
meshlet_bindings::{
|
||||
Meshlet,
|
||||
meshlet_visibility_buffer,
|
||||
meshlet_cluster_meshlet_ids,
|
||||
meshlets,
|
||||
meshlet_vertex_ids,
|
||||
meshlet_vertex_data,
|
||||
meshlet_cluster_instance_ids,
|
||||
meshlet_instance_uniforms,
|
||||
get_meshlet_index,
|
||||
unpack_meshlet_vertex,
|
||||
get_meshlet_vertex_id,
|
||||
get_meshlet_vertex_position,
|
||||
get_meshlet_vertex_normal,
|
||||
get_meshlet_vertex_uv,
|
||||
},
|
||||
mesh_view_bindings::view,
|
||||
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 cluster_id = packed_ids >> 7u;
|
||||
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 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(meshlet_vertex_ids[indices.x], meshlet_vertex_ids[indices.y], meshlet_vertex_ids[indices.z]);
|
||||
let vertex_1 = unpack_meshlet_vertex(meshlet_vertex_data[vertex_ids.x]);
|
||||
let vertex_2 = unpack_meshlet_vertex(meshlet_vertex_data[vertex_ids.y]);
|
||||
let vertex_3 = unpack_meshlet_vertex(meshlet_vertex_data[vertex_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_0 = load_vertex(&meshlet, vertex_ids[0]);
|
||||
let vertex_1 = load_vertex(&meshlet, vertex_ids[1]);
|
||||
let vertex_2 = load_vertex(&meshlet, vertex_ids[2]);
|
||||
|
||||
let instance_id = meshlet_cluster_instance_ids[cluster_id];
|
||||
var instance_uniform = meshlet_instance_uniforms[instance_id];
|
||||
|
||||
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_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 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,
|
||||
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(
|
||||
world_position_0.xyz - view.world_position,
|
||||
world_position_1.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 ddy_world_position = world_positions_camera_relative * partial_derivatives.ddy;
|
||||
|
||||
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_2.normal, &instance_uniform),
|
||||
normal_local_to_world(vertex_3.normal, &instance_uniform),
|
||||
) * partial_derivatives.barycentrics;
|
||||
|
||||
let uv = mat3x2(vertex_1.uv, vertex_2.uv, vertex_3.uv) * partial_derivatives.barycentrics;
|
||||
let ddx_uv = mat3x2(vertex_1.uv, vertex_2.uv, vertex_3.uv) * partial_derivatives.ddx;
|
||||
let ddy_uv = mat3x2(vertex_1.uv, vertex_2.uv, vertex_3.uv) * partial_derivatives.ddy;
|
||||
let uv = mat3x2(vertex_0.uv, vertex_1.uv, vertex_2.uv) * partial_derivatives.barycentrics;
|
||||
let ddx_uv = mat3x2(vertex_0.uv, vertex_1.uv, vertex_2.uv) * partial_derivatives.ddx;
|
||||
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);
|
||||
|
||||
#ifdef PREPASS_FRAGMENT
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
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_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_1, previous_world_position_2, previous_world_position_3) * partial_derivatives.barycentrics;
|
||||
let previous_world_position = mat3x4(previous_world_position_0, previous_world_position_1, previous_world_position_2) * partial_derivatives.barycentrics;
|
||||
let motion_vector = calculate_motion_vector(world_position, previous_world_position);
|
||||
#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> {
|
||||
if any(vertex_normal != vec3<f32>(0.0)) {
|
||||
return normalize(
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
meshlet_bindings::{
|
||||
meshlet_cluster_meshlet_ids,
|
||||
meshlets,
|
||||
meshlet_vertex_ids,
|
||||
meshlet_vertex_data,
|
||||
meshlet_cluster_instance_ids,
|
||||
meshlet_instance_uniforms,
|
||||
meshlet_raster_clusters,
|
||||
meshlet_software_raster_cluster_count,
|
||||
meshlet_visibility_buffer,
|
||||
view,
|
||||
get_meshlet_index,
|
||||
unpack_meshlet_vertex,
|
||||
get_meshlet_vertex_count,
|
||||
get_meshlet_triangle_count,
|
||||
get_meshlet_vertex_id,
|
||||
get_meshlet_vertex_position,
|
||||
},
|
||||
mesh_functions::mesh_position_local_to_world,
|
||||
view_transformations::ndc_to_uv,
|
||||
|
@ -42,7 +42,7 @@ fn rasterize_cluster(
|
|||
|
||||
let cluster_id = meshlet_raster_clusters[workgroup_id_1d];
|
||||
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_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
|
||||
for (var i = 0u; i <= 128u; i += 128u) {
|
||||
let vertex_id = local_invocation_index + i;
|
||||
if vertex_id < meshlet.vertex_count {
|
||||
let meshlet_vertex_id = meshlet_vertex_ids[meshlet.start_vertex_id + vertex_id];
|
||||
let vertex = unpack_meshlet_vertex(meshlet_vertex_data[meshlet_vertex_id]);
|
||||
if vertex_id < get_meshlet_vertex_count(&meshlet) {
|
||||
let vertex_position = get_meshlet_vertex_position(&meshlet, vertex_id);
|
||||
|
||||
// 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);
|
||||
var ndc_position = clip_position.xyz / clip_position.w;
|
||||
#ifdef DEPTH_CLAMP_ORTHO
|
||||
|
@ -72,9 +71,9 @@ fn rasterize_cluster(
|
|||
|
||||
// Load 1 triangle's worth of vertex data per thread
|
||||
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 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_1 = viewport_vertices[vertex_ids[1]];
|
||||
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.
|
||||
fn octahedral_decode(v: vec2<f32>) -> vec3<f32> {
|
||||
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 w = select(vec2(t), vec2(-t), n.xy >= vec2(0.0));
|
||||
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};
|
||||
|
||||
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 {
|
||||
if !Path::new("./assets/models/bunny.meshlet_mesh").exists() {
|
||||
|
|
Loading…
Reference in a new issue