diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 2dd45e2c6e1f7..d704c5a1c628d 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -385,6 +385,13 @@ where )); if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) { shader_defs.push("DEPTH_CLAMP_ORTHO".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. + // We should try to find a way to resolve this without forcing the fragment + // shader to run. + // https://github.com/bevyengine/bevy/pull/8877 + shader_defs.push("PREPASS_FRAGMENT".into()); } if layout.contains(Mesh::ATTRIBUTE_UV_0) { @@ -457,8 +464,9 @@ 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 + // prepass shader or we are clamping the orthographic depth. let fragment_required = !targets.is_empty() + || key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && self.material_fragment_shader.is_some()); diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 9aba5afb6ceb6..03ddee145aee7 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -41,6 +41,10 @@ struct VertexOutput { @location(3) world_position: vec4, @location(4) previous_world_position: vec4, #endif // MOTION_VECTOR_PREPASS + +#ifdef DEPTH_CLAMP_ORTHO + @location(5) clip_position_unclamped: vec4, +#endif // DEPTH_CLAMP_ORTHO } @vertex @@ -55,7 +59,8 @@ fn vertex(vertex: Vertex) -> VertexOutput { out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); #ifdef DEPTH_CLAMP_ORTHO - out.clip_position.z = min(out.clip_position.z, 1.0); + out.clip_position_unclamped = out.clip_position; + out.clip_position.z = min(out.clip_position.z, 1.0); #endif // DEPTH_CLAMP_ORTHO #ifdef VERTEX_UVS @@ -96,6 +101,10 @@ struct FragmentInput { @location(3) world_position: vec4, @location(4) previous_world_position: vec4, #endif // MOTION_VECTOR_PREPASS + +#ifdef DEPTH_CLAMP_ORTHO + @location(5) clip_position_unclamped: vec4, +#endif // DEPTH_CLAMP_ORTHO } struct FragmentOutput { @@ -106,6 +115,10 @@ struct FragmentOutput { #ifdef MOTION_VECTOR_PREPASS @location(1) motion_vector: vec2, #endif // MOTION_VECTOR_PREPASS + +#ifdef DEPTH_CLAMP_ORTHO + @builtin(frag_depth) frag_depth: f32, +#endif // DEPTH_CLAMP_ORTHO } @fragment @@ -116,6 +129,10 @@ fn fragment(in: FragmentInput) -> 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 MOTION_VECTOR_PREPASS let clip_position_t = view.unjittered_view_proj * in.world_position; let clip_position = clip_position_t.xy / clip_position_t.w; diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index 1a90c4570934f..bb2d1dfdd97a8 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -22,6 +22,10 @@ struct FragmentInput { @location(3) world_position: vec4, @location(4) previous_world_position: vec4, #endif // MOTION_VECTOR_PREPASS + +#ifdef DEPTH_CLAMP_ORTHO + @location(5) clip_position_unclamped: vec4, +#endif // DEPTH_CLAMP_ORTHO }; // Cutoff used for the premultiplied alpha modes BLEND and ADD. @@ -66,6 +70,10 @@ struct FragmentOutput { #ifdef MOTION_VECTOR_PREPASS @location(1) motion_vector: vec2, #endif // MOTION_VECTOR_PREPASS + +#ifdef DEPTH_CLAMP_ORTHO + @builtin(frag_depth) frag_depth: f32, +#endif // DEPTH_CLAMP_ORTHO } @fragment @@ -74,6 +82,10 @@ fn fragment(in: FragmentInput) -> FragmentOutput { var out: FragmentOutput; +#ifdef DEPTH_CLAMP_ORTHO + out.frag_depth = in.clip_position_unclamped.z; +#endif // DEPTH_CLAMP_ORTHO + #ifdef NORMAL_PREPASS // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit if (material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u {