From 577071d49c2994df6ca71dc5e277f473b3ebe614 Mon Sep 17 00:00:00 2001 From: VictorElHajj Date: Mon, 22 Jun 2026 19:31:14 +0200 Subject: [PATCH 1/2] Add prepass_reads_material flag in material that sets PREPASS_READS_MATERIAL --- crates/bevy_material/src/lib.rs | 2 ++ crates/bevy_pbr/src/extended_material.rs | 9 +++++++++ crates/bevy_pbr/src/material.rs | 7 +++++++ crates/bevy_pbr/src/prepass/mod.rs | 13 +++++++++++-- crates/bevy_pbr/src/render/light.rs | 4 ++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/bevy_material/src/lib.rs b/crates/bevy_material/src/lib.rs index 296554085a129..fcb1375ea0a71 100644 --- a/crates/bevy_material/src/lib.rs +++ b/crates/bevy_material/src/lib.rs @@ -94,6 +94,8 @@ pub struct MaterialProperties { pub shadows_enabled: bool, /// Whether prepass is enabled for this material pub prepass_enabled: bool, + /// Whether prepass needs to read the material + pub prepass_reads_material: bool, } impl MaterialProperties { diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index 088a81f65cb2f..24f7e76372ef9 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -61,6 +61,11 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized { true } + #[inline] + fn prepass_reads_material() -> bool { + false + } + /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader /// will be used. fn prepass_vertex_shader() -> ShaderRef { @@ -355,6 +360,10 @@ impl Material for ExtendedMaterial { E::enable_shadows() } + fn prepass_reads_material() -> bool { + E::prepass_reads_material() + } + fn prepass_vertex_shader() -> ShaderRef { match E::prepass_vertex_shader() { ShaderRef::Default => B::prepass_vertex_shader(), diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 967ccda522ace..fb7fe28342e8a 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -200,6 +200,11 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { true } + #[inline] + fn prepass_reads_material() -> bool { + false + } + /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader /// will be used. /// @@ -1690,6 +1695,7 @@ where let shadows_enabled = M::enable_shadows(); let prepass_enabled = M::enable_prepass(); + let prepass_reads_material = M::prepass_reads_material(); let draw_opaque_pbr = opaque_draw_functions.read().id::(); let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::(); @@ -1790,6 +1796,7 @@ where material_key, shadows_enabled, prepass_enabled, + prepass_reads_material, }), }) } diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 20644c2a2ac76..317c40716e940 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -390,7 +390,9 @@ impl SpecializedMeshPipeline for PrepassPipelineSpecializer { } fn is_depth_only_opaque_prepass(mesh_key: MeshPipelineKey) -> bool { - mesh_key.intersection(MeshPipelineKey::ALL_PREPASS_BITS) == MeshPipelineKey::DEPTH_PREPASS + !mesh_key.contains(MeshPipelineKey::PREPASS_READS_MATERIAL) + && mesh_key.intersection(MeshPipelineKey::ALL_PREPASS_BITS) + == MeshPipelineKey::DEPTH_PREPASS } impl PrepassPipeline { @@ -425,7 +427,10 @@ impl PrepassPipeline { // or emulated by setting depth in the fragment shader for GPUs that don't support it natively. let emulate_unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) && !self.depth_clip_control_supported; - if is_depth_only_opaque_prepass(mesh_key) && !emulate_unclipped_depth { + if is_depth_only_opaque_prepass(mesh_key) + && !emulate_unclipped_depth + && !material_properties.prepass_reads_material + { bind_group_layouts.push(self.empty_layout.clone()); } else { bind_group_layouts.push( @@ -1181,6 +1186,10 @@ pub(crate) fn specialize_prepass_material_meshes( mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; } + if material.properties.prepass_reads_material { + mesh_key |= MeshPipelineKey::PREPASS_READS_MATERIAL; + } + // If the previous frame has skins or morph targets, note that. if motion_vector_prepass.is_some() { if mesh_instance diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 0188614a17623..dd1ccabed4baf 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -2448,6 +2448,10 @@ pub(crate) fn specialize_shadows( _ => MeshPipelineKey::NONE, }; + if material.properties.prepass_reads_material { + mesh_key |= MeshPipelineKey::PREPASS_READS_MATERIAL; + } + work_items.push(ShadowSpecializationWorkItem { visible_entity: *visible_entity, retained_view_entity: extracted_view_light.retained_view_entity, From 70e83a47edfd56de5cd9da0b67f514aa2a53398c Mon Sep 17 00:00:00 2001 From: Luo Zhiaho Date: Thu, 2 Jul 2026 20:14:22 +0800 Subject: [PATCH 2/2] Don't batch depth only prepasses when using custom vertex and fragment shaders --- crates/bevy_material/src/lib.rs | 2 -- crates/bevy_pbr/src/extended_material.rs | 9 --------- crates/bevy_pbr/src/material.rs | 23 ++++++++++++++++------- crates/bevy_pbr/src/prepass/mod.rs | 19 +++++++++++-------- crates/bevy_pbr/src/render/light.rs | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/crates/bevy_material/src/lib.rs b/crates/bevy_material/src/lib.rs index fcb1375ea0a71..296554085a129 100644 --- a/crates/bevy_material/src/lib.rs +++ b/crates/bevy_material/src/lib.rs @@ -94,8 +94,6 @@ pub struct MaterialProperties { pub shadows_enabled: bool, /// Whether prepass is enabled for this material pub prepass_enabled: bool, - /// Whether prepass needs to read the material - pub prepass_reads_material: bool, } impl MaterialProperties { diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index 24f7e76372ef9..088a81f65cb2f 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -61,11 +61,6 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized { true } - #[inline] - fn prepass_reads_material() -> bool { - false - } - /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader /// will be used. fn prepass_vertex_shader() -> ShaderRef { @@ -360,10 +355,6 @@ impl Material for ExtendedMaterial { E::enable_shadows() } - fn prepass_reads_material() -> bool { - E::prepass_reads_material() - } - fn prepass_vertex_shader() -> ShaderRef { match E::prepass_vertex_shader() { ShaderRef::Default => B::prepass_vertex_shader(), diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index fb7fe28342e8a..0151654a9b05f 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -200,11 +200,6 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { true } - #[inline] - fn prepass_reads_material() -> bool { - false - } - /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader /// will be used. /// @@ -1695,7 +1690,6 @@ where let shadows_enabled = M::enable_shadows(); let prepass_enabled = M::enable_prepass(); - let prepass_reads_material = M::prepass_reads_material(); let draw_opaque_pbr = opaque_draw_functions.read().id::(); let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::(); @@ -1796,7 +1790,6 @@ where material_key, shadows_enabled, prepass_enabled, - prepass_reads_material, }), }) } @@ -1883,3 +1876,19 @@ pub fn get_mesh_instance_world_from_local( } } } + +pub(crate) trait MaterialPropertiesExt { + fn prepass_reads_material(&self) -> bool; +} + +impl MaterialPropertiesExt for MaterialProperties { + fn prepass_reads_material(&self) -> bool { + // The default prepass shaders doesn't need material's bind group, + // but for user provided prepass shaders currently we don't have a way to known this + // because material's bind group is used for both prepass and the other passes. + // + // So we have to disable the optimization for depth only prepass and always bind the material's bind group. + self.get_shader(PrepassVertexShader).is_some() + || self.get_shader(PrepassFragmentShader).is_some() + } +} diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 317c40716e940..795c7ab331c49 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -4,11 +4,12 @@ use crate::{ alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, collect_meshes_for_gpu_building, init_material_pipeline, set_mesh_motion_vector_flags, setup_morph_and_skinning_defs, skin, DeferredAlphaMaskDrawFunction, DeferredFragmentShader, - DeferredOpaqueDrawFunction, DeferredVertexShader, DrawMesh, MaterialPipeline, MeshLayouts, - MeshPipeline, MeshPipelineKey, PreparedMaterial, PrepassAlphaMaskDrawFunction, - PrepassFragmentShader, PrepassOpaqueDepthOnlyDrawFunction, PrepassOpaqueDrawFunction, - PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, - RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, + DeferredOpaqueDrawFunction, DeferredVertexShader, DrawMesh, MaterialPipeline, + MaterialPropertiesExt, MeshLayouts, MeshPipeline, MeshPipelineKey, PreparedMaterial, + PrepassAlphaMaskDrawFunction, PrepassFragmentShader, PrepassOpaqueDepthOnlyDrawFunction, + PrepassOpaqueDrawFunction, PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, + RenderMeshInstanceFlags, RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, + ShadowView, }; use bevy_app::{App, Plugin, PreUpdate}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; @@ -390,7 +391,7 @@ impl SpecializedMeshPipeline for PrepassPipelineSpecializer { } fn is_depth_only_opaque_prepass(mesh_key: MeshPipelineKey) -> bool { - !mesh_key.contains(MeshPipelineKey::PREPASS_READS_MATERIAL) + !mesh_key.intersects(MeshPipelineKey::MAY_DISCARD | MeshPipelineKey::PREPASS_READS_MATERIAL) && mesh_key.intersection(MeshPipelineKey::ALL_PREPASS_BITS) == MeshPipelineKey::DEPTH_PREPASS } @@ -429,8 +430,10 @@ impl PrepassPipeline { && !self.depth_clip_control_supported; if is_depth_only_opaque_prepass(mesh_key) && !emulate_unclipped_depth - && !material_properties.prepass_reads_material + && !material_properties.prepass_reads_material() { + // The shaders for depth only opaque prepass doesn't need material's bind group. + // We set an empty layout and batch them by setting `material_bind_group_index` to `None` in batch set key. bind_group_layouts.push(self.empty_layout.clone()); } else { bind_group_layouts.push( @@ -1186,7 +1189,7 @@ pub(crate) fn specialize_prepass_material_meshes( mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; } - if material.properties.prepass_reads_material { + if material.properties.prepass_reads_material() { mesh_key |= MeshPipelineKey::PREPASS_READS_MATERIAL; } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index dd1ccabed4baf..6bd5bf4376a4c 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -2448,7 +2448,7 @@ pub(crate) fn specialize_shadows( _ => MeshPipelineKey::NONE, }; - if material.properties.prepass_reads_material { + if material.properties.prepass_reads_material() { mesh_key |= MeshPipelineKey::PREPASS_READS_MATERIAL; }