Skip to main content

bevy_pbr/
extended_material.rs

1use bevy_asset::Asset;
2use bevy_ecs::system::SystemParamItem;
3use bevy_material::{AlphaMode, OpaqueRendererMethod};
4use bevy_mesh::MeshVertexBufferLayoutRef;
5use bevy_reflect::{impl_type_path, Reflect};
6use bevy_render::{
7    combined_bind_group as cbg,
8    render_resource::{
9        AsBindGroup, AsBindGroupError, BindGroupBuilder, BindGroupLayout, BindGroupLayoutEntry,
10        BindlessDescriptor, BindlessSlabResourceLimit, RenderPipelineDescriptor,
11        SpecializedMeshPipelineError,
12    },
13    renderer::RenderDevice,
14};
15use bevy_shader::ShaderRef;
16
17use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey};
18
19pub struct MaterialExtensionPipeline {
20    pub mesh_pipeline: MeshPipeline,
21}
22
23pub struct MaterialExtensionKey<E: MaterialExtension> {
24    pub mesh_key: MeshPipelineKey,
25    pub bind_group_data: E::Data,
26}
27
28/// A subset of the `Material` trait for defining extensions to a base `Material`, such as the builtin `StandardMaterial`.
29///
30/// A user type implementing the trait should be used as the `E` generic param in an `ExtendedMaterial` struct.
31pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
32    /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the base material mesh vertex shader
33    /// will be used.
34    fn vertex_shader() -> ShaderRef {
35        ShaderRef::Default
36    }
37
38    /// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the base material mesh fragment shader
39    /// will be used.
40    fn fragment_shader() -> ShaderRef {
41        ShaderRef::Default
42    }
43
44    // Returns this material’s AlphaMode. If None is returned, the base material alpha mode will be used.
45    fn alpha_mode() -> Option<AlphaMode> {
46        None
47    }
48
49    /// Controls if the prepass is enabled for the Material.
50    /// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
51    #[inline]
52    fn enable_prepass() -> bool {
53        true
54    }
55
56    /// Controls if shadows are enabled for the Material.
57    #[inline]
58    fn enable_shadows() -> bool {
59        true
60    }
61
62    /// Controls whether order independent transparency is enabled for the Material.
63    /// This is ignored if the camera does not have [`bevy_core_pipeline::oit::OrderIndependentTransparencySettings`]
64    /// or if [`Self::alpha_mode`] is not supported by OIT.
65    #[inline]
66    fn enable_oit() -> bool {
67        true
68    }
69
70    /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader
71    /// will be used.
72    fn prepass_vertex_shader() -> ShaderRef {
73        ShaderRef::Default
74    }
75
76    /// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material prepass fragment shader
77    /// will be used.
78    fn prepass_fragment_shader() -> ShaderRef {
79        ShaderRef::Default
80    }
81
82    /// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the base material deferred vertex shader
83    /// will be used.
84    fn deferred_vertex_shader() -> ShaderRef {
85        ShaderRef::Default
86    }
87
88    /// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material deferred fragment shader
89    /// will be used.
90    fn deferred_fragment_shader() -> ShaderRef {
91        ShaderRef::Default
92    }
93
94    /// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned,
95    /// the default meshlet mesh fragment shader will be used.
96    #[cfg(feature = "meshlet")]
97    fn meshlet_mesh_fragment_shader() -> ShaderRef {
98        ShaderRef::Default
99    }
100
101    /// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned,
102    /// the default meshlet mesh prepass fragment shader will be used.
103    #[cfg(feature = "meshlet")]
104    fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
105        ShaderRef::Default
106    }
107
108    /// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned,
109    /// the default meshlet mesh deferred fragment shader will be used.
110    #[cfg(feature = "meshlet")]
111    fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
112        ShaderRef::Default
113    }
114
115    /// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
116    /// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.
117    /// Specialization for the base material is applied before this function is called.
118    #[expect(
119        unused_variables,
120        reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion."
121    )]
122    #[inline]
123    fn specialize(
124        pipeline: &MaterialExtensionPipeline,
125        descriptor: &mut RenderPipelineDescriptor,
126        layout: &MeshVertexBufferLayoutRef,
127        key: MaterialExtensionKey<Self>,
128    ) -> Result<(), SpecializedMeshPipelineError> {
129        Ok(())
130    }
131}
132
133/// A material that extends a base [`Material`] with additional shaders and data.
134///
135/// The data from both materials will be combined and made available to the shader
136/// so that shader functions built for the base material (and referencing the base material
137/// bindings) will work as expected, and custom alterations based on custom data can also be used.
138///
139/// If the extension `E` returns a non-default result from `vertex_shader()` it will be used in place of the base
140/// material's vertex shader.
141///
142/// If the extension `E` returns a non-default result from `fragment_shader()` it will be used in place of the base
143/// fragment shader.
144///
145/// When used with `StandardMaterial` as the base, all the standard material fields are
146/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see
147/// the `extended_material` example).
148#[derive(Asset, Clone, Debug, Reflect)]
149#[reflect(type_path = false)]
150#[reflect(Clone)]
151pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {
152    pub base: B,
153    pub extension: E,
154}
155
156impl<B, E> Default for ExtendedMaterial<B, E>
157where
158    B: Material + Default,
159    E: MaterialExtension + Default,
160{
161    fn default() -> Self {
162        Self {
163            base: B::default(),
164            extension: E::default(),
165        }
166    }
167}
168
169// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]`
170// causes the `TypePath` derive to not generate an implementation.
171impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E: MaterialExtension>);
172
173impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
174    type Data = cbg::CombinedBindGroupData<B::Data, E::Data>;
175    type Param = (B::Param, E::Param);
176
177    fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
178        cbg::bindless_slot_count::<B, E>()
179    }
180
181    fn bindless_supported(render_device: &RenderDevice) -> bool {
182        B::bindless_supported(render_device) && E::bindless_supported(render_device)
183    }
184
185    fn label() -> &'static str {
186        E::label()
187    }
188
189    fn bind_group_data(&self) -> Self::Data {
190        cbg::bind_group_data(&self.base, &self.extension)
191    }
192
193    fn build_bind_group(
194        &self,
195        layout: &BindGroupLayout,
196        render_device: &RenderDevice,
197        param: &mut SystemParamItem<'_, '_, Self::Param>,
198        force_no_bindless: bool,
199        output: &mut BindGroupBuilder,
200    ) -> Result<(), AsBindGroupError> {
201        cbg::build_bind_group(
202            &self.base,
203            &self.extension,
204            layout,
205            render_device,
206            param,
207            force_no_bindless,
208            output,
209        )
210    }
211
212    fn bind_group_layout_entries(
213        render_device: &RenderDevice,
214        force_no_bindless: bool,
215    ) -> Vec<BindGroupLayoutEntry>
216    where
217        Self: Sized,
218    {
219        cbg::bind_group_layout_entries::<B, E>(render_device, force_no_bindless)
220    }
221
222    fn bindless_descriptor() -> Option<BindlessDescriptor> {
223        cbg::bindless_descriptor::<B, E>()
224    }
225}
226
227impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
228    fn vertex_shader() -> ShaderRef {
229        match E::vertex_shader() {
230            ShaderRef::Default => B::vertex_shader(),
231            specified => specified,
232        }
233    }
234
235    fn fragment_shader() -> ShaderRef {
236        match E::fragment_shader() {
237            ShaderRef::Default => B::fragment_shader(),
238            specified => specified,
239        }
240    }
241
242    fn alpha_mode(&self) -> AlphaMode {
243        match E::alpha_mode() {
244            Some(specified) => specified,
245            None => B::alpha_mode(&self.base),
246        }
247    }
248
249    fn opaque_render_method(&self) -> OpaqueRendererMethod {
250        B::opaque_render_method(&self.base)
251    }
252
253    fn depth_bias(&self) -> f32 {
254        B::depth_bias(&self.base)
255    }
256
257    fn reads_view_transmission_texture(&self) -> bool {
258        B::reads_view_transmission_texture(&self.base)
259    }
260
261    fn enable_prepass() -> bool {
262        E::enable_prepass()
263    }
264
265    fn enable_shadows() -> bool {
266        E::enable_shadows()
267    }
268
269    fn enable_oit() -> bool {
270        E::enable_oit()
271    }
272
273    fn prepass_vertex_shader() -> ShaderRef {
274        match E::prepass_vertex_shader() {
275            ShaderRef::Default => B::prepass_vertex_shader(),
276            specified => specified,
277        }
278    }
279
280    fn prepass_fragment_shader() -> ShaderRef {
281        match E::prepass_fragment_shader() {
282            ShaderRef::Default => B::prepass_fragment_shader(),
283            specified => specified,
284        }
285    }
286
287    fn deferred_vertex_shader() -> ShaderRef {
288        match E::deferred_vertex_shader() {
289            ShaderRef::Default => B::deferred_vertex_shader(),
290            specified => specified,
291        }
292    }
293
294    fn deferred_fragment_shader() -> ShaderRef {
295        match E::deferred_fragment_shader() {
296            ShaderRef::Default => B::deferred_fragment_shader(),
297            specified => specified,
298        }
299    }
300
301    #[cfg(feature = "meshlet")]
302    fn meshlet_mesh_fragment_shader() -> ShaderRef {
303        match E::meshlet_mesh_fragment_shader() {
304            ShaderRef::Default => B::meshlet_mesh_fragment_shader(),
305            specified => specified,
306        }
307    }
308
309    #[cfg(feature = "meshlet")]
310    fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
311        match E::meshlet_mesh_prepass_fragment_shader() {
312            ShaderRef::Default => B::meshlet_mesh_prepass_fragment_shader(),
313            specified => specified,
314        }
315    }
316
317    #[cfg(feature = "meshlet")]
318    fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
319        match E::meshlet_mesh_deferred_fragment_shader() {
320            ShaderRef::Default => B::meshlet_mesh_deferred_fragment_shader(),
321            specified => specified,
322        }
323    }
324
325    fn specialize(
326        pipeline: &MaterialPipeline,
327        descriptor: &mut RenderPipelineDescriptor,
328        layout: &MeshVertexBufferLayoutRef,
329        key: MaterialPipelineKey<Self>,
330    ) -> Result<(), SpecializedMeshPipelineError> {
331        // Call the base material's specialize function
332        let base_key = MaterialPipelineKey::<B> {
333            mesh_key: key.mesh_key,
334            bind_group_data: key.bind_group_data.base,
335        };
336        B::specialize(pipeline, descriptor, layout, base_key)?;
337
338        // Call the extended material's specialize function afterwards
339        E::specialize(
340            &MaterialExtensionPipeline {
341                mesh_pipeline: pipeline.mesh_pipeline.clone(),
342            },
343            descriptor,
344            layout,
345            MaterialExtensionKey {
346                mesh_key: key.mesh_key,
347                bind_group_data: key.bind_group_data.extension,
348            },
349        )
350    }
351}
352
353#[deprecated = "Use `bevy_render::combined_bind_group::CombinedBindGroupData` instead"]
354pub type MaterialExtensionBindGroupData<B, E> = cbg::CombinedBindGroupData<B, E>;