mirror of
https://github.com/bevyengine/bevy
synced 2024-12-19 17:43: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},
|
||||
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<M: Material> FromWorld for PrepassPipeline<M> {
|
|||
let render_device = world.resource::<RenderDevice>();
|
||||
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(
|
||||
"prepass_view_layout_motion_vectors",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
(
|
||||
// View
|
||||
uniform_buffer::<ViewUniform>(true),
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
// Globals
|
||||
uniform_buffer::<GlobalsUniform>(false),
|
||||
(1, uniform_buffer::<GlobalsUniform>(false)),
|
||||
// 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(
|
||||
"prepass_view_layout_no_motion_vectors",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
(
|
||||
// View
|
||||
uniform_buffer::<ViewUniform>(true),
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
// 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());
|
||||
}
|
||||
|
||||
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<M: Material>(
|
|||
view_uniforms: Res<ViewUniforms>,
|
||||
globals_buffer: Res<GlobalsBuffer>,
|
||||
previous_view_uniforms: Res<PreviousViewUniforms>,
|
||||
visibility_ranges: Res<RenderVisibilityRanges>,
|
||||
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(),
|
||||
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<M: Material>(
|
|||
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
||||
render_material_instances: Res<RenderMaterialInstances<M>>,
|
||||
render_lightmaps: Res<RenderLightmaps>,
|
||||
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
||||
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
|
||||
mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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<f32>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,10 @@ struct VertexOutput {
|
|||
#ifdef VERTEX_COLORS
|
||||
@location(8) color: vec4<f32>,
|
||||
#endif
|
||||
|
||||
#ifdef VISIBILITY_RANGE_DITHER
|
||||
@location(9) @interpolate(flat) visibility_range_dither: i32,
|
||||
#endif // VISIBILITY_RANGE_DITHER
|
||||
}
|
||||
|
||||
#ifdef PREPASS_FRAGMENT
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -172,7 +172,7 @@ impl From<Option<&ViewPrepassTextures>> for MeshPipelineViewLayoutKey {
|
|||
}
|
||||
}
|
||||
|
||||
fn buffer_layout(
|
||||
pub(crate) fn buffer_layout(
|
||||
buffer_binding_type: BufferBindingType,
|
||||
has_dynamic_offset: bool,
|
||||
min_binding_size: Option<NonZero<u64>>,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<MainModel>,
|
||||
// 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<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.
|
||||
fn update_help_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>) {
|
||||
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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue