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

Make visibility range (HLOD) dithering work when prepasses are enabled. #16286

Merged
merged 4 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 57 additions & 14 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand All @@ -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},
Expand Down Expand Up @@ -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),
),
),
),
);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()),
)),
));
}
Expand Down Expand Up @@ -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>>,
Expand Down Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_pbr/src/prepass/prepass.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_pbr/src/prepass/prepass_io.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_pbr/src/render/mesh.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/mesh_view_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand Down
11 changes: 9 additions & 2 deletions crates/bevy_pbr/src/render/pbr_prepass.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
36 changes: 35 additions & 1 deletion examples/3d/visibility_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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.
Expand All @@ -84,6 +87,7 @@ fn main() {
set_visibility_ranges,
update_help_text,
update_mode,
toggle_prepass,
),
)
.run();
Expand Down Expand Up @@ -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() {
Expand All @@ -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 {
Expand All @@ -320,6 +353,7 @@ Press WASD or use the mouse wheel to move the camera",
} else {
' '
},
if self.prepass { "disable" } else { "enable" }
)
.into()
}
Expand Down
Loading