1use core::ops::Range;
4
5use bevy_app::{App, Plugin};
6use bevy_asset::{AssetServer, Handle};
7use bevy_core_pipeline::{
8 core_3d::{main_opaque_pass_3d, DEPTH_PREPASS_TEXTURE_SUPPORTED},
9 prepass::{DeferredPrepass, DepthPrepass},
10 schedule::{Core3d, Core3dSystems},
11 FullscreenShader,
12};
13use bevy_derive::{Deref, DerefMut};
14use bevy_ecs::{
15 component::Component,
16 entity::Entity,
17 query::{QueryItem, With},
18 reflect::ReflectComponent,
19 resource::Resource,
20 schedule::IntoScheduleConfigs as _,
21 system::{lifetimeless::Read, Commands, Query, Res, ResMut},
22};
23use bevy_reflect::{std_traits::ReflectDefault, Reflect};
24use bevy_render::{
25 diagnostic::RecordDiagnostics,
26 extract_component::{ExtractComponent, ExtractComponentPlugin},
27 render_asset::RenderAssets,
28 render_resource::{
29 binding_types, AddressMode, BindGroupEntries, BindGroupLayoutDescriptor,
30 BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites,
31 DynamicUniformBuffer, FilterMode, FragmentState, Operations, PipelineCache,
32 RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
33 SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType, SpecializedRenderPipeline,
34 SpecializedRenderPipelines, TextureFormat, TextureSampleType, TextureViewDescriptor,
35 TextureViewDimension,
36 },
37 renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue, ViewQuery},
38 sync_component::SyncComponent,
39 texture::GpuImage,
40 view::{ExtractedView, ViewTarget},
41 GpuResourceAppExt, Render, RenderApp, RenderStartup, RenderSystems,
42};
43use bevy_shader::{load_shader_library, Shader};
44use bevy_utils::{once, prelude::default};
45use tracing::info;
46
47use crate::{
48 binding_arrays_are_usable, deferred::deferred_lighting, Bluenoise, MeshPipelineSystems,
49 MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, ViewKeyCache,
50};
51
52pub struct ScreenSpaceReflectionsPlugin;
56
57#[derive(Clone, Component, Reflect)]
79#[reflect(Component, Default, Clone)]
80#[require(DepthPrepass, DeferredPrepass)]
81#[doc(alias = "Ssr")]
82pub struct ScreenSpaceReflections {
83 pub min_perceptual_roughness: Range<f32>,
88
89 pub max_perceptual_roughness: Range<f32>,
94
95 pub thickness: f32,
100
101 pub linear_steps: u32,
108
109 pub linear_march_exponent: f32,
118
119 pub edge_fadeout: Range<f32>,
126
127 pub bisection_steps: u32,
132
133 pub use_secant: bool,
137}
138
139#[derive(Clone, Copy, Component, ShaderType)]
144pub struct ScreenSpaceReflectionsUniform {
145 min_perceptual_roughness: f32,
146 min_perceptual_roughness_fully_active: f32,
147 max_perceptual_roughness_starts_to_fade: f32,
148 max_perceptual_roughness: f32,
149 edge_fadeout_fully_active: f32,
150 edge_fadeout_no_longer_active: f32,
151 thickness: f32,
152 linear_steps: u32,
153 linear_march_exponent: f32,
154 bisection_steps: u32,
155 use_secant: u32,
157}
158
159#[derive(Component, Deref, DerefMut)]
161pub struct ScreenSpaceReflectionsPipelineId(pub CachedRenderPipelineId);
162
163#[derive(Resource)]
166pub struct ScreenSpaceReflectionsPipeline {
167 mesh_view_layouts: MeshPipelineViewLayouts,
168 color_sampler: Sampler,
169 depth_linear_sampler: Sampler,
170 depth_nearest_sampler: Sampler,
171 bind_group_layout: BindGroupLayoutDescriptor,
172 binding_arrays_are_usable: bool,
173 fullscreen_shader: FullscreenShader,
174 fragment_shader: Handle<Shader>,
175}
176
177#[derive(Resource, Default, Deref, DerefMut)]
179pub struct ScreenSpaceReflectionsBuffer(pub DynamicUniformBuffer<ScreenSpaceReflectionsUniform>);
180
181#[derive(Component, Default, Deref, DerefMut)]
184pub struct ViewScreenSpaceReflectionsUniformOffset(u32);
185
186#[derive(Clone, Copy, PartialEq, Eq, Hash)]
188pub struct ScreenSpaceReflectionsPipelineKey {
189 mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
190 target_format: TextureFormat,
191}
192
193impl Plugin for ScreenSpaceReflectionsPlugin {
194 fn build(&self, app: &mut App) {
195 load_shader_library!(app, "raymarch.wesl");
196
197 app.add_plugins(ExtractComponentPlugin::<ScreenSpaceReflections>::default());
198
199 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
200 return;
201 };
202
203 render_app
204 .init_gpu_resource::<ScreenSpaceReflectionsBuffer>()
205 .init_gpu_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>()
206 .add_systems(
207 RenderStartup,
208 init_screen_space_reflections_pipeline.after(MeshPipelineSystems),
209 )
210 .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare))
211 .add_systems(
212 Render,
213 prepare_ssr_settings.in_set(RenderSystems::PrepareResources),
214 )
215 .add_systems(
216 Core3d,
217 screen_space_reflections
218 .after(deferred_lighting)
219 .before(main_opaque_pass_3d)
220 .in_set(Core3dSystems::MainPass),
221 );
222 }
223}
224
225impl Default for ScreenSpaceReflections {
226 fn default() -> Self {
231 Self {
232 min_perceptual_roughness: 0.08..0.12,
233 max_perceptual_roughness: 0.55..0.6,
234 linear_steps: 10,
235 bisection_steps: 5,
236 use_secant: true,
237 thickness: 0.25,
238 linear_march_exponent: 1.0,
239 edge_fadeout: 0.0..0.0,
240 }
241 }
242}
243
244pub fn screen_space_reflections(
245 view: ViewQuery<(
246 &ViewTarget,
247 &MeshViewBindGroup,
248 &ScreenSpaceReflectionsPipelineId,
249 )>,
250 pipeline_cache: Res<PipelineCache>,
251 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
252 bluenoise: Res<Bluenoise>,
253 render_images: Res<RenderAssets<GpuImage>>,
254 mut ctx: RenderContext,
255) {
256 let (view_target, view_bind_group, ssr_pipeline_id) = view.into_inner();
257
258 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else {
260 return;
261 };
262
263 let postprocess = view_target.post_process_write();
265
266 let Some(stbn_texture) = render_images.get(&bluenoise.texture) else {
268 return;
269 };
270 let stbn_view = stbn_texture.texture.create_view(&TextureViewDescriptor {
271 label: Some("ssr_stbn_view"),
272 dimension: Some(TextureViewDimension::D2Array),
273 ..default()
274 });
275
276 let ssr_bind_group = ctx.render_device().create_bind_group(
278 "SSR bind group",
279 &pipeline_cache.get_bind_group_layout(&ssr_pipeline.bind_group_layout),
280 &BindGroupEntries::sequential((
281 postprocess.source,
282 &ssr_pipeline.color_sampler,
283 &ssr_pipeline.depth_linear_sampler,
284 &ssr_pipeline.depth_nearest_sampler,
285 &stbn_view,
286 )),
287 );
288
289 let diagnostics = ctx.diagnostic_recorder();
290 let diagnostics = diagnostics.as_deref();
291
292 let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
294 label: Some("ssr"),
295 color_attachments: &[Some(RenderPassColorAttachment {
296 view: postprocess.destination,
297 depth_slice: None,
298 resolve_target: None,
299 ops: Operations::default(),
300 })],
301 depth_stencil_attachment: None,
302 timestamp_writes: None,
303 occlusion_query_set: None,
304 multiview_mask: None,
305 });
306 let pass_span = diagnostics.pass_span(&mut render_pass, "ssr");
307
308 render_pass.set_render_pipeline(render_pipeline);
310
311 render_pass.set_bind_group(0, &view_bind_group.main, &view_bind_group.main_offsets);
312 render_pass.set_bind_group(1, &view_bind_group.binding_array, &[]);
313
314 render_pass.set_bind_group(2, &ssr_bind_group, &[]);
316 render_pass.draw(0..3, 0..1);
317
318 pass_span.end(&mut render_pass);
319}
320
321pub fn init_screen_space_reflections_pipeline(
322 mut commands: Commands,
323 render_device: Res<RenderDevice>,
324 render_adapter: Res<RenderAdapter>,
325 mesh_view_layouts: Res<MeshPipelineViewLayouts>,
326 fullscreen_shader: Res<FullscreenShader>,
327 asset_server: Res<AssetServer>,
328) {
329 let bind_group_layout = BindGroupLayoutDescriptor::new(
331 "SSR bind group layout",
332 &BindGroupLayoutEntries::sequential(
333 ShaderStages::FRAGMENT,
334 (
335 binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
336 binding_types::sampler(SamplerBindingType::Filtering),
337 binding_types::sampler(SamplerBindingType::Filtering),
338 binding_types::sampler(SamplerBindingType::NonFiltering),
339 binding_types::texture_2d_array(TextureSampleType::Float { filterable: false }),
340 ),
341 ),
342 );
343
344 let color_sampler = render_device.create_sampler(&SamplerDescriptor {
347 label: "SSR color sampler".into(),
348 address_mode_u: AddressMode::ClampToEdge,
349 address_mode_v: AddressMode::ClampToEdge,
350 mag_filter: FilterMode::Linear,
351 min_filter: FilterMode::Linear,
352 ..default()
353 });
354
355 let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
356 label: "SSR depth linear sampler".into(),
357 address_mode_u: AddressMode::ClampToEdge,
358 address_mode_v: AddressMode::ClampToEdge,
359 mag_filter: FilterMode::Linear,
360 min_filter: FilterMode::Linear,
361 ..default()
362 });
363
364 let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
365 label: "SSR depth nearest sampler".into(),
366 address_mode_u: AddressMode::ClampToEdge,
367 address_mode_v: AddressMode::ClampToEdge,
368 mag_filter: FilterMode::Nearest,
369 min_filter: FilterMode::Nearest,
370 ..default()
371 });
372
373 commands.insert_resource(ScreenSpaceReflectionsPipeline {
374 mesh_view_layouts: mesh_view_layouts.clone(),
375 color_sampler,
376 depth_linear_sampler,
377 depth_nearest_sampler,
378 bind_group_layout,
379 binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
380 fullscreen_shader: fullscreen_shader.clone(),
381 fragment_shader: asset_server.load("embedded://bevy_pbr/ssr.wesl"),
384 });
385}
386
387pub fn prepare_ssr_pipelines(
389 mut commands: Commands,
390 pipeline_cache: Res<PipelineCache>,
391 view_key_cache: Res<ViewKeyCache>,
392 mut pipelines: ResMut<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>,
393 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
394 views: Query<
395 (Entity, &ExtractedView),
396 (
397 With<ScreenSpaceReflectionsUniform>,
398 With<DepthPrepass>,
399 With<DeferredPrepass>,
400 ),
401 >,
402) {
403 for (entity, extracted_view) in &views {
404 let Some(view_key) = view_key_cache.get(&extracted_view.retained_view_entity) else {
405 continue;
406 };
407 let pipeline_id = pipelines.specialize(
409 &pipeline_cache,
410 &ssr_pipeline,
411 ScreenSpaceReflectionsPipelineKey {
412 mesh_pipeline_view_key: (*view_key).into(),
413 target_format: extracted_view.target_format,
414 },
415 );
416
417 commands
419 .entity(entity)
420 .insert(ScreenSpaceReflectionsPipelineId(pipeline_id));
421 }
422}
423
424pub fn prepare_ssr_settings(
427 mut commands: Commands,
428 views: Query<(Entity, &ScreenSpaceReflectionsUniform), With<ExtractedView>>,
429 mut ssr_settings_buffer: ResMut<ScreenSpaceReflectionsBuffer>,
430 render_device: Res<RenderDevice>,
431 render_queue: Res<RenderQueue>,
432) {
433 let Some(mut writer) =
434 ssr_settings_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
435 else {
436 return;
437 };
438
439 for (view, ssr_uniform) in views.iter() {
440 let uniform_offset = writer.write(ssr_uniform);
441 commands
442 .entity(view)
443 .insert(ViewScreenSpaceReflectionsUniformOffset(uniform_offset));
444 }
445}
446
447impl SyncComponent<RenderApp> for ScreenSpaceReflections {
448 type Target = (
449 ScreenSpaceReflectionsUniform,
450 ViewScreenSpaceReflectionsUniformOffset,
451 ScreenSpaceReflectionsPipelineId,
452 );
453}
454
455impl ExtractComponent<RenderApp> for ScreenSpaceReflections {
456 type QueryData = Read<ScreenSpaceReflections>;
457 type QueryFilter = ();
458 type Out = ScreenSpaceReflectionsUniform;
459
460 fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
461 if !DEPTH_PREPASS_TEXTURE_SUPPORTED {
462 once!(info!(
463 "Disabling screen-space reflections on this platform because depth textures \
464 aren't supported correctly"
465 ));
466 return None;
467 }
468
469 Some(settings.clone().into())
470 }
471}
472
473impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
474 type Key = ScreenSpaceReflectionsPipelineKey;
475
476 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
477 let layout = self
478 .mesh_view_layouts
479 .get_view_layout(key.mesh_pipeline_view_key);
480 let layout = vec![
481 layout.main_layout,
482 layout.binding_array_layout,
483 self.bind_group_layout.clone(),
484 ];
485
486 let mut shader_defs = vec![
487 "DEPTH_PREPASS".into(),
488 "DEFERRED_PREPASS".into(),
489 "SCREEN_SPACE_REFLECTIONS".into(),
490 ];
491
492 if key
493 .mesh_pipeline_view_key
494 .contains(MeshPipelineViewLayoutKey::ENVIRONMENT_MAP)
495 {
496 shader_defs.push("ENVIRONMENT_MAP".into());
497 }
498
499 if self.binding_arrays_are_usable {
500 shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
501 }
502
503 if key
504 .mesh_pipeline_view_key
505 .contains(MeshPipelineViewLayoutKey::ATMOSPHERE)
506 {
507 shader_defs.push("ATMOSPHERE".into());
508 }
509
510 if cfg!(feature = "bluenoise_texture") {
511 shader_defs.push("BLUE_NOISE_TEXTURE".into());
512 }
513 if cfg!(feature = "dfg_lut") {
514 shader_defs.push("DFG_LUT".into());
515 }
516 if cfg!(feature = "area_light_luts") {
517 shader_defs.push("AREA_LIGHT_LUTS".into());
518 }
519
520 #[cfg(not(target_arch = "wasm32"))]
521 shader_defs.push("USE_DEPTH_SAMPLERS".into());
522
523 RenderPipelineDescriptor {
524 label: Some("SSR pipeline".into()),
525 layout,
526 vertex: self.fullscreen_shader.to_vertex_state(),
527 fragment: Some(FragmentState {
528 shader: self.fragment_shader.clone(),
529 shader_defs,
530 targets: vec![Some(ColorTargetState {
531 format: key.target_format,
532 blend: None,
533 write_mask: ColorWrites::ALL,
534 })],
535 ..default()
536 }),
537 ..default()
538 }
539 }
540}
541
542impl From<ScreenSpaceReflections> for ScreenSpaceReflectionsUniform {
543 fn from(settings: ScreenSpaceReflections) -> Self {
544 Self {
545 min_perceptual_roughness: settings.min_perceptual_roughness.start,
546 min_perceptual_roughness_fully_active: settings.min_perceptual_roughness.end,
547 max_perceptual_roughness_starts_to_fade: settings.max_perceptual_roughness.start,
548 max_perceptual_roughness: settings.max_perceptual_roughness.end,
549 edge_fadeout_no_longer_active: settings.edge_fadeout.start,
550 edge_fadeout_fully_active: settings.edge_fadeout.end,
551 thickness: settings.thickness,
552 linear_steps: settings.linear_steps,
553 linear_march_exponent: settings.linear_march_exponent,
554 bisection_steps: settings.bisection_steps,
555 use_secant: settings.use_secant as u32,
556 }
557 }
558}