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