diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 70da162be4..c85ee80453 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -142,6 +142,7 @@ impl MeshletPlugin { WgpuFeatures::SHADER_INT64_ATOMIC_MIN_MAX | WgpuFeatures::SHADER_INT64 | WgpuFeatures::SUBGROUP + | WgpuFeatures::DEPTH_CLIP_CONTROL | WgpuFeatures::PUSH_CONSTANTS } } diff --git a/crates/bevy_pbr/src/meshlet/pipelines.rs b/crates/bevy_pbr/src/meshlet/pipelines.rs index ab333e736c..97f1203d2d 100644 --- a/crates/bevy_pbr/src/meshlet/pipelines.rs +++ b/crates/bevy_pbr/src/meshlet/pipelines.rs @@ -34,10 +34,9 @@ pub struct MeshletPipelines { downsample_depth_second_shadow_view: CachedComputePipelineId, visibility_buffer_software_raster: CachedComputePipelineId, visibility_buffer_software_raster_depth_only: CachedComputePipelineId, - visibility_buffer_software_raster_depth_only_clamp_ortho: CachedComputePipelineId, visibility_buffer_hardware_raster: CachedRenderPipelineId, visibility_buffer_hardware_raster_depth_only: CachedRenderPipelineId, - visibility_buffer_hardware_raster_depth_only_clamp_ortho: CachedRenderPipelineId, + visibility_buffer_hardware_raster_depth_only_unclipped: CachedRenderPipelineId, resolve_depth: CachedRenderPipelineId, resolve_depth_shadow_view: CachedRenderPipelineId, resolve_material_depth: CachedRenderPipelineId, @@ -215,29 +214,6 @@ impl FromWorld for MeshletPipelines { }, ), - visibility_buffer_software_raster_depth_only_clamp_ortho: pipeline_cache - .queue_compute_pipeline(ComputePipelineDescriptor { - label: Some( - "meshlet_visibility_buffer_software_raster_depth_only_clamp_ortho_pipeline" - .into(), - ), - layout: vec![visibility_buffer_raster_layout.clone()], - push_constant_ranges: vec![], - shader: MESHLET_VISIBILITY_BUFFER_SOFTWARE_RASTER_SHADER_HANDLE, - shader_defs: vec![ - "MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into(), - "DEPTH_CLAMP_ORTHO".into(), - if remap_1d_to_2d_dispatch_layout.is_some() { - "MESHLET_2D_DISPATCH" - } else { - "" - } - .into(), - ], - entry_point: "rasterize_cluster".into(), - zero_initialize_workgroup_memory: false, - }), - visibility_buffer_hardware_raster: pipeline_cache.queue_render_pipeline( RenderPipelineDescriptor { label: Some("meshlet_visibility_buffer_hardware_raster_pipeline".into()), @@ -324,10 +300,10 @@ impl FromWorld for MeshletPipelines { }, ), - visibility_buffer_hardware_raster_depth_only_clamp_ortho: pipeline_cache + visibility_buffer_hardware_raster_depth_only_unclipped: pipeline_cache .queue_render_pipeline(RenderPipelineDescriptor { label: Some( - "meshlet_visibility_buffer_hardware_raster_depth_only_clamp_ortho_pipeline" + "meshlet_visibility_buffer_hardware_raster_depth_only_unclipped_pipeline" .into(), ), layout: vec![visibility_buffer_raster_layout], @@ -337,10 +313,7 @@ impl FromWorld for MeshletPipelines { }], vertex: VertexState { shader: MESHLET_VISIBILITY_BUFFER_HARDWARE_RASTER_SHADER_HANDLE, - shader_defs: vec![ - "MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into(), - "DEPTH_CLAMP_ORTHO".into(), - ], + shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into()], entry_point: "vertex".into(), buffers: vec![], }, @@ -349,7 +322,7 @@ impl FromWorld for MeshletPipelines { strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: Some(Face::Back), - unclipped_depth: false, + unclipped_depth: true, polygon_mode: PolygonMode::Fill, conservative: false, }, @@ -357,10 +330,7 @@ impl FromWorld for MeshletPipelines { multisample: MultisampleState::default(), fragment: Some(FragmentState { shader: MESHLET_VISIBILITY_BUFFER_HARDWARE_RASTER_SHADER_HANDLE, - shader_defs: vec![ - "MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into(), - "DEPTH_CLAMP_ORTHO".into(), - ], + shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS".into()], entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { format: TextureFormat::R8Uint, @@ -484,7 +454,6 @@ impl MeshletPipelines { &ComputePipeline, &ComputePipeline, &ComputePipeline, - &ComputePipeline, &RenderPipeline, &RenderPipeline, &RenderPipeline, @@ -506,14 +475,11 @@ impl MeshletPipelines { pipeline_cache.get_compute_pipeline(pipeline.visibility_buffer_software_raster)?, pipeline_cache .get_compute_pipeline(pipeline.visibility_buffer_software_raster_depth_only)?, - pipeline_cache.get_compute_pipeline( - pipeline.visibility_buffer_software_raster_depth_only_clamp_ortho, - )?, pipeline_cache.get_render_pipeline(pipeline.visibility_buffer_hardware_raster)?, pipeline_cache .get_render_pipeline(pipeline.visibility_buffer_hardware_raster_depth_only)?, pipeline_cache.get_render_pipeline( - pipeline.visibility_buffer_hardware_raster_depth_only_clamp_ortho, + pipeline.visibility_buffer_hardware_raster_depth_only_unclipped, )?, pipeline_cache.get_render_pipeline(pipeline.resolve_depth)?, pipeline_cache.get_render_pipeline(pipeline.resolve_depth_shadow_view)?, diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl b/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl index 65ccb17482..fb2e090051 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl @@ -23,9 +23,6 @@ struct VertexOutput { #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT @location(0) @interpolate(flat) packed_ids: u32, #endif -#ifdef DEPTH_CLAMP_ORTHO - @location(0) unclamped_clip_depth: f32, -#endif } @vertex @@ -45,19 +42,12 @@ fn vertex(@builtin(instance_index) instance_index: u32, @builtin(vertex_index) v let vertex_position = get_meshlet_vertex_position(&meshlet, vertex_id); let world_from_local = affine3_to_square(instance_uniform.world_from_local); let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex_position, 1.0)); - var clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0); -#ifdef DEPTH_CLAMP_ORTHO - let unclamped_clip_depth = clip_position.z; - clip_position.z = min(clip_position.z, 1.0); -#endif + let clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0); return VertexOutput( clip_position, #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT (cluster_id << 7u) | triangle_id, -#endif -#ifdef DEPTH_CLAMP_ORTHO - unclamped_clip_depth, #endif ); } @@ -70,9 +60,6 @@ fn fragment(vertex_output: VertexOutput) { let depth = bitcast(vertex_output.position.z); let visibility = (u64(depth) << 32u) | u64(vertex_output.packed_ids); atomicMax(&meshlet_visibility_buffer[frag_coord_1d], visibility); -#else ifdef DEPTH_CLAMP_ORTHO - let depth = bitcast(vertex_output.unclamped_clip_depth); - atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth); #else let depth = bitcast(vertex_output.position.z); atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth); @@ -84,9 +71,6 @@ fn dummy_vertex() -> VertexOutput { vec4(divide(0.0, 0.0)), // NaN vertex position #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT 0u, -#endif -#ifdef DEPTH_CLAMP_ORTHO - 0.0, #endif ); } diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index f0de6d7769..aa549ae679 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -85,10 +85,9 @@ impl Node for MeshletVisibilityBufferRasterPassNode { downsample_depth_second_shadow_view_pipeline, visibility_buffer_software_raster_pipeline, visibility_buffer_software_raster_depth_only_pipeline, - visibility_buffer_software_raster_depth_only_clamp_ortho, visibility_buffer_hardware_raster_pipeline, visibility_buffer_hardware_raster_depth_only_pipeline, - visibility_buffer_hardware_raster_depth_only_clamp_ortho, + visibility_buffer_hardware_raster_depth_only_unclipped_pipeline, resolve_depth_pipeline, resolve_depth_shadow_view_pipeline, resolve_material_depth_pipeline, @@ -223,19 +222,12 @@ impl Node for MeshletVisibilityBufferRasterPassNode { continue; }; - let ( - shadow_visibility_buffer_software_raster_pipeline, - shadow_visibility_buffer_hardware_raster_pipeline, - ) = match light_type { - LightEntity::Directional { .. } => ( - visibility_buffer_software_raster_depth_only_clamp_ortho, - visibility_buffer_hardware_raster_depth_only_clamp_ortho, - ), - _ => ( - visibility_buffer_software_raster_depth_only_pipeline, - visibility_buffer_hardware_raster_depth_only_pipeline, - ), - }; + let shadow_visibility_buffer_hardware_raster_pipeline = + if let LightEntity::Directional { .. } = light_type { + visibility_buffer_hardware_raster_depth_only_unclipped_pipeline + } else { + visibility_buffer_hardware_raster_depth_only_pipeline + }; render_context.command_encoder().push_debug_group(&format!( "meshlet_visibility_buffer_raster: {}", @@ -270,7 +262,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode { &meshlet_view_resources.dummy_render_target.default_view, meshlet_view_bind_groups, view_offset, - shadow_visibility_buffer_software_raster_pipeline, + visibility_buffer_software_raster_depth_only_pipeline, shadow_visibility_buffer_hardware_raster_pipeline, None, meshlet_view_resources.raster_cluster_rightmost_slot, @@ -306,7 +298,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode { &meshlet_view_resources.dummy_render_target.default_view, meshlet_view_bind_groups, view_offset, - shadow_visibility_buffer_software_raster_pipeline, + visibility_buffer_software_raster_depth_only_pipeline, shadow_visibility_buffer_hardware_raster_pipeline, None, meshlet_view_resources.raster_cluster_rightmost_slot, diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl b/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl index ec00bb2930..941c31f093 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl @@ -57,10 +57,7 @@ fn rasterize_cluster( // Project vertex to viewport space let world_position = mesh_position_local_to_world(world_from_local, vec4(vertex_position, 1.0)); let clip_position = view.clip_from_world * vec4(world_position.xyz, 1.0); - var ndc_position = clip_position.xyz / clip_position.w; -#ifdef DEPTH_CLAMP_ORTHO - ndc_position.z = 1.0 / clip_position.z; -#endif + let ndc_position = clip_position.xyz / clip_position.w; let viewport_position_xy = ndc_to_uv(ndc_position.xy) * view.viewport.zw; // Write vertex to workgroup shared memory @@ -176,9 +173,6 @@ fn write_visibility_buffer_pixel(x: f32, y: f32, z: f32, packed_ids: u32) { let depth = bitcast(z); let visibility = (u64(depth) << 32u) | u64(packed_ids); atomicMax(&meshlet_visibility_buffer[frag_coord_1d], visibility); -#else ifdef DEPTH_CLAMP_ORTHO - let depth = bitcast(1.0 / z); - atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth); #else let depth = bitcast(z); atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth); diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 8bd10f9678..d641d17c4b 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -251,6 +251,7 @@ pub struct PrepassPipeline { pub deferred_material_vertex_shader: Option>, pub deferred_material_fragment_shader: Option>, pub material_pipeline: MaterialPipeline, + pub depth_clip_control_supported: bool, _marker: PhantomData, } @@ -289,6 +290,10 @@ impl FromWorld for PrepassPipeline { let mesh_pipeline = world.resource::(); + let depth_clip_control_supported = render_device + .features() + .contains(WgpuFeatures::DEPTH_CLIP_CONTROL); + PrepassPipeline { view_layout_motion_vectors, view_layout_no_motion_vectors, @@ -315,6 +320,7 @@ impl FromWorld for PrepassPipeline { }, material_layout: M::bind_group_layout(render_device), material_pipeline: world.resource::>().clone(), + depth_clip_control_supported, _marker: PhantomData, } } @@ -379,8 +385,14 @@ where vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } - if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) { - shader_defs.push("DEPTH_CLAMP_ORTHO".into()); + // For directional light shadow map views, use unclipped depth via either the native GPU feature, + // or emulated by setting depth in the fragment shader for GPUs that don't support it natively. + let emulate_unclipped_depth = key + .mesh_key + .contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) + && !self.depth_clip_control_supported; + if emulate_unclipped_depth { + shader_defs.push("UNCLIPPED_DEPTH_ORTHO_EMULATION".into()); // PERF: This line forces the "prepass fragment shader" to always run in // common scenarios like "directional light calculation". Doing so resolves // a pretty nasty depth clamping bug, but it also feels a bit excessive. @@ -389,6 +401,10 @@ where // https://github.com/bevyengine/bevy/pull/8877 shader_defs.push("PREPASS_FRAGMENT".into()); } + let unclipped_depth = key + .mesh_key + .contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) + && self.depth_clip_control_supported; if layout.0.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); @@ -488,10 +504,10 @@ where } // The fragment shader is only used when the normal prepass or motion vectors prepass - // is enabled or the material uses alpha cutoff values and doesn't rely on the standard - // prepass shader or we are clamping the orthographic depth. + // is enabled, the material uses alpha cutoff values and doesn't rely on the standard + // prepass shader, or we are emulating unclipped depth in the fragment shader. let fragment_required = !targets.is_empty() - || key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) + || emulate_unclipped_depth || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && self.prepass_material_fragment_shader.is_some()); @@ -544,7 +560,7 @@ where strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: None, - unclipped_depth: false, + unclipped_depth, polygon_mode: PolygonMode::Fill, conservative: false, }, diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 7a0fcb89ed..725afb8d40 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -76,10 +76,10 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); out.position = position_world_to_clip(out.world_position.xyz); -#ifdef DEPTH_CLAMP_ORTHO - out.clip_position_unclamped = out.position; - out.position.z = min(out.position.z, 1.0); -#endif // DEPTH_CLAMP_ORTHO +#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION + out.unclipped_depth = out.position.z; + out.position.z = min(out.position.z, 1.0); // Clamp depth to avoid clipping +#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION #ifdef VERTEX_UVS_A out.uv = vertex.uv; @@ -173,9 +173,9 @@ fn fragment(in: VertexOutput) -> FragmentOutput { out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0); #endif -#ifdef DEPTH_CLAMP_ORTHO - out.frag_depth = in.clip_position_unclamped.z; -#endif // DEPTH_CLAMP_ORTHO +#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION + out.frag_depth = in.unclipped_depth; +#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION #ifdef MOTION_VECTOR_PREPASS let clip_position_t = view.unjittered_clip_from_world * in.world_position; diff --git a/crates/bevy_pbr/src/prepass/prepass_io.wgsl b/crates/bevy_pbr/src/prepass/prepass_io.wgsl index bd05de978c..4a8e465752 100644 --- a/crates/bevy_pbr/src/prepass/prepass_io.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_io.wgsl @@ -60,9 +60,9 @@ struct VertexOutput { @location(5) previous_world_position: vec4, #endif -#ifdef DEPTH_CLAMP_ORTHO - @location(6) clip_position_unclamped: vec4, -#endif // DEPTH_CLAMP_ORTHO +#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION + @location(6) unclipped_depth: f32, +#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION #ifdef VERTEX_OUTPUT_INSTANCE_INDEX @location(7) instance_index: u32, #endif @@ -87,8 +87,8 @@ struct FragmentOutput { @location(3) deferred_lighting_pass_id: u32, #endif -#ifdef DEPTH_CLAMP_ORTHO +#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION @builtin(frag_depth) frag_depth: f32, -#endif // DEPTH_CLAMP_ORTHO +#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION } #endif //PREPASS_FRAGMENT diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index b307429c8e..8293274932 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1554,7 +1554,7 @@ pub fn queue_shadows( .expect("Failed to get spot light visible entities"), }; let mut light_key = MeshPipelineKey::DEPTH_PREPASS; - light_key.set(MeshPipelineKey::DEPTH_CLAMP_ORTHO, is_directional_light); + light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light); // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index e8adff671b..5e51830d89 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1489,7 +1489,9 @@ bitflags::bitflags! { // See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test const ENVIRONMENT_MAP = 1 << 8; const SCREEN_SPACE_AMBIENT_OCCLUSION = 1 << 9; - const DEPTH_CLAMP_ORTHO = 1 << 10; + const UNCLIPPED_DEPTH_ORTHO = 1 << 10; // Disables depth clipping for use with directional light shadow views + // Emulated via fragment shader depth on hardware that doesn't support it natively + // See: https://www.w3.org/TR/webgpu/#depth-clipping and https://therealmjp.github.io/posts/shadow-maps/#disabling-z-clipping const TEMPORAL_JITTER = 1 << 11; const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12; const LIGHTMAPPED = 1 << 13; diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index 97cd3fa429..17f62409a0 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -32,9 +32,9 @@ fn fragment( var out: prepass_io::FragmentOutput; -#ifdef DEPTH_CLAMP_ORTHO - out.frag_depth = in.clip_position_unclamped.z; -#endif // DEPTH_CLAMP_ORTHO +#ifdef UNCLIPPED_DEPTH_ORTHO_EMULATION + out.frag_depth = in.unclipped_depth; +#endif // UNCLIPPED_DEPTH_ORTHO_EMULATION #ifdef NORMAL_PREPASS // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit