Skip to main content

bevy_pbr/
lib.rs

1#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![forbid(unsafe_code)]
4#![doc(
5    html_logo_url = "https://bevy.org/assets/icon.png",
6    html_favicon_url = "https://bevy.org/assets/icon.png"
7)]
8
9extern crate alloc;
10
11#[cfg(feature = "meshlet")]
12mod meshlet;
13pub mod wireframe;
14
15/// Experimental features that are not yet finished. Please report any issues you encounter!
16///
17/// Expect bugs, missing features, compatibility issues, low performance, and/or future breaking changes.
18#[cfg(feature = "meshlet")]
19pub mod experimental {
20    /// Render high-poly 3d meshes using an efficient GPU-driven method.
21    /// See [`MeshletPlugin`](meshlet::MeshletPlugin) and [`MeshletMesh`](meshlet::MeshletMesh) for details.
22    pub mod meshlet {
23        pub use crate::meshlet::*;
24    }
25}
26
27mod atmosphere;
28mod cluster;
29pub mod contact_shadows;
30#[cfg(feature = "bevy_gltf")]
31mod gltf;
32use bevy_light::cluster::GlobalClusterSettings;
33use bevy_render::{
34    sync_component::SyncComponent,
35    view::{
36        RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin,
37        RenderShadowMapVisibleEntities,
38    },
39};
40pub use contact_shadows::{
41    ContactShadows, ContactShadowsBuffer, ContactShadowsPlugin, ContactShadowsUniform,
42    ViewContactShadowsUniformOffset,
43};
44pub mod decal;
45pub mod deferred;
46pub mod diagnostic;
47mod extended_material;
48mod fog;
49mod light_probe;
50mod lightmap;
51mod material;
52mod material_bind_groups;
53mod medium;
54mod mesh_material;
55mod parallax;
56mod pbr_material;
57mod prepass;
58mod render;
59mod ssao;
60mod ssr;
61mod transmission;
62mod volumetric_fog;
63
64use bevy_color::{Color, LinearRgba};
65
66pub use atmosphere::*;
67use bevy_light::{
68    AmbientLight, DirectionalLight, PointLight, RectLight, ShadowFilteringMethod, SpotLight,
69};
70use bevy_shader::{load_shader_library, ShaderRef};
71pub use cluster::*;
72pub use decal::clustered::ClusteredDecalPlugin;
73pub use extended_material::*;
74pub use fog::*;
75pub use light_probe::*;
76pub use lightmap::*;
77pub use material::*;
78pub use material_bind_groups::*;
79pub use medium::*;
80pub use mesh_material::*;
81pub use parallax::*;
82pub use pbr_material::*;
83pub use prepass::*;
84pub use render::*;
85pub use ssao::*;
86pub use ssr::*;
87pub use transmission::*;
88pub use volumetric_fog::VolumetricFogPlugin;
89
90/// The PBR prelude.
91///
92/// This includes the most common types in this crate, re-exported for your convenience.
93pub mod prelude {
94    #[doc(hidden)]
95    pub use crate::{
96        contact_shadows::ContactShadowsPlugin,
97        fog::{DistanceFog, FogFalloff},
98        material::{Material, MaterialPlugin},
99        mesh_material::MeshMaterial3d,
100        parallax::ParallaxMappingMethod,
101        pbr_material::StandardMaterial,
102        ssao::ScreenSpaceAmbientOcclusionPlugin,
103    };
104}
105
106use crate::gpu::GpuClusteringPlugin;
107use crate::{deferred::DeferredPbrLightingPlugin, gpu::extract_clusters_for_gpu_clustering};
108use bevy_app::prelude::*;
109use bevy_asset::{AssetApp, AssetPath, Assets, Handle, RenderAssetUsages};
110use bevy_core_pipeline::mip_generation::experimental::depth::early_downsample_depth;
111use bevy_core_pipeline::schedule::{Core3d, Core3dSystems};
112use bevy_ecs::prelude::*;
113use bevy_image::{Image, ImageSampler};
114use bevy_material::AlphaMode;
115use bevy_render::{
116    camera::sort_cameras,
117    extract_resource::ExtractResourcePlugin,
118    render_resource::{
119        Extent3d, TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat,
120        TextureUsages, TextureViewDescriptor, TextureViewDimension,
121    },
122    sync_component::SyncComponentPlugin,
123    ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags, RenderStartup,
124    RenderSystems,
125};
126
127use std::path::PathBuf;
128
129fn shader_ref(path: PathBuf) -> ShaderRef {
130    ShaderRef::Path(AssetPath::from_path_buf(path).with_source("embedded"))
131}
132
133/// Sets up the entire PBR infrastructure of bevy.
134pub struct PbrPlugin {
135    /// Controls if the prepass is enabled for the [`StandardMaterial`].
136    /// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
137    pub prepass_enabled: bool,
138    /// Controls if [`DeferredPbrLightingPlugin`] is added.
139    pub add_default_deferred_lighting_plugin: bool,
140    /// Controls if GPU [`MeshUniform`] building is enabled.
141    ///
142    /// This requires compute shader support and so will be forcibly disabled if
143    /// the platform doesn't support those.
144    pub use_gpu_instance_buffer_builder: bool,
145    /// Debugging flags that can optionally be set when constructing the renderer.
146    pub debug_flags: RenderDebugFlags,
147    /// Builds and inserts `StandardMaterial` when loading glTF files
148    pub gltf_enable_standard_materials: bool,
149}
150
151impl Default for PbrPlugin {
152    fn default() -> Self {
153        Self {
154            prepass_enabled: true,
155            add_default_deferred_lighting_plugin: true,
156            use_gpu_instance_buffer_builder: true,
157            debug_flags: RenderDebugFlags::default(),
158            gltf_enable_standard_materials: true,
159        }
160    }
161}
162
163/// A resource that stores the spatio-temporal blue noise texture.
164#[derive(Resource)]
165pub struct Bluenoise {
166    /// Texture handle for spatio-temporal blue noise
167    pub texture: Handle<Image>,
168}
169
170/// LTC (Linearly Transformed Cosines) LUT textures for area light shading.
171///
172/// It is a texture array containing 2 LUT textures:
173/// The first entry encodes the 4 non-trivial elements of the inverse GGX LTC matrix.
174/// The second entry encodes amplitude and Fresnel-related weights.
175///
176/// [LUT source and fitting code](https://github.com/selfshadow/ltc_code/blob/master/fit/results)
177#[derive(Resource, Clone)]
178pub struct AreaLightLuts {
179    pub image: Handle<Image>,
180}
181
182// See https://github.com/bevyengine/bevy/pull/23737 for information on how the LUT was generated.
183/// The split-sum approximation LUT (`F_AB`) indexed by (`NdotV`, `perceptual_roughness`).
184#[derive(Resource, Clone)]
185pub struct DfgLut {
186    pub texture: Handle<Image>,
187}
188
189impl Plugin for PbrPlugin {
190    fn build(&self, app: &mut App) {
191        load_shader_library!(app, "render/pbr_types.wgsl");
192        load_shader_library!(app, "render/pbr_bindings.wgsl");
193        load_shader_library!(app, "render/utils.wgsl");
194        load_shader_library!(app, "render/clustered_forward.wgsl");
195        load_shader_library!(app, "render/pbr_lighting.wgsl");
196        load_shader_library!(app, "render/shadows.wgsl");
197        load_shader_library!(app, "deferred/pbr_deferred_types.wgsl");
198        load_shader_library!(app, "deferred/pbr_deferred_functions.wgsl");
199        load_shader_library!(app, "render/shadow_sampling.wgsl");
200        load_shader_library!(app, "render/pbr_functions.wgsl");
201        load_shader_library!(app, "render/rgb9e5.wgsl");
202        load_shader_library!(app, "render/pbr_ambient.wgsl");
203        load_shader_library!(app, "render/pbr_fragment.wgsl");
204        load_shader_library!(app, "render/pbr.wgsl");
205        load_shader_library!(app, "render/pbr_prepass_functions.wgsl");
206        load_shader_library!(app, "render/pbr_prepass.wgsl");
207        load_shader_library!(app, "render/parallax_mapping.wgsl");
208        load_shader_library!(app, "render/view_transformations.wgsl");
209
210        // Setup dummy shaders for when MeshletPlugin is not used to prevent shader import errors.
211        load_shader_library!(app, "meshlet/dummy_visibility_buffer_resolve.wgsl");
212
213        app.register_asset_reflect::<StandardMaterial>()
214            .init_resource::<DefaultOpaqueRendererMethod>()
215            .add_plugins((
216                MeshRenderPlugin {
217                    use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder,
218                    debug_flags: self.debug_flags,
219                },
220                MaterialsPlugin {
221                    debug_flags: self.debug_flags,
222                },
223                MaterialPlugin::<StandardMaterial> {
224                    debug_flags: self.debug_flags,
225                    ..Default::default()
226                },
227                ScreenSpaceAmbientOcclusionPlugin,
228                FogPlugin,
229                ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
230                SyncComponentPlugin::<ShadowFilteringMethod, Self>::default(),
231                LightmapPlugin,
232                LightProbePlugin,
233                GpuMeshPreprocessPlugin {
234                    use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder,
235                },
236                VolumetricFogPlugin,
237                ScreenSpaceReflectionsPlugin,
238                ScreenSpaceTransmissionPlugin,
239                ClusteredDecalPlugin,
240                ContactShadowsPlugin,
241            ))
242            .add_plugins((
243                decal::ForwardDecalPlugin,
244                SyncComponentPlugin::<DirectionalLight, Self>::default(),
245                SyncComponentPlugin::<PointLight, Self>::default(),
246                SyncComponentPlugin::<SpotLight, Self>::default(),
247                SyncComponentPlugin::<RectLight, Self>::default(),
248                SyncComponentPlugin::<AmbientLight, Self>::default(),
249            ))
250            .add_plugins((
251                ScatteringMediumPlugin,
252                AtmospherePlugin,
253                GpuClusteringPlugin,
254            ));
255
256        #[cfg(feature = "bevy_gltf")]
257        if self.gltf_enable_standard_materials {
258            gltf::add_gltf(app);
259        }
260
261        if self.add_default_deferred_lighting_plugin {
262            app.add_plugins(DeferredPbrLightingPlugin);
263        }
264
265        // Initialize the default material handle.
266        app.world_mut()
267            .resource_mut::<Assets<StandardMaterial>>()
268            .insert(
269                &Handle::<StandardMaterial>::default(),
270                StandardMaterial {
271                    base_color: Color::srgb(1.0, 0.0, 0.5),
272                    ..Default::default()
273                },
274            )
275            .unwrap();
276
277        let has_bluenoise = app
278            .get_sub_app(RenderApp)
279            .is_some_and(|render_app| render_app.world().is_resource_added::<Bluenoise>());
280
281        if !has_bluenoise {
282            let mut images = app.world_mut().resource_mut::<Assets<Image>>();
283            #[cfg(feature = "bluenoise_texture")]
284            let handle = {
285                let mut image = Image::from_buffer(
286                    include_bytes!("bluenoise/stbn.ktx2"),
287                    bevy_image::ImageType::Extension("ktx2"),
288                    bevy_image::CompressedImageFormats::NONE,
289                    false,
290                    ImageSampler::Default,
291                    RenderAssetUsages::RENDER_WORLD,
292                )
293                .expect("Failed to decode embedded blue-noise texture");
294                image.texture_descriptor.label = Some("bluenoise");
295                images.add(image)
296            };
297
298            #[cfg(not(feature = "bluenoise_texture"))]
299            let handle = { images.add(stbn_placeholder()) };
300
301            if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
302                render_app
303                    .world_mut()
304                    .insert_resource(Bluenoise { texture: handle });
305            }
306        }
307
308        let has_area_light_luts = app
309            .get_sub_app(RenderApp)
310            .is_some_and(|render_app| render_app.world().is_resource_added::<AreaLightLuts>());
311
312        if !has_area_light_luts {
313            let mut images = app.world_mut().resource_mut::<Assets<Image>>();
314            #[cfg(feature = "area_light_luts")]
315            let handle = {
316                let mut image = Image::from_buffer(
317                    include_bytes!("ltc/ltc.ktx2"),
318                    bevy_image::ImageType::Extension("ktx2"),
319                    bevy_image::CompressedImageFormats::NONE,
320                    false,
321                    ImageSampler::linear(),
322                    RenderAssetUsages::RENDER_WORLD,
323                )
324                .expect("Failed to decode embedded LTC LUTs");
325                image.texture_descriptor.label = Some("area_light_luts");
326                images.add(image)
327            };
328            #[cfg(not(feature = "area_light_luts"))]
329            let handle = images.add(area_light_luts_placeholder());
330
331            let area_light_luts = AreaLightLuts { image: handle };
332            if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
333                render_app.world_mut().insert_resource(area_light_luts);
334            }
335        }
336
337        let has_dfg_lut = app
338            .get_sub_app(RenderApp)
339            .is_some_and(|render_app| render_app.world().is_resource_added::<DfgLut>());
340
341        if !has_dfg_lut {
342            #[cfg(feature = "dfg_lut")]
343            let texture = app.world_mut().resource_mut::<Assets<Image>>().add(
344                Image::from_buffer(
345                    include_bytes!("environment_map/dfg.ktx2"),
346                    bevy_image::ImageType::Extension("ktx2"),
347                    bevy_image::CompressedImageFormats::NONE,
348                    false,
349                    ImageSampler::linear(),
350                    RenderAssetUsages::RENDER_WORLD,
351                )
352                .expect("Failed to decode embedded DFG LUT"),
353            );
354            #[cfg(not(feature = "dfg_lut"))]
355            let texture = Handle::default();
356
357            if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
358                render_app.world_mut().insert_resource(DfgLut { texture });
359            }
360        }
361
362        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
363            return;
364        };
365
366        // Extract the required data from the main world
367        render_app
368            .add_systems(
369                RenderStartup,
370                (
371                    init_shadow_samplers,
372                    init_global_clusterable_object_meta,
373                    init_fallback_bindless_resources,
374                ),
375            )
376            .add_systems(
377                ExtractSchedule,
378                (
379                    extract_clusters_for_cpu_clustering
380                        .run_if(not(gpu_clustering_is_enabled_during_extraction)),
381                    extract_clusters_for_gpu_clustering
382                        .run_if(gpu_clustering_is_enabled_during_extraction),
383                ),
384            )
385            .add_systems(
386                ExtractSchedule,
387                (
388                    extract_lights,
389                    extract_ambient_light_resource,
390                    extract_ambient_light,
391                    extract_shadow_filtering_method,
392                    extract_shadow_lod_origin,
393                    late_sweep_material_instances,
394                ),
395            )
396            .add_systems(
397                Render,
398                (
399                    prepare_lights
400                        .in_set(RenderSystems::CreateViews)
401                        .after(sort_cameras),
402                    prepare_clusters_for_cpu_clustering
403                        .in_set(RenderSystems::PrepareResources)
404                        .run_if(
405                            |global_cluster_settings: Res<GlobalClusterSettings>| -> bool {
406                                global_cluster_settings.gpu_clustering.is_none()
407                            },
408                        ),
409                ),
410            )
411            .init_gpu_resource::<LightMeta>()
412            .init_gpu_resource::<RenderMaterialBindings>()
413            .init_resource::<RenderShadowLodOrigin>()
414            .allow_ambiguous_resource::<RenderMaterialBindings>();
415
416        render_app.world_mut().add_observer(add_light_view_entities);
417        render_app
418            .world_mut()
419            .add_observer(remove_light_view_entities);
420        render_app
421            .world_mut()
422            .add_observer(remove_point_and_spot_light_view_entities);
423
424        render_app.add_systems(
425            Core3d,
426            (
427                per_view_shadow_pass::<EARLY_SHADOW_PASS>
428                    .after(early_prepass_build_indirect_parameters)
429                    .before(early_downsample_depth)
430                    .before(per_view_shadow_pass::<LATE_SHADOW_PASS>),
431                per_view_shadow_pass::<LATE_SHADOW_PASS>
432                    .after(late_prepass_build_indirect_parameters)
433                    .before(main_build_indirect_parameters)
434                    .before(Core3dSystems::MainPass),
435                shared_shadow_pass::<EARLY_SHADOW_PASS>
436                    .after(early_prepass_build_indirect_parameters)
437                    .before(early_downsample_depth)
438                    .before(shared_shadow_pass::<LATE_SHADOW_PASS>),
439                shared_shadow_pass::<LATE_SHADOW_PASS>
440                    .after(late_prepass_build_indirect_parameters)
441                    .before(main_build_indirect_parameters)
442                    .before(Core3dSystems::MainPass),
443            ),
444        );
445    }
446
447    fn finish(&self, app: &mut App) {
448        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
449            return;
450        };
451
452        let global_cluster_settings = make_global_cluster_settings(render_app.world());
453        app.insert_resource(global_cluster_settings);
454    }
455}
456
457pub fn stbn_placeholder() -> Image {
458    let format = TextureFormat::Rgba8Unorm;
459    let data = vec![255, 0, 255, 255];
460    Image {
461        data: Some(data),
462        data_order: TextureDataOrder::default(),
463        texture_descriptor: TextureDescriptor {
464            size: Extent3d::default(),
465            format,
466            dimension: TextureDimension::D2,
467            label: Some("bluenoise_placeholder"),
468            mip_level_count: 1,
469            sample_count: 1,
470            usage: TextureUsages::TEXTURE_BINDING,
471            view_formats: &[],
472        },
473        sampler: ImageSampler::Default,
474        texture_view_descriptor: None,
475        asset_usage: RenderAssetUsages::RENDER_WORLD,
476        copy_on_resize: false,
477    }
478}
479
480pub fn area_light_luts_placeholder() -> Image {
481    let format = TextureFormat::Rgba16Float;
482    let data = vec![0; 16];
483    Image {
484        data: Some(data),
485        data_order: TextureDataOrder::default(),
486        texture_descriptor: TextureDescriptor {
487            size: Extent3d {
488                width: 1,
489                height: 1,
490                depth_or_array_layers: 2,
491            },
492            format,
493            dimension: TextureDimension::D2,
494            label: Some("area_light_luts_placeholder"),
495            mip_level_count: 1,
496            sample_count: 1,
497            usage: TextureUsages::TEXTURE_BINDING,
498            view_formats: &[],
499        },
500        sampler: ImageSampler::Default,
501        texture_view_descriptor: Some(TextureViewDescriptor {
502            dimension: Some(TextureViewDimension::D2Array),
503            ..Default::default()
504        }),
505        asset_usage: RenderAssetUsages::RENDER_WORLD,
506        copy_on_resize: false,
507    }
508}
509
510impl SyncComponent<PbrPlugin> for DirectionalLight {
511    type Target = (
512        Self,
513        ExtractedDirectionalLight,
514        RenderExtractedShadowMapVisibleEntities,
515        RenderShadowMapVisibleEntities,
516        DirectionalLightViewEntities,
517    );
518}
519impl SyncComponent<PbrPlugin> for PointLight {
520    type Target = (
521        Self,
522        ExtractedPointLight,
523        RenderExtractedShadowMapVisibleEntities,
524        RenderShadowMapVisibleEntities,
525        PointAndSpotLightViewEntities,
526    );
527}
528impl SyncComponent<PbrPlugin> for SpotLight {
529    type Target = (
530        Self,
531        ExtractedPointLight,
532        RenderExtractedShadowMapVisibleEntities,
533        RenderShadowMapVisibleEntities,
534        PointAndSpotLightViewEntities,
535    );
536}
537impl SyncComponent<PbrPlugin> for RectLight {
538    type Target = (Self, ExtractedRectLight);
539}
540impl SyncComponent<PbrPlugin> for AmbientLight {
541    type Target = Self;
542}
543impl SyncComponent<PbrPlugin> for ShadowFilteringMethod {
544    type Target = Self;
545}