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.
This commit is contained in:
Patrick Walton 2024-12-04 09:34:36 -08:00 committed by GitHub
parent c9fa975977
commit 56c70f8463
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 118 additions and 21 deletions

View file

@ -5,6 +5,7 @@ use bevy_render::{
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
render_resource::binding_types::uniform_buffer, render_resource::binding_types::uniform_buffer,
sync_world::RenderEntity, sync_world::RenderEntity,
view::{RenderVisibilityRanges, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT},
}; };
pub use prepass_bindings::*; pub use prepass_bindings::*;
@ -19,7 +20,7 @@ use bevy_ecs::{
SystemParamItem, SystemParamItem,
}, },
}; };
use bevy_math::Affine3A; use bevy_math::{Affine3A, Vec4};
use bevy_render::{ use bevy_render::{
globals::{GlobalsBuffer, GlobalsUniform}, globals::{GlobalsBuffer, GlobalsUniform},
prelude::{Camera, Mesh}, prelude::{Camera, Mesh},
@ -261,30 +262,53 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
let render_device = world.resource::<RenderDevice>(); let render_device = world.resource::<RenderDevice>();
let asset_server = world.resource::<AssetServer>(); let asset_server = world.resource::<AssetServer>();
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( let view_layout_motion_vectors = render_device.create_bind_group_layout(
"prepass_view_layout_motion_vectors", "prepass_view_layout_motion_vectors",
&BindGroupLayoutEntries::sequential( &BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX_FRAGMENT, ShaderStages::VERTEX_FRAGMENT,
( (
// View // View
uniform_buffer::<ViewUniform>(true), (0, uniform_buffer::<ViewUniform>(true)),
// Globals // Globals
uniform_buffer::<GlobalsUniform>(false), (1, uniform_buffer::<GlobalsUniform>(false)),
// PreviousViewUniforms // PreviousViewUniforms
uniform_buffer::<PreviousViewData>(true), (2, uniform_buffer::<PreviousViewData>(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( let view_layout_no_motion_vectors = render_device.create_bind_group_layout(
"prepass_view_layout_no_motion_vectors", "prepass_view_layout_no_motion_vectors",
&BindGroupLayoutEntries::sequential( &BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX_FRAGMENT, ShaderStages::VERTEX_FRAGMENT,
( (
// View // View
uniform_buffer::<ViewUniform>(true), (0, uniform_buffer::<ViewUniform>(true)),
// Globals // Globals
uniform_buffer::<GlobalsUniform>(false), (1, uniform_buffer::<GlobalsUniform>(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()); 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( if key.mesh_key.intersects(
MeshPipelineKey::NORMAL_PREPASS MeshPipelineKey::NORMAL_PREPASS
| MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS
@ -666,26 +697,33 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
view_uniforms: Res<ViewUniforms>, view_uniforms: Res<ViewUniforms>,
globals_buffer: Res<GlobalsBuffer>, globals_buffer: Res<GlobalsBuffer>,
previous_view_uniforms: Res<PreviousViewUniforms>, previous_view_uniforms: Res<PreviousViewUniforms>,
visibility_ranges: Res<RenderVisibilityRanges>,
mut prepass_view_bind_group: ResMut<PrepassViewBindGroup>, mut prepass_view_bind_group: ResMut<PrepassViewBindGroup>,
) { ) {
if let (Some(view_binding), Some(globals_binding)) = ( if let (Some(view_binding), Some(globals_binding), Some(visibility_ranges_buffer)) = (
view_uniforms.uniforms.binding(), view_uniforms.uniforms.binding(),
globals_buffer.buffer.binding(), globals_buffer.buffer.binding(),
visibility_ranges.buffer().buffer(),
) { ) {
prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group( prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group(
"prepass_view_no_motion_vectors_bind_group", "prepass_view_no_motion_vectors_bind_group",
&prepass_pipeline.view_layout_no_motion_vectors, &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() { 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_bind_group.motion_vectors = Some(render_device.create_bind_group(
"prepass_view_motion_vectors_bind_group", "prepass_view_motion_vectors_bind_group",
&prepass_pipeline.view_layout_motion_vectors, &prepass_pipeline.view_layout_motion_vectors,
&BindGroupEntries::sequential(( &BindGroupEntries::with_indices((
view_binding, (0, view_binding),
globals_binding, (1, globals_binding),
previous_view_uniforms_binding, (2, previous_view_uniforms_binding),
(14, visibility_ranges_buffer.as_entire_binding()),
)), )),
)); ));
} }
@ -713,6 +751,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
render_materials: Res<RenderAssets<PreparedMaterial<M>>>, render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_material_instances: Res<RenderMaterialInstances<M>>, render_material_instances: Res<RenderMaterialInstances<M>>,
render_lightmaps: Res<RenderLightmaps>, render_lightmaps: Res<RenderLightmaps>,
render_visibility_ranges: Res<RenderVisibilityRanges>,
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>, material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>, mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>, mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
@ -854,6 +893,10 @@ pub fn queue_prepass_material_meshes<M: Material>(
mesh_key |= MeshPipelineKey::LIGHTMAPPED; 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 the previous frame has skins or morph targets, note that.
if motion_vector_prepass.is_some() { if motion_vector_prepass.is_some() {
if mesh_instance if mesh_instance

View file

@ -66,12 +66,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
var vertex = vertex_no_morph; var vertex = vertex_no_morph;
#endif #endif
let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index);
#ifdef SKINNED #ifdef SKINNED
var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
#else // SKINNED #else // SKINNED
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // 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 // 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 #endif // SKINNED
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(vertex.position, 1.0)); out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(vertex.position, 1.0));
@ -161,6 +163,11 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
out.instance_index = vertex_no_morph.instance_index; out.instance_index = vertex_no_morph.instance_index;
#endif #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; return out;
} }

View file

@ -70,6 +70,10 @@ struct VertexOutput {
#ifdef VERTEX_COLORS #ifdef VERTEX_COLORS
@location(8) color: vec4<f32>, @location(8) color: vec4<f32>,
#endif #endif
#ifdef VISIBILITY_RANGE_DITHER
@location(9) @interpolate(flat) visibility_range_dither: i32,
#endif // VISIBILITY_RANGE_DITHER
} }
#ifdef PREPASS_FRAGMENT #ifdef PREPASS_FRAGMENT

View file

@ -41,12 +41,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
var vertex = vertex_no_morph; var vertex = vertex_no_morph;
#endif #endif
let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index);
#ifdef SKINNED #ifdef SKINNED
var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
#else #else
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // 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 . // 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 #endif
#ifdef VERTEX_NORMALS #ifdef VERTEX_NORMALS
@ -96,7 +98,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
#ifdef VISIBILITY_RANGE_DITHER #ifdef VISIBILITY_RANGE_DITHER
out.visibility_range_dither = mesh_functions::get_visibility_range_dither_level( 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 #endif
return out; return out;

View file

@ -172,7 +172,7 @@ impl From<Option<&ViewPrepassTextures>> for MeshPipelineViewLayoutKey {
} }
} }
fn buffer_layout( pub(crate) fn buffer_layout(
buffer_binding_type: BufferBindingType, buffer_binding_type: BufferBindingType,
has_dynamic_offset: bool, has_dynamic_offset: bool,
min_binding_size: Option<NonZero<u64>>, min_binding_size: Option<NonZero<u64>>,

View file

@ -26,9 +26,16 @@ fn fragment(
#ifdef MESHLET_MESH_MATERIAL_PASS #ifdef MESHLET_MESH_MATERIAL_PASS
let in = resolve_vertex_output(frag_coord); let in = resolve_vertex_output(frag_coord);
let is_front = true; 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); pbr_prepass_functions::prepass_alpha_discard(in);
#endif #endif // MESHLET_MESH_MATERIAL_PASS
var out: prepass_io::FragmentOutput; var out: prepass_io::FragmentOutput;

View file

@ -3,6 +3,7 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use bevy::{ use bevy::{
core_pipeline::prepass::{DepthPrepass, NormalPrepass},
input::mouse::MouseWheel, input::mouse::MouseWheel,
math::vec3, math::vec3,
pbr::{light_consts::lux::FULL_DAYLIGHT, CascadeShadowConfigBuilder}, pbr::{light_consts::lux::FULL_DAYLIGHT, CascadeShadowConfigBuilder},
@ -63,6 +64,8 @@ enum MainModel {
struct AppStatus { struct AppStatus {
// Whether to show only one model. // Whether to show only one model.
show_one_model_only: Option<MainModel>, show_one_model_only: Option<MainModel>,
// Whether to enable the prepass.
prepass: bool,
} }
// Sets up the app. // Sets up the app.
@ -84,6 +87,7 @@ fn main() {
set_visibility_ranges, set_visibility_ranges,
update_help_text, update_help_text,
update_mode, update_mode,
toggle_prepass,
), ),
) )
.run(); .run();
@ -288,6 +292,34 @@ fn update_mode(
} }
} }
// Toggles the prepass if the user requests.
fn toggle_prepass(
mut commands: Commands,
cameras: Query<Entity, With<Camera3d>>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut app_status: ResMut<AppStatus>,
) {
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::<DepthPrepass>()
.remove::<NormalPrepass>();
}
}
}
// A system that updates the help text. // A system that updates the help text.
fn update_help_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>) { fn update_help_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>) {
for mut text in text_query.iter_mut() { for mut text in text_query.iter_mut() {
@ -304,7 +336,8 @@ impl AppStatus {
{} (2) Show only the high-poly model {} (2) Show only the high-poly model
{} (3) Show only the low-poly model {} (3) Show only the low-poly model
Press 1, 2, or 3 to switch which model is shown 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() { if self.show_one_model_only.is_none() {
'>' '>'
} else { } else {
@ -320,6 +353,7 @@ Press WASD or use the mouse wheel to move the camera",
} else { } else {
' ' ' '
}, },
if self.prepass { "disable" } else { "enable" }
) )
.into() .into()
} }