1use bevy_app::{App, Plugin};
4use bevy_asset::{load_embedded_asset, AssetServer, Handle};
5use bevy_core_pipeline::{
6 core_3d::{
7 graph::{Core3d, Node3d},
8 DEPTH_TEXTURE_SAMPLING_SUPPORTED,
9 },
10 prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
11 FullscreenShader,
12};
13use bevy_derive::{Deref, DerefMut};
14use bevy_ecs::{
15 component::Component,
16 entity::Entity,
17 query::{Has, QueryItem, With},
18 reflect::ReflectComponent,
19 resource::Resource,
20 schedule::IntoScheduleConfigs as _,
21 system::{lifetimeless::Read, Commands, Query, Res, ResMut},
22 world::World,
23};
24use bevy_image::BevyDefault as _;
25use bevy_light::EnvironmentMapLight;
26use bevy_reflect::{std_traits::ReflectDefault, Reflect};
27use bevy_render::{
28 diagnostic::RecordDiagnostics,
29 extract_component::{ExtractComponent, ExtractComponentPlugin},
30 render_graph::{
31 NodeRunError, RenderGraph, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner,
32 },
33 render_resource::{
34 binding_types, AddressMode, BindGroupEntries, BindGroupLayoutDescriptor,
35 BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites,
36 DynamicUniformBuffer, FilterMode, FragmentState, Operations, PipelineCache,
37 RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
38 SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType, SpecializedRenderPipeline,
39 SpecializedRenderPipelines, TextureFormat, TextureSampleType,
40 },
41 renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
42 view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset},
43 Render, RenderApp, RenderStartup, RenderSystems,
44};
45use bevy_shader::{load_shader_library, Shader};
46use bevy_utils::{once, prelude::default};
47use tracing::info;
48
49use crate::{
50 binding_arrays_are_usable, graph::NodePbr, ExtractedAtmosphere, MeshPipelineViewLayoutKey,
51 MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes,
52 ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset,
53 ViewLightsUniformOffset,
54};
55
56pub struct ScreenSpaceReflectionsPlugin;
60
61#[derive(Clone, Copy, Component, Reflect)]
86#[reflect(Component, Default, Clone)]
87#[require(DepthPrepass, DeferredPrepass)]
88#[doc(alias = "Ssr")]
89pub struct ScreenSpaceReflections {
90 pub perceptual_roughness_threshold: f32,
93
94 pub thickness: f32,
99
100 pub linear_steps: u32,
107
108 pub linear_march_exponent: f32,
117
118 pub bisection_steps: u32,
123
124 pub use_secant: bool,
128}
129
130#[derive(Clone, Copy, Component, ShaderType)]
135pub struct ScreenSpaceReflectionsUniform {
136 perceptual_roughness_threshold: f32,
137 thickness: f32,
138 linear_steps: u32,
139 linear_march_exponent: f32,
140 bisection_steps: u32,
141 use_secant: u32,
143}
144
145#[derive(Default)]
147pub struct ScreenSpaceReflectionsNode;
148
149#[derive(Component, Deref, DerefMut)]
151pub struct ScreenSpaceReflectionsPipelineId(pub CachedRenderPipelineId);
152
153#[derive(Resource)]
156pub struct ScreenSpaceReflectionsPipeline {
157 mesh_view_layouts: MeshPipelineViewLayouts,
158 color_sampler: Sampler,
159 depth_linear_sampler: Sampler,
160 depth_nearest_sampler: Sampler,
161 bind_group_layout: BindGroupLayoutDescriptor,
162 binding_arrays_are_usable: bool,
163 fullscreen_shader: FullscreenShader,
164 fragment_shader: Handle<Shader>,
165}
166
167#[derive(Resource, Default, Deref, DerefMut)]
169pub struct ScreenSpaceReflectionsBuffer(pub DynamicUniformBuffer<ScreenSpaceReflectionsUniform>);
170
171#[derive(Component, Default, Deref, DerefMut)]
174pub struct ViewScreenSpaceReflectionsUniformOffset(u32);
175
176#[derive(Clone, Copy, PartialEq, Eq, Hash)]
178pub struct ScreenSpaceReflectionsPipelineKey {
179 mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
180 is_hdr: bool,
181 has_environment_maps: bool,
182 has_atmosphere: bool,
183}
184
185impl Plugin for ScreenSpaceReflectionsPlugin {
186 fn build(&self, app: &mut App) {
187 load_shader_library!(app, "ssr.wgsl");
188 load_shader_library!(app, "raymarch.wgsl");
189
190 app.add_plugins(ExtractComponentPlugin::<ScreenSpaceReflections>::default());
191
192 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
193 return;
194 };
195
196 render_app
197 .init_resource::<ScreenSpaceReflectionsBuffer>()
198 .init_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>()
199 .add_systems(
200 RenderStartup,
201 (
202 init_screen_space_reflections_pipeline,
203 add_screen_space_reflections_render_graph_edges,
204 ),
205 )
206 .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare))
207 .add_systems(
208 Render,
209 prepare_ssr_settings.in_set(RenderSystems::PrepareResources),
210 )
211 .add_render_graph_node::<ViewNodeRunner<ScreenSpaceReflectionsNode>>(
214 Core3d,
215 NodePbr::ScreenSpaceReflections,
216 );
217 }
218}
219
220fn add_screen_space_reflections_render_graph_edges(mut render_graph: ResMut<RenderGraph>) {
221 let subgraph = render_graph.sub_graph_mut(Core3d);
222
223 subgraph.add_node_edge(NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass);
224
225 if subgraph
226 .get_node_state(NodePbr::DeferredLightingPass)
227 .is_ok()
228 {
229 subgraph.add_node_edge(
230 NodePbr::DeferredLightingPass,
231 NodePbr::ScreenSpaceReflections,
232 );
233 }
234}
235
236impl Default for ScreenSpaceReflections {
237 fn default() -> Self {
242 Self {
243 perceptual_roughness_threshold: 0.1,
244 linear_steps: 16,
245 bisection_steps: 4,
246 use_secant: true,
247 thickness: 0.25,
248 linear_march_exponent: 1.0,
249 }
250 }
251}
252
253impl ViewNode for ScreenSpaceReflectionsNode {
254 type ViewQuery = (
255 Read<ViewTarget>,
256 Read<ViewUniformOffset>,
257 Read<ViewLightsUniformOffset>,
258 Read<ViewFogUniformOffset>,
259 Read<ViewLightProbesUniformOffset>,
260 Read<ViewScreenSpaceReflectionsUniformOffset>,
261 Read<ViewEnvironmentMapUniformOffset>,
262 Read<MeshViewBindGroup>,
263 Read<ScreenSpaceReflectionsPipelineId>,
264 );
265
266 fn run<'w>(
267 &self,
268 _: &mut RenderGraphContext,
269 render_context: &mut RenderContext<'w>,
270 (
271 view_target,
272 view_uniform_offset,
273 view_lights_offset,
274 view_fog_offset,
275 view_light_probes_offset,
276 view_ssr_offset,
277 view_environment_map_offset,
278 view_bind_group,
279 ssr_pipeline_id,
280 ): QueryItem<'w, '_, Self::ViewQuery>,
281 world: &'w World,
282 ) -> Result<(), NodeRunError> {
283 let pipeline_cache = world.resource::<PipelineCache>();
285 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else {
286 return Ok(());
287 };
288
289 let diagnostics = render_context.diagnostic_recorder();
290
291 let postprocess = view_target.post_process_write();
293
294 let ssr_pipeline = world.resource::<ScreenSpaceReflectionsPipeline>();
296 let ssr_bind_group = render_context.render_device().create_bind_group(
297 "SSR bind group",
298 &pipeline_cache.get_bind_group_layout(&ssr_pipeline.bind_group_layout),
299 &BindGroupEntries::sequential((
300 postprocess.source,
301 &ssr_pipeline.color_sampler,
302 &ssr_pipeline.depth_linear_sampler,
303 &ssr_pipeline.depth_nearest_sampler,
304 )),
305 );
306
307 let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
309 label: Some("ssr"),
310 color_attachments: &[Some(RenderPassColorAttachment {
311 view: postprocess.destination,
312 depth_slice: None,
313 resolve_target: None,
314 ops: Operations::default(),
315 })],
316 depth_stencil_attachment: None,
317 timestamp_writes: None,
318 occlusion_query_set: None,
319 });
320 let pass_span = diagnostics.pass_span(&mut render_pass, "ssr");
321
322 render_pass.set_render_pipeline(render_pipeline);
324 render_pass.set_bind_group(
325 0,
326 &view_bind_group.main,
327 &[
328 view_uniform_offset.offset,
329 view_lights_offset.offset,
330 view_fog_offset.offset,
331 **view_light_probes_offset,
332 **view_ssr_offset,
333 **view_environment_map_offset,
334 ],
335 );
336 render_pass.set_bind_group(1, &view_bind_group.binding_array, &[]);
337
338 render_pass.set_bind_group(2, &ssr_bind_group, &[]);
340 render_pass.draw(0..3, 0..1);
341
342 pass_span.end(&mut render_pass);
343
344 Ok(())
345 }
346}
347
348pub fn init_screen_space_reflections_pipeline(
349 mut commands: Commands,
350 render_device: Res<RenderDevice>,
351 render_adapter: Res<RenderAdapter>,
352 mesh_view_layouts: Res<MeshPipelineViewLayouts>,
353 fullscreen_shader: Res<FullscreenShader>,
354 asset_server: Res<AssetServer>,
355) {
356 let bind_group_layout = BindGroupLayoutDescriptor::new(
358 "SSR bind group layout",
359 &BindGroupLayoutEntries::sequential(
360 ShaderStages::FRAGMENT,
361 (
362 binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
363 binding_types::sampler(SamplerBindingType::Filtering),
364 binding_types::sampler(SamplerBindingType::Filtering),
365 binding_types::sampler(SamplerBindingType::NonFiltering),
366 ),
367 ),
368 );
369
370 let color_sampler = render_device.create_sampler(&SamplerDescriptor {
373 label: "SSR color sampler".into(),
374 address_mode_u: AddressMode::ClampToEdge,
375 address_mode_v: AddressMode::ClampToEdge,
376 mag_filter: FilterMode::Linear,
377 min_filter: FilterMode::Linear,
378 ..default()
379 });
380
381 let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
382 label: "SSR depth linear sampler".into(),
383 address_mode_u: AddressMode::ClampToEdge,
384 address_mode_v: AddressMode::ClampToEdge,
385 mag_filter: FilterMode::Linear,
386 min_filter: FilterMode::Linear,
387 ..default()
388 });
389
390 let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
391 label: "SSR depth nearest sampler".into(),
392 address_mode_u: AddressMode::ClampToEdge,
393 address_mode_v: AddressMode::ClampToEdge,
394 mag_filter: FilterMode::Nearest,
395 min_filter: FilterMode::Nearest,
396 ..default()
397 });
398
399 commands.insert_resource(ScreenSpaceReflectionsPipeline {
400 mesh_view_layouts: mesh_view_layouts.clone(),
401 color_sampler,
402 depth_linear_sampler,
403 depth_nearest_sampler,
404 bind_group_layout,
405 binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
406 fullscreen_shader: fullscreen_shader.clone(),
407 fragment_shader: load_embedded_asset!(asset_server.as_ref(), "ssr.wgsl"),
410 });
411}
412
413pub fn prepare_ssr_pipelines(
415 mut commands: Commands,
416 pipeline_cache: Res<PipelineCache>,
417 mut pipelines: ResMut<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>,
418 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
419 views: Query<
420 (
421 Entity,
422 &ExtractedView,
423 Has<RenderViewLightProbes<EnvironmentMapLight>>,
424 Has<NormalPrepass>,
425 Has<MotionVectorPrepass>,
426 Has<ExtractedAtmosphere>,
427 ),
428 (
429 With<ScreenSpaceReflectionsUniform>,
430 With<DepthPrepass>,
431 With<DeferredPrepass>,
432 ),
433 >,
434) {
435 for (
436 entity,
437 extracted_view,
438 has_environment_maps,
439 has_normal_prepass,
440 has_motion_vector_prepass,
441 has_atmosphere,
442 ) in &views
443 {
444 let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(Msaa::Off)
447 | MeshPipelineViewLayoutKey::DEPTH_PREPASS
448 | MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
449 mesh_pipeline_view_key.set(
450 MeshPipelineViewLayoutKey::NORMAL_PREPASS,
451 has_normal_prepass,
452 );
453 mesh_pipeline_view_key.set(
454 MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS,
455 has_motion_vector_prepass,
456 );
457 mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::ATMOSPHERE, has_atmosphere);
458
459 let pipeline_id = pipelines.specialize(
461 &pipeline_cache,
462 &ssr_pipeline,
463 ScreenSpaceReflectionsPipelineKey {
464 mesh_pipeline_view_key,
465 is_hdr: extracted_view.hdr,
466 has_environment_maps,
467 has_atmosphere,
468 },
469 );
470
471 commands
473 .entity(entity)
474 .insert(ScreenSpaceReflectionsPipelineId(pipeline_id));
475 }
476}
477
478pub fn prepare_ssr_settings(
481 mut commands: Commands,
482 views: Query<(Entity, Option<&ScreenSpaceReflectionsUniform>), With<ExtractedView>>,
483 mut ssr_settings_buffer: ResMut<ScreenSpaceReflectionsBuffer>,
484 render_device: Res<RenderDevice>,
485 render_queue: Res<RenderQueue>,
486) {
487 let Some(mut writer) =
488 ssr_settings_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
489 else {
490 return;
491 };
492
493 for (view, ssr_uniform) in views.iter() {
494 let uniform_offset = match ssr_uniform {
495 None => 0,
496 Some(ssr_uniform) => writer.write(ssr_uniform),
497 };
498 commands
499 .entity(view)
500 .insert(ViewScreenSpaceReflectionsUniformOffset(uniform_offset));
501 }
502}
503
504impl ExtractComponent for ScreenSpaceReflections {
505 type QueryData = Read<ScreenSpaceReflections>;
506
507 type QueryFilter = ();
508
509 type Out = ScreenSpaceReflectionsUniform;
510
511 fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
512 if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
513 once!(info!(
514 "Disabling screen-space reflections on this platform because depth textures \
515 aren't supported correctly"
516 ));
517 return None;
518 }
519
520 Some((*settings).into())
521 }
522}
523
524impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
525 type Key = ScreenSpaceReflectionsPipelineKey;
526
527 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
528 let layout = self
529 .mesh_view_layouts
530 .get_view_layout(key.mesh_pipeline_view_key);
531 let layout = vec![
532 layout.main_layout.clone(),
533 layout.binding_array_layout.clone(),
534 self.bind_group_layout.clone(),
535 ];
536
537 let mut shader_defs = vec![
538 "DEPTH_PREPASS".into(),
539 "DEFERRED_PREPASS".into(),
540 "SCREEN_SPACE_REFLECTIONS".into(),
541 ];
542
543 if key.has_environment_maps {
544 shader_defs.push("ENVIRONMENT_MAP".into());
545 }
546
547 if self.binding_arrays_are_usable {
548 shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
549 }
550
551 if key.has_atmosphere {
552 shader_defs.push("ATMOSPHERE".into());
553 }
554
555 #[cfg(not(target_arch = "wasm32"))]
556 shader_defs.push("USE_DEPTH_SAMPLERS".into());
557
558 RenderPipelineDescriptor {
559 label: Some("SSR pipeline".into()),
560 layout,
561 vertex: self.fullscreen_shader.to_vertex_state(),
562 fragment: Some(FragmentState {
563 shader: self.fragment_shader.clone(),
564 shader_defs,
565 targets: vec![Some(ColorTargetState {
566 format: if key.is_hdr {
567 ViewTarget::TEXTURE_FORMAT_HDR
568 } else {
569 TextureFormat::bevy_default()
570 },
571 blend: None,
572 write_mask: ColorWrites::ALL,
573 })],
574 ..default()
575 }),
576 ..default()
577 }
578 }
579}
580
581impl From<ScreenSpaceReflections> for ScreenSpaceReflectionsUniform {
582 fn from(settings: ScreenSpaceReflections) -> Self {
583 Self {
584 perceptual_roughness_threshold: settings.perceptual_roughness_threshold,
585 thickness: settings.thickness,
586 linear_steps: settings.linear_steps,
587 linear_march_exponent: settings.linear_march_exponent,
588 bisection_steps: settings.bisection_steps,
589 use_secant: settings.use_secant as u32,
590 }
591 }
592}