mirror of
https://github.com/bevyengine/bevy
synced 2024-12-24 20:13:07 +00:00
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:
parent
c9fa975977
commit
56c70f8463
7 changed files with 118 additions and 21 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>>,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue