1use core::ops::Range;
4
5use bevy_app::{App, Plugin};
6use bevy_asset::{load_embedded_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, "ssr.wgsl");
196 load_shader_library!(app, "raymarch.wgsl");
197
198 app.add_plugins(ExtractComponentPlugin::<ScreenSpaceReflections>::default());
199
200 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
201 return;
202 };
203
204 render_app
205 .init_gpu_resource::<ScreenSpaceReflectionsBuffer>()
206 .init_gpu_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>()
207 .add_systems(
208 RenderStartup,
209 init_screen_space_reflections_pipeline.after(MeshPipelineSystems),
210 )
211 .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare))
212 .add_systems(
213 Render,
214 prepare_ssr_settings.in_set(RenderSystems::PrepareResources),
215 )
216 .add_systems(
217 Core3d,
218 screen_space_reflections
219 .after(deferred_lighting)
220 .before(main_opaque_pass_3d)
221 .in_set(Core3dSystems::MainPass),
222 );
223 }
224}
225
226impl Default for ScreenSpaceReflections {
227 fn default() -> Self {
232 Self {
233 min_perceptual_roughness: 0.08..0.12,
234 max_perceptual_roughness: 0.55..0.6,
235 linear_steps: 10,
236 bisection_steps: 5,
237 use_secant: true,
238 thickness: 0.25,
239 linear_march_exponent: 1.0,
240 edge_fadeout: 0.0..0.0,
241 }
242 }
243}
244
245pub fn screen_space_reflections(
246 view: ViewQuery<(
247 &ViewTarget,
248 &MeshViewBindGroup,
249 &ScreenSpaceReflectionsPipelineId,
250 )>,
251 pipeline_cache: Res<PipelineCache>,
252 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
253 bluenoise: Res<Bluenoise>,
254 render_images: Res<RenderAssets<GpuImage>>,
255 mut ctx: RenderContext,
256) {
257 let (view_target, view_bind_group, ssr_pipeline_id) = view.into_inner();
258
259 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else {
261 return;
262 };
263
264 let postprocess = view_target.post_process_write();
266
267 let Some(stbn_texture) = render_images.get(&bluenoise.texture) else {
269 return;
270 };
271 let stbn_view = stbn_texture.texture.create_view(&TextureViewDescriptor {
272 label: Some("ssr_stbn_view"),
273 dimension: Some(TextureViewDimension::D2Array),
274 ..default()
275 });
276
277 let ssr_bind_group = ctx.render_device().create_bind_group(
279 "SSR bind group",
280 &pipeline_cache.get_bind_group_layout(&ssr_pipeline.bind_group_layout),
281 &BindGroupEntries::sequential((
282 postprocess.source,
283 &ssr_pipeline.color_sampler,
284 &ssr_pipeline.depth_linear_sampler,
285 &ssr_pipeline.depth_nearest_sampler,
286 &stbn_view,
287 )),
288 );
289
290 let diagnostics = ctx.diagnostic_recorder();
291 let diagnostics = diagnostics.as_deref();
292
293 let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
295 label: Some("ssr"),
296 color_attachments: &[Some(RenderPassColorAttachment {
297 view: postprocess.destination,
298 depth_slice: None,
299 resolve_target: None,
300 ops: Operations::default(),
301 })],
302 depth_stencil_attachment: None,
303 timestamp_writes: None,
304 occlusion_query_set: None,
305 multiview_mask: None,
306 });
307 let pass_span = diagnostics.pass_span(&mut render_pass, "ssr");
308
309 render_pass.set_render_pipeline(render_pipeline);
311
312 render_pass.set_bind_group(0, &view_bind_group.main, &view_bind_group.main_offsets);
313 render_pass.set_bind_group(1, &view_bind_group.binding_array, &[]);
314
315 render_pass.set_bind_group(2, &ssr_bind_group, &[]);
317 render_pass.draw(0..3, 0..1);
318
319 pass_span.end(&mut render_pass);
320}
321
322pub fn init_screen_space_reflections_pipeline(
323 mut commands: Commands,
324 render_device: Res<RenderDevice>,
325 render_adapter: Res<RenderAdapter>,
326 mesh_view_layouts: Res<MeshPipelineViewLayouts>,
327 fullscreen_shader: Res<FullscreenShader>,
328 asset_server: Res<AssetServer>,
329) {
330 let bind_group_layout = BindGroupLayoutDescriptor::new(
332 "SSR bind group layout",
333 &BindGroupLayoutEntries::sequential(
334 ShaderStages::FRAGMENT,
335 (
336 binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
337 binding_types::sampler(SamplerBindingType::Filtering),
338 binding_types::sampler(SamplerBindingType::Filtering),
339 binding_types::sampler(SamplerBindingType::NonFiltering),
340 binding_types::texture_2d_array(TextureSampleType::Float { filterable: false }),
341 ),
342 ),
343 );
344
345 let color_sampler = render_device.create_sampler(&SamplerDescriptor {
348 label: "SSR color sampler".into(),
349 address_mode_u: AddressMode::ClampToEdge,
350 address_mode_v: AddressMode::ClampToEdge,
351 mag_filter: FilterMode::Linear,
352 min_filter: FilterMode::Linear,
353 ..default()
354 });
355
356 let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
357 label: "SSR depth linear sampler".into(),
358 address_mode_u: AddressMode::ClampToEdge,
359 address_mode_v: AddressMode::ClampToEdge,
360 mag_filter: FilterMode::Linear,
361 min_filter: FilterMode::Linear,
362 ..default()
363 });
364
365 let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
366 label: "SSR depth nearest sampler".into(),
367 address_mode_u: AddressMode::ClampToEdge,
368 address_mode_v: AddressMode::ClampToEdge,
369 mag_filter: FilterMode::Nearest,
370 min_filter: FilterMode::Nearest,
371 ..default()
372 });
373
374 commands.insert_resource(ScreenSpaceReflectionsPipeline {
375 mesh_view_layouts: mesh_view_layouts.clone(),
376 color_sampler,
377 depth_linear_sampler,
378 depth_nearest_sampler,
379 bind_group_layout,
380 binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
381 fullscreen_shader: fullscreen_shader.clone(),
382 fragment_shader: load_embedded_asset!(asset_server.as_ref(), "ssr.wgsl"),
385 });
386}
387
388pub fn prepare_ssr_pipelines(
390 mut commands: Commands,
391 pipeline_cache: Res<PipelineCache>,
392 view_key_cache: Res<ViewKeyCache>,
393 mut pipelines: ResMut<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>,
394 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
395 views: Query<
396 (Entity, &ExtractedView),
397 (
398 With<ScreenSpaceReflectionsUniform>,
399 With<DepthPrepass>,
400 With<DeferredPrepass>,
401 ),
402 >,
403) {
404 for (entity, extracted_view) in &views {
405 let Some(view_key) = view_key_cache.get(&extracted_view.retained_view_entity) else {
406 continue;
407 };
408 let pipeline_id = pipelines.specialize(
410 &pipeline_cache,
411 &ssr_pipeline,
412 ScreenSpaceReflectionsPipelineKey {
413 mesh_pipeline_view_key: (*view_key).into(),
414 target_format: extracted_view.target_format,
415 },
416 );
417
418 commands
420 .entity(entity)
421 .insert(ScreenSpaceReflectionsPipelineId(pipeline_id));
422 }
423}
424
425pub fn prepare_ssr_settings(
428 mut commands: Commands,
429 views: Query<(Entity, &ScreenSpaceReflectionsUniform), With<ExtractedView>>,
430 mut ssr_settings_buffer: ResMut<ScreenSpaceReflectionsBuffer>,
431 render_device: Res<RenderDevice>,
432 render_queue: Res<RenderQueue>,
433) {
434 let Some(mut writer) =
435 ssr_settings_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
436 else {
437 return;
438 };
439
440 for (view, ssr_uniform) in views.iter() {
441 let uniform_offset = writer.write(ssr_uniform);
442 commands
443 .entity(view)
444 .insert(ViewScreenSpaceReflectionsUniformOffset(uniform_offset));
445 }
446}
447
448impl SyncComponent for ScreenSpaceReflections {
449 type Target = (
450 ScreenSpaceReflectionsUniform,
451 ViewScreenSpaceReflectionsUniformOffset,
452 ScreenSpaceReflectionsPipelineId,
453 );
454}
455
456impl ExtractComponent for ScreenSpaceReflections {
457 type QueryData = Read<ScreenSpaceReflections>;
458 type QueryFilter = ();
459 type Out = ScreenSpaceReflectionsUniform;
460
461 fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
462 if !DEPTH_PREPASS_TEXTURE_SUPPORTED {
463 once!(info!(
464 "Disabling screen-space reflections on this platform because depth textures \
465 aren't supported correctly"
466 ));
467 return None;
468 }
469
470 Some(settings.clone().into())
471 }
472}
473
474impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
475 type Key = ScreenSpaceReflectionsPipelineKey;
476
477 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
478 let layout = self
479 .mesh_view_layouts
480 .get_view_layout(key.mesh_pipeline_view_key);
481 let layout = vec![
482 layout.main_layout,
483 layout.binding_array_layout,
484 self.bind_group_layout.clone(),
485 ];
486
487 let mut shader_defs = vec![
488 "DEPTH_PREPASS".into(),
489 "DEFERRED_PREPASS".into(),
490 "SCREEN_SPACE_REFLECTIONS".into(),
491 ];
492
493 if key
494 .mesh_pipeline_view_key
495 .contains(MeshPipelineViewLayoutKey::ENVIRONMENT_MAP)
496 {
497 shader_defs.push("ENVIRONMENT_MAP".into());
498 }
499
500 if self.binding_arrays_are_usable {
501 shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
502 }
503
504 if key
505 .mesh_pipeline_view_key
506 .contains(MeshPipelineViewLayoutKey::ATMOSPHERE)
507 {
508 shader_defs.push("ATMOSPHERE".into());
509 }
510
511 if cfg!(feature = "bluenoise_texture") {
512 shader_defs.push("BLUE_NOISE_TEXTURE".into());
513 }
514 if cfg!(feature = "dfg_lut") {
515 shader_defs.push("DFG_LUT".into());
516 }
517 if cfg!(feature = "area_light_luts") {
518 shader_defs.push("AREA_LIGHT_LUTS".into());
519 }
520
521 #[cfg(not(target_arch = "wasm32"))]
522 shader_defs.push("USE_DEPTH_SAMPLERS".into());
523
524 RenderPipelineDescriptor {
525 label: Some("SSR pipeline".into()),
526 layout,
527 vertex: self.fullscreen_shader.to_vertex_state(),
528 fragment: Some(FragmentState {
529 shader: self.fragment_shader.clone(),
530 shader_defs,
531 targets: vec![Some(ColorTargetState {
532 format: key.target_format,
533 blend: None,
534 write_mask: ColorWrites::ALL,
535 })],
536 ..default()
537 }),
538 ..default()
539 }
540 }
541}
542
543impl From<ScreenSpaceReflections> for ScreenSpaceReflectionsUniform {
544 fn from(settings: ScreenSpaceReflections) -> Self {
545 Self {
546 min_perceptual_roughness: settings.min_perceptual_roughness.start,
547 min_perceptual_roughness_fully_active: settings.min_perceptual_roughness.end,
548 max_perceptual_roughness_starts_to_fade: settings.max_perceptual_roughness.start,
549 max_perceptual_roughness: settings.max_perceptual_roughness.end,
550 edge_fadeout_no_longer_active: settings.edge_fadeout.start,
551 edge_fadeout_fully_active: settings.edge_fadeout.end,
552 thickness: settings.thickness,
553 linear_steps: settings.linear_steps,
554 linear_march_exponent: settings.linear_march_exponent,
555 bisection_steps: settings.bisection_steps,
556 use_secant: settings.use_secant as u32,
557 }
558 }
559}