From 56c70f8463e32d2e69c531b2238f1ac8fe9d1dbd Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 4 Dec 2024 09:34:36 -0800 Subject: [PATCH] Make visibility range (HLOD) dithering work when prepasses are enabled. (#16286) Currently, the prepass has no support for visibility ranges, so artifacts appear when using dithering visibility ranges in conjunction with a prepass. This patch fixes that problem. Note that this patch changes the prepass to use sparse bind group indices instead of sequential ones. I figured this is cleaner, because it allows for greater sharing of WGSL code between the forward pipeline and the prepass pipeline. The `visibility_range` example has been updated to allow the prepass to be toggled on and off. --- crates/bevy_pbr/src/prepass/mod.rs | 71 +++++++++++++++---- crates/bevy_pbr/src/prepass/prepass.wgsl | 9 ++- crates/bevy_pbr/src/prepass/prepass_io.wgsl | 4 ++ crates/bevy_pbr/src/render/mesh.wgsl | 6 +- .../bevy_pbr/src/render/mesh_view_bindings.rs | 2 +- crates/bevy_pbr/src/render/pbr_prepass.wgsl | 11 ++- examples/3d/visibility_range.rs | 36 +++++++++- 7 files changed, 118 insertions(+), 21 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 5d99e1927e..05cbc9cda9 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -5,6 +5,7 @@ use bevy_render::{ mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_resource::binding_types::uniform_buffer, sync_world::RenderEntity, + view::{RenderVisibilityRanges, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT}, }; pub use prepass_bindings::*; @@ -19,7 +20,7 @@ use bevy_ecs::{ SystemParamItem, }, }; -use bevy_math::Affine3A; +use bevy_math::{Affine3A, Vec4}; use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, prelude::{Camera, Mesh}, @@ -261,30 +262,53 @@ impl FromWorld for PrepassPipeline { let render_device = world.resource::(); let asset_server = world.resource::(); + let visibility_ranges_buffer_binding_type = render_device + .get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); + let view_layout_motion_vectors = render_device.create_bind_group_layout( "prepass_view_layout_motion_vectors", - &BindGroupLayoutEntries::sequential( + &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, ( // View - uniform_buffer::(true), + (0, uniform_buffer::(true)), // Globals - uniform_buffer::(false), + (1, uniform_buffer::(false)), // PreviousViewUniforms - uniform_buffer::(true), + (2, uniform_buffer::(true)), + // VisibilityRanges + ( + 14, + buffer_layout( + visibility_ranges_buffer_binding_type, + false, + Some(Vec4::min_size()), + ) + .visibility(ShaderStages::VERTEX), + ), ), ), ); let view_layout_no_motion_vectors = render_device.create_bind_group_layout( "prepass_view_layout_no_motion_vectors", - &BindGroupLayoutEntries::sequential( + &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, ( // View - uniform_buffer::(true), + (0, uniform_buffer::(true)), // Globals - uniform_buffer::(false), + (1, uniform_buffer::(false)), + // VisibilityRanges + ( + 14, + buffer_layout( + visibility_ranges_buffer_binding_type, + false, + Some(Vec4::min_size()), + ) + .visibility(ShaderStages::VERTEX), + ), ), ), ); @@ -470,6 +494,13 @@ where shader_defs.push("HAS_PREVIOUS_MORPH".into()); } + if key + .mesh_key + .contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) + { + shader_defs.push("VISIBILITY_RANGE_DITHER".into()); + } + if key.mesh_key.intersects( MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS @@ -666,26 +697,33 @@ pub fn prepare_prepass_view_bind_group( view_uniforms: Res, globals_buffer: Res, previous_view_uniforms: Res, + visibility_ranges: Res, mut prepass_view_bind_group: ResMut, ) { - if let (Some(view_binding), Some(globals_binding)) = ( + if let (Some(view_binding), Some(globals_binding), Some(visibility_ranges_buffer)) = ( view_uniforms.uniforms.binding(), globals_buffer.buffer.binding(), + visibility_ranges.buffer().buffer(), ) { prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group( "prepass_view_no_motion_vectors_bind_group", &prepass_pipeline.view_layout_no_motion_vectors, - &BindGroupEntries::sequential((view_binding.clone(), globals_binding.clone())), + &BindGroupEntries::with_indices(( + (0, view_binding.clone()), + (1, globals_binding.clone()), + (14, visibility_ranges_buffer.as_entire_binding()), + )), )); if let Some(previous_view_uniforms_binding) = previous_view_uniforms.uniforms.binding() { prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group( "prepass_view_motion_vectors_bind_group", &prepass_pipeline.view_layout_motion_vectors, - &BindGroupEntries::sequential(( - view_binding, - globals_binding, - previous_view_uniforms_binding, + &BindGroupEntries::with_indices(( + (0, view_binding), + (1, globals_binding), + (2, previous_view_uniforms_binding), + (14, visibility_ranges_buffer.as_entire_binding()), )), )); } @@ -713,6 +751,7 @@ pub fn queue_prepass_material_meshes( render_materials: Res>>, render_material_instances: Res>, render_lightmaps: Res, + render_visibility_ranges: Res, material_bind_group_allocator: Res>, mut opaque_prepass_render_phases: ResMut>, mut alpha_mask_prepass_render_phases: ResMut>, @@ -854,6 +893,10 @@ pub fn queue_prepass_material_meshes( mesh_key |= MeshPipelineKey::LIGHTMAPPED; } + if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { + mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; + } + // If the previous frame has skins or morph targets, note that. if motion_vector_prepass.is_some() { if mesh_instance diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 725afb8d40..8f7d45c2fd 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -66,12 +66,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { var vertex = vertex_no_morph; #endif + let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); + #ifdef SKINNED var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 - var world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); + var world_from_local = mesh_world_from_local; #endif // SKINNED out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); @@ -161,6 +163,11 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.instance_index = vertex_no_morph.instance_index; #endif +#ifdef VISIBILITY_RANGE_DITHER + out.visibility_range_dither = mesh_functions::get_visibility_range_dither_level( + vertex_no_morph.instance_index, mesh_world_from_local[3]); +#endif // VISIBILITY_RANGE_DITHER + return out; } diff --git a/crates/bevy_pbr/src/prepass/prepass_io.wgsl b/crates/bevy_pbr/src/prepass/prepass_io.wgsl index 4a8e465752..5f7d8ec071 100644 --- a/crates/bevy_pbr/src/prepass/prepass_io.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_io.wgsl @@ -70,6 +70,10 @@ struct VertexOutput { #ifdef VERTEX_COLORS @location(8) color: vec4, #endif + +#ifdef VISIBILITY_RANGE_DITHER + @location(9) @interpolate(flat) visibility_range_dither: i32, +#endif // VISIBILITY_RANGE_DITHER } #ifdef PREPASS_FRAGMENT diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 7d617755ad..3971a53902 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -41,12 +41,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { var vertex = vertex_no_morph; #endif + let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); + #ifdef SKINNED var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 . - var world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); + var world_from_local = mesh_world_from_local; #endif #ifdef VERTEX_NORMALS @@ -96,7 +98,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef VISIBILITY_RANGE_DITHER out.visibility_range_dither = mesh_functions::get_visibility_range_dither_level( - vertex_no_morph.instance_index, world_from_local[3]); + vertex_no_morph.instance_index, mesh_world_from_local[3]); #endif return out; diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index c3a723164e..8067680eff 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -172,7 +172,7 @@ impl From> for MeshPipelineViewLayoutKey { } } -fn buffer_layout( +pub(crate) fn buffer_layout( buffer_binding_type: BufferBindingType, has_dynamic_offset: bool, min_binding_size: Option>, diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index 17f62409a0..a2758ce698 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -26,9 +26,16 @@ fn fragment( #ifdef MESHLET_MESH_MATERIAL_PASS let in = resolve_vertex_output(frag_coord); let is_front = true; -#else +#else // MESHLET_MESH_MATERIAL_PASS + + // If we're in the crossfade section of a visibility range, conditionally + // discard the fragment according to the visibility pattern. +#ifdef VISIBILITY_RANGE_DITHER + pbr_functions::visibility_range_dither(in.position, in.visibility_range_dither); +#endif // VISIBILITY_RANGE_DITHER + pbr_prepass_functions::prepass_alpha_discard(in); -#endif +#endif // MESHLET_MESH_MATERIAL_PASS var out: prepass_io::FragmentOutput; diff --git a/examples/3d/visibility_range.rs b/examples/3d/visibility_range.rs index 6fdbdf13a9..17bc26439b 100644 --- a/examples/3d/visibility_range.rs +++ b/examples/3d/visibility_range.rs @@ -3,6 +3,7 @@ use std::f32::consts::PI; use bevy::{ + core_pipeline::prepass::{DepthPrepass, NormalPrepass}, input::mouse::MouseWheel, math::vec3, pbr::{light_consts::lux::FULL_DAYLIGHT, CascadeShadowConfigBuilder}, @@ -63,6 +64,8 @@ enum MainModel { struct AppStatus { // Whether to show only one model. show_one_model_only: Option, + // Whether to enable the prepass. + prepass: bool, } // Sets up the app. @@ -84,6 +87,7 @@ fn main() { set_visibility_ranges, update_help_text, update_mode, + toggle_prepass, ), ) .run(); @@ -288,6 +292,34 @@ fn update_mode( } } +// Toggles the prepass if the user requests. +fn toggle_prepass( + mut commands: Commands, + cameras: Query>, + keyboard_input: Res>, + mut app_status: ResMut, +) { + if !keyboard_input.just_pressed(KeyCode::Space) { + return; + } + + app_status.prepass = !app_status.prepass; + + for camera in cameras.iter() { + if app_status.prepass { + commands + .entity(camera) + .insert(DepthPrepass) + .insert(NormalPrepass); + } else { + commands + .entity(camera) + .remove::() + .remove::(); + } + } +} + // A system that updates the help text. fn update_help_text(mut text_query: Query<&mut Text>, app_status: Res) { for mut text in text_query.iter_mut() { @@ -304,7 +336,8 @@ impl AppStatus { {} (2) Show only the high-poly model {} (3) Show only the low-poly model Press 1, 2, or 3 to switch which model is shown -Press WASD or use the mouse wheel to move the camera", +Press WASD or use the mouse wheel to move the camera +Press Space to {} the prepass", if self.show_one_model_only.is_none() { '>' } else { @@ -320,6 +353,7 @@ Press WASD or use the mouse wheel to move the camera", } else { ' ' }, + if self.prepass { "disable" } else { "enable" } ) .into() }