Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meshlet screenspace-derived tangents #15084

Merged
merged 29 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f565cd5
WIP
JMS55 Sep 2, 2024
559535e
Fix bug from previous PR
JMS55 Sep 2, 2024
0866f4a
Fix broken software raster vertex cache
JMS55 Sep 3, 2024
852923e
Fix comment typo
JMS55 Sep 3, 2024
e0180b9
Add note about 255 vertex limit
JMS55 Sep 3, 2024
8bc76c6
Uncommit bunny
JMS55 Sep 3, 2024
ffb3d3a
Change 65 -> 95% simplification threshold
JMS55 Sep 5, 2024
e9f3029
Add TODO
JMS55 Sep 5, 2024
ab4c870
Add another TODO
JMS55 Sep 5, 2024
05b2ad3
Remove explicit size_of() imports
JMS55 Sep 7, 2024
cb1edd8
Screen-spaced derived tangents
JMS55 Sep 7, 2024
696e23f
Remove explicit vertex tangents
JMS55 Sep 7, 2024
31e6a61
Update bunny asset URL
JMS55 Sep 7, 2024
db2260a
Revert example back to normal
JMS55 Sep 7, 2024
d27a1e8
Add note on normal maps
JMS55 Sep 7, 2024
73dac63
Fix clippy lint
JMS55 Sep 7, 2024
1a6d28e
Merge commit 'a0faf9cd01750cb8eea243bdf7bb1dd123d73f2c' into meshlet-…
JMS55 Sep 8, 2024
0a61126
Flip flipped ddy in visbuffer resolve
JMS55 Sep 10, 2024
367f2c2
Minor visbuffer resolve refactor
JMS55 Sep 10, 2024
9a8d184
Paper-based impl
JMS55 Sep 13, 2024
e5838ac
Idk just negate it ig
JMS55 Sep 13, 2024
aea5869
Merge branch 'meshlet-implicit-tangents' of https://github.com/JMS55/…
JMS55 Sep 13, 2024
d758b0d
Fix merge
JMS55 Sep 13, 2024
c919802
Merge branch 'main' into meshlet-implicit-tangents
JMS55 Sep 13, 2024
5a7df13
Merge branch 'main' into meshlet-implicit-tangents
JMS55 Sep 14, 2024
545b550
Merge commit 'eb92ba8815c2532fd8a67bb76113366a8eca9584' into meshlet-…
JMS55 Sep 25, 2024
d526bcb
Merge commit 'a0c722ff4ca3f95b44eddee5098b3bdb86e63812' into meshlet-…
JMS55 Sep 27, 2024
baabc52
Update crates/bevy_pbr/src/meshlet/asset.rs
JMS55 Sep 29, 2024
88696f5
Merge commit '8316d8969973259d342a930eed7d805ec6dcc780' into meshlet-…
JMS55 Sep 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ setup = [
"curl",
"-o",
"assets/models/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/b6c712cfc87c65de419f856845401aba336a7bcd/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/854eb98353ad94aea1104f355fc24dbe4fda679d/bunny.meshlet_mesh",
],
]

Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
/// the default meshlet mesh fragment shader will be used.
///
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
///
/// See [`crate::meshlet::MeshletMesh`] for limitations.
#[allow(unused_variables)]
#[cfg(feature = "meshlet")]
fn meshlet_mesh_fragment_shader() -> ShaderRef {
Expand All @@ -189,6 +191,8 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
/// the default meshlet mesh prepass fragment shader will be used.
///
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
///
/// See [`crate::meshlet::MeshletMesh`] for limitations.
#[allow(unused_variables)]
#[cfg(feature = "meshlet")]
fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
Expand All @@ -199,6 +203,8 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
/// the default meshlet mesh deferred fragment shader will be used.
///
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
///
/// See [`crate::meshlet::MeshletMesh`] for limitations.
#[allow(unused_variables)]
#[cfg(feature = "meshlet")]
fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_pbr/src/meshlet/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ pub const MESHLET_MESH_ASSET_VERSION: u64 = 1;
/// There are restrictions on the [`crate::Material`] functionality that can be used with this type of mesh.
/// * Materials have no control over the vertex shader or vertex attributes.
/// * Materials must be opaque. Transparent, alpha masked, and transmissive materials are not supported.
/// * Do not use normal maps baked from higher-poly geometry. Use the high-poly geometry directly and skip the normal map.
/// * If additional detail is needed, a smaller tiling normal map not baked from a mesh is ok.
/// * Material shaders must not use builtin functions that automatically calculate derivatives <https://gpuweb.github.io/gpuweb/wgsl/#derivatives>.
/// * Use `pbr_functions::sample_texture` to sample textures instead.
/// * Performing manual arithmetic on UV coordinates is forbidden. Use the chain-rule version of arithmetic functions instead (TODO: not yet implemented).
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
/// * Limited control over [`bevy_render::render_resource::RenderPipelineDescriptor`] attributes.
/// * Materials must use the [`crate::Material::meshlet_mesh_fragment_shader`] method (and similar variants for prepass/deferred shaders)
/// which requires certain shader patterns that differ from the regular material shaders.
/// * Limited control over [`bevy_render::render_resource::RenderPipelineDescriptor`] attributes.
///
/// See also [`super::MaterialMeshletMeshBundle`] and [`super::MeshletPlugin`].
#[derive(Asset, TypePath, Clone)]
Expand Down
50 changes: 17 additions & 33 deletions crates/bevy_pbr/src/meshlet/from_mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use bevy_utils::HashMap;
use itertools::Itertools;
use meshopt::{
build_meshlets, compute_cluster_bounds, compute_meshlet_bounds, ffi::meshopt_Bounds, simplify,
simplify_scale, Meshlets, SimplifyOptions, VertexDataAdapter,
Meshlets, SimplifyOptions, VertexDataAdapter,
};
use metis::Graph;
use smallvec::SmallVec;
Expand All @@ -23,7 +23,7 @@ impl MeshletMesh {
/// The input mesh must:
/// 1. Use [`PrimitiveTopology::TriangleList`]
/// 2. Use indices
/// 3. Have the exact following set of vertex attributes: `{POSITION, NORMAL, UV_0, TANGENT}`
/// 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> {
// Validate mesh format
let indices = validate_input_mesh(mesh)?;
Expand All @@ -49,11 +49,9 @@ impl MeshletMesh {
},
})
.collect::<Vec<_>>();
let mesh_scale = simplify_scale(&vertices);

// Build further LODs
let mut simplification_queue = 0..meshlets.len();
let mut lod_level = 1;
while simplification_queue.len() > 1 {
// For each meshlet build a list of connected meshlets (meshlets that share a triangle edge)
let connected_meshlets_per_meshlet =
Expand All @@ -70,19 +68,14 @@ impl MeshletMesh {

for group_meshlets in groups.into_iter().filter(|group| group.len() > 1) {
// Simplify the group to ~50% triangle count
let Some((simplified_group_indices, mut group_error)) = simplify_meshlet_groups(
&group_meshlets,
&meshlets,
&vertices,
lod_level,
mesh_scale,
) else {
let Some((simplified_group_indices, mut group_error)) =
simplify_meshlet_group(&group_meshlets, &meshlets, &vertices)
else {
continue;
};

// Add the maximum child error to the parent error to make parent error cumulative from LOD 0
// (we're currently building the parent from its children)
group_error += group_meshlets.iter().fold(0.0f32, |acc, meshlet_id| {
// Force parent error to be >= child error (we're currently building the parent from its children)
group_error = group_meshlets.iter().fold(group_error, |acc, meshlet_id| {
acc.max(bounding_spheres[*meshlet_id].self_lod.radius)
});

Expand All @@ -99,7 +92,7 @@ impl MeshletMesh {
}

// Build new meshlets using the simplified group
let new_meshlets_count = split_simplified_groups_into_new_meshlets(
let new_meshlets_count = split_simplified_group_into_new_meshlets(
&simplified_group_indices,
&vertices,
&mut meshlets,
Expand All @@ -125,7 +118,6 @@ impl MeshletMesh {
}

simplification_queue = next_lod_start..meshlets.len();
lod_level += 1;
}

// Convert meshopt_Meshlet data to a custom format
Expand Down Expand Up @@ -159,7 +151,6 @@ fn validate_input_mesh(mesh: &Mesh) -> Result<Cow<'_, [u32]>, MeshToMeshletMeshC
Mesh::ATTRIBUTE_POSITION.id,
Mesh::ATTRIBUTE_NORMAL.id,
Mesh::ATTRIBUTE_UV_0.id,
Mesh::ATTRIBUTE_TANGENT.id,
]) {
return Err(MeshToMeshletMeshConversionError::WrongMeshVertexAttributes);
}
Expand All @@ -172,7 +163,7 @@ fn validate_input_mesh(mesh: &Mesh) -> Result<Cow<'_, [u32]>, MeshToMeshletMeshC
}

fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets {
build_meshlets(indices, vertices, 64, 64, 0.0)
build_meshlets(indices, vertices, 255, 128, 0.0) // Meshoptimizer won't currently let us do 256 vertices
}

fn find_connected_meshlets(
Expand Down Expand Up @@ -252,7 +243,7 @@ fn group_meshlets(
xadj.push(adjncy.len() as i32);

let mut group_per_meshlet = vec![0; simplification_queue.len()];
let partition_count = simplification_queue.len().div_ceil(4);
let partition_count = simplification_queue.len().div_ceil(4); // TODO: Nanite uses groups of 8-32, probably based on some kind of heuristic
Graph::new(1, partition_count as i32, &xadj, &adjncy)
.unwrap()
.set_adjwgt(&adjwgt)
Expand All @@ -267,12 +258,10 @@ fn group_meshlets(
groups
}

fn simplify_meshlet_groups(
fn simplify_meshlet_group(
group_meshlets: &[usize],
meshlets: &Meshlets,
vertices: &VertexDataAdapter<'_>,
lod_level: u32,
mesh_scale: f32,
) -> Option<(Vec<u32>, f32)> {
// Build a new index buffer into the mesh vertex data by combining all meshlet data in the group
let mut group_indices = Vec::new();
Expand All @@ -283,25 +272,20 @@ fn simplify_meshlet_groups(
}
}

// Allow more deformation for high LOD levels (1% at LOD 1, 10% at LOD 20+)
let t = (lod_level - 1) as f32 / 19.0;
let target_error_relative = 0.1 * t + 0.01 * (1.0 - t);
let target_error = target_error_relative * mesh_scale;

// Simplify the group to ~50% triangle count
// TODO: Simplify using vertex attributes
let mut error = 0.0;
let simplified_group_indices = simplify(
&group_indices,
vertices,
group_indices.len() / 2,
target_error,
SimplifyOptions::LockBorder | SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute,
f32::MAX,
SimplifyOptions::LockBorder | SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute, // TODO: Specify manual vertex locks instead of meshopt's overly-strict locks
Some(&mut error),
);

// Check if we were able to simplify to at least 65% triangle count
if simplified_group_indices.len() as f32 / group_indices.len() as f32 > 0.65 {
// Check if we were able to simplify at least a little (95% of the original triangle count)
if simplified_group_indices.len() as f32 / group_indices.len() as f32 > 0.95 {
return None;
}

Expand All @@ -311,7 +295,7 @@ fn simplify_meshlet_groups(
Some((simplified_group_indices, error))
}

fn split_simplified_groups_into_new_meshlets(
fn split_simplified_group_into_new_meshlets(
simplified_group_indices: &[u32],
vertices: &VertexDataAdapter<'_>,
meshlets: &mut Meshlets,
Expand Down Expand Up @@ -350,7 +334,7 @@ fn convert_meshlet_bounds(bounds: meshopt_Bounds) -> MeshletBoundingSphere {
pub enum MeshToMeshletMeshConversionError {
#[error("Mesh primitive topology is not TriangleList")]
WrongMeshPrimitiveTopology,
#[error("Mesh attributes are not {{POSITION, NORMAL, UV_0, TANGENT}}")]
#[error("Mesh attributes are not {{POSITION, NORMAL, UV_0}}")]
WrongMeshVertexAttributes,
#[error("Mesh has no indices")]
MeshMissingIndices,
Expand Down
4 changes: 0 additions & 4 deletions crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,19 @@
struct PackedMeshletVertex {
a: vec4<f32>,
b: vec4<f32>,
tangent: vec4<f32>,
}

// TODO: Octahedral encode normal, remove tangent and derive from UV derivatives
struct MeshletVertex {
position: vec3<f32>,
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
normal: vec3<f32>,
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
uv: vec2<f32>,
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
tangent: vec4<f32>,
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
}

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;
vertex.tangent = packed.tangent;
return vertex;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bevy_render::{
renderer::{RenderDevice, RenderQueue},
};
use bevy_utils::HashMap;
use std::{mem::size_of, ops::Range, sync::Arc};
use std::{ops::Range, sync::Arc};

/// Manages uploading [`MeshletMesh`] asset data to the GPU.
#[derive(Resource)]
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use super::{
asset::{Meshlet, MeshletBoundingSpheres},
persistent_buffer::PersistentGpuBufferable,
};
use std::{mem::size_of, sync::Arc};
use std::sync::Arc;

const MESHLET_VERTEX_SIZE_IN_BYTES: u32 = 48;
const MESHLET_VERTEX_SIZE_IN_BYTES: u32 = 32;

impl PersistentGpuBufferable for Arc<[u8]> {
type Metadata = ();
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/meshlet/resolve_render_targets.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn resolve_material_depth(in: FullscreenVertexOutput) -> @builtin(frag_depth) f3
let depth = visibility >> 32u;
if depth == 0lu { return 0.0; }

let cluster_id = u32(visibility) >> 6u;
let cluster_id = u32(visibility) >> 7u;
let instance_id = meshlet_cluster_instance_ids[cluster_id];
let material_id = meshlet_instance_material_ids[instance_id];
return f32(material_id) / 65535.0;
Expand Down
7 changes: 3 additions & 4 deletions crates/bevy_pbr/src/meshlet/resource_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use binding_types::*;
use encase::internal::WriteInto;
use std::{
array, iter,
mem::size_of,
sync::{atomic::AtomicBool, Arc},
};

Expand Down Expand Up @@ -63,7 +62,7 @@ pub struct ResourceManager {
impl ResourceManager {
pub fn new(cluster_buffer_slots: u32, render_device: &RenderDevice) -> Self {
let needs_dispatch_remap =
cluster_buffer_slots < render_device.limits().max_compute_workgroups_per_dimension;
cluster_buffer_slots > render_device.limits().max_compute_workgroups_per_dimension;

Self {
visibility_buffer_raster_clusters: render_device.create_buffer(&BufferDescriptor {
Expand Down Expand Up @@ -472,7 +471,7 @@ pub fn prepare_meshlet_per_frame_resources(
.create_buffer_with_data(&BufferInitDescriptor {
label: Some("meshlet_visibility_buffer_hardware_raster_indirect_args_first"),
contents: DrawIndirectArgs {
vertex_count: 64 * 3,
vertex_count: 128 * 3,
instance_count: 0,
first_vertex: 0,
first_instance: 0,
Expand All @@ -484,7 +483,7 @@ pub fn prepare_meshlet_per_frame_resources(
.create_buffer_with_data(&BufferInitDescriptor {
label: Some("visibility_buffer_hardware_raster_indirect_args_second"),
contents: DrawIndirectArgs {
vertex_count: 64 * 3,
vertex_count: 128 * 3,
instance_count: 0,
first_vertex: 0,
first_instance: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn vertex(@builtin(instance_index) instance_index: u32, @builtin(vertex_index) v
return VertexOutput(
clip_position,
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
(cluster_id << 6u) | triangle_id,
(cluster_id << 7u) | triangle_id,
#endif
#ifdef DEPTH_CLAMP_ORTHO
unclamped_clip_depth,
Expand All @@ -83,7 +83,7 @@ fn fragment(vertex_output: VertexOutput) {

fn dummy_vertex() -> VertexOutput {
return VertexOutput(
vec4(0.0),
vec4(divide(0.0, 0.0)), // NaN vertex position
#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT
0u,
#endif
Expand All @@ -92,3 +92,8 @@ fn dummy_vertex() -> VertexOutput {
#endif
);
}

// Naga doesn't allow divide by zero literals, but this lets us work around it
fn divide(a: f32, b: f32) -> f32 {
return a / b;
}
Loading