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, BindGroupLayout, BindGroupLayoutEntries,
35 CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer, FilterMode,
36 FragmentState, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
37 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
38 ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat,
39 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, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts,
51 MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset,
52 ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset,
53};
54
55pub struct ScreenSpaceReflectionsPlugin;
59
60#[derive(Clone, Copy, Component, Reflect)]
84#[reflect(Component, Default, Clone)]
85#[require(DepthPrepass, DeferredPrepass)]
86#[doc(alias = "Ssr")]
87pub struct ScreenSpaceReflections {
88 pub perceptual_roughness_threshold: f32,
91
92 pub thickness: f32,
97
98 pub linear_steps: u32,
105
106 pub linear_march_exponent: f32,
115
116 pub bisection_steps: u32,
121
122 pub use_secant: bool,
126}
127
128#[derive(Clone, Copy, Component, ShaderType)]
133pub struct ScreenSpaceReflectionsUniform {
134 perceptual_roughness_threshold: f32,
135 thickness: f32,
136 linear_steps: u32,
137 linear_march_exponent: f32,
138 bisection_steps: u32,
139 use_secant: u32,
141}
142
143#[derive(Default)]
145pub struct ScreenSpaceReflectionsNode;
146
147#[derive(Component, Deref, DerefMut)]
149pub struct ScreenSpaceReflectionsPipelineId(pub CachedRenderPipelineId);
150
151#[derive(Resource)]
154pub struct ScreenSpaceReflectionsPipeline {
155 mesh_view_layouts: MeshPipelineViewLayouts,
156 color_sampler: Sampler,
157 depth_linear_sampler: Sampler,
158 depth_nearest_sampler: Sampler,
159 bind_group_layout: BindGroupLayout,
160 binding_arrays_are_usable: bool,
161 fullscreen_shader: FullscreenShader,
162 fragment_shader: Handle<Shader>,
163}
164
165#[derive(Resource, Default, Deref, DerefMut)]
167pub struct ScreenSpaceReflectionsBuffer(pub DynamicUniformBuffer<ScreenSpaceReflectionsUniform>);
168
169#[derive(Component, Default, Deref, DerefMut)]
172pub struct ViewScreenSpaceReflectionsUniformOffset(u32);
173
174#[derive(Clone, Copy, PartialEq, Eq, Hash)]
176pub struct ScreenSpaceReflectionsPipelineKey {
177 mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
178 is_hdr: bool,
179 has_environment_maps: bool,
180}
181
182impl Plugin for ScreenSpaceReflectionsPlugin {
183 fn build(&self, app: &mut App) {
184 load_shader_library!(app, "ssr.wgsl");
185 load_shader_library!(app, "raymarch.wgsl");
186
187 app.add_plugins(ExtractComponentPlugin::<ScreenSpaceReflections>::default());
188
189 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
190 return;
191 };
192
193 render_app
194 .init_resource::<ScreenSpaceReflectionsBuffer>()
195 .init_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>()
196 .add_systems(
197 RenderStartup,
198 (
199 init_screen_space_reflections_pipeline,
200 add_screen_space_reflections_render_graph_edges,
201 ),
202 )
203 .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare))
204 .add_systems(
205 Render,
206 prepare_ssr_settings.in_set(RenderSystems::PrepareResources),
207 )
208 .add_render_graph_node::<ViewNodeRunner<ScreenSpaceReflectionsNode>>(
211 Core3d,
212 NodePbr::ScreenSpaceReflections,
213 );
214 }
215}
216
217fn add_screen_space_reflections_render_graph_edges(mut render_graph: ResMut<RenderGraph>) {
218 let subgraph = render_graph.sub_graph_mut(Core3d);
219
220 subgraph.add_node_edge(NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass);
221
222 if subgraph
223 .get_node_state(NodePbr::DeferredLightingPass)
224 .is_ok()
225 {
226 subgraph.add_node_edge(
227 NodePbr::DeferredLightingPass,
228 NodePbr::ScreenSpaceReflections,
229 );
230 }
231}
232
233impl Default for ScreenSpaceReflections {
234 fn default() -> Self {
239 Self {
240 perceptual_roughness_threshold: 0.1,
241 linear_steps: 16,
242 bisection_steps: 4,
243 use_secant: true,
244 thickness: 0.25,
245 linear_march_exponent: 1.0,
246 }
247 }
248}
249
250impl ViewNode for ScreenSpaceReflectionsNode {
251 type ViewQuery = (
252 Read<ViewTarget>,
253 Read<ViewUniformOffset>,
254 Read<ViewLightsUniformOffset>,
255 Read<ViewFogUniformOffset>,
256 Read<ViewLightProbesUniformOffset>,
257 Read<ViewScreenSpaceReflectionsUniformOffset>,
258 Read<ViewEnvironmentMapUniformOffset>,
259 Read<MeshViewBindGroup>,
260 Read<ScreenSpaceReflectionsPipelineId>,
261 );
262
263 fn run<'w>(
264 &self,
265 _: &mut RenderGraphContext,
266 render_context: &mut RenderContext<'w>,
267 (
268 view_target,
269 view_uniform_offset,
270 view_lights_offset,
271 view_fog_offset,
272 view_light_probes_offset,
273 view_ssr_offset,
274 view_environment_map_offset,
275 view_bind_group,
276 ssr_pipeline_id,
277 ): QueryItem<'w, '_, Self::ViewQuery>,
278 world: &'w World,
279 ) -> Result<(), NodeRunError> {
280 let pipeline_cache = world.resource::<PipelineCache>();
282 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else {
283 return Ok(());
284 };
285
286 let diagnostics = render_context.diagnostic_recorder();
287
288 let postprocess = view_target.post_process_write();
290
291 let ssr_pipeline = world.resource::<ScreenSpaceReflectionsPipeline>();
293 let ssr_bind_group = render_context.render_device().create_bind_group(
294 "SSR bind group",
295 &ssr_pipeline.bind_group_layout,
296 &BindGroupEntries::sequential((
297 postprocess.source,
298 &ssr_pipeline.color_sampler,
299 &ssr_pipeline.depth_linear_sampler,
300 &ssr_pipeline.depth_nearest_sampler,
301 )),
302 );
303
304 let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
306 label: Some("ssr"),
307 color_attachments: &[Some(RenderPassColorAttachment {
308 view: postprocess.destination,
309 depth_slice: None,
310 resolve_target: None,
311 ops: Operations::default(),
312 })],
313 depth_stencil_attachment: None,
314 timestamp_writes: None,
315 occlusion_query_set: None,
316 });
317 let pass_span = diagnostics.pass_span(&mut render_pass, "ssr");
318
319 render_pass.set_render_pipeline(render_pipeline);
321 render_pass.set_bind_group(
322 0,
323 &view_bind_group.main,
324 &[
325 view_uniform_offset.offset,
326 view_lights_offset.offset,
327 view_fog_offset.offset,
328 **view_light_probes_offset,
329 **view_ssr_offset,
330 **view_environment_map_offset,
331 ],
332 );
333 render_pass.set_bind_group(1, &view_bind_group.binding_array, &[]);
334
335 render_pass.set_bind_group(2, &ssr_bind_group, &[]);
337 render_pass.draw(0..3, 0..1);
338
339 pass_span.end(&mut render_pass);
340
341 Ok(())
342 }
343}
344
345pub fn init_screen_space_reflections_pipeline(
346 mut commands: Commands,
347 render_device: Res<RenderDevice>,
348 render_adapter: Res<RenderAdapter>,
349 mesh_view_layouts: Res<MeshPipelineViewLayouts>,
350 fullscreen_shader: Res<FullscreenShader>,
351 asset_server: Res<AssetServer>,
352) {
353 let bind_group_layout = render_device.create_bind_group_layout(
355 "SSR bind group layout",
356 &BindGroupLayoutEntries::sequential(
357 ShaderStages::FRAGMENT,
358 (
359 binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
360 binding_types::sampler(SamplerBindingType::Filtering),
361 binding_types::sampler(SamplerBindingType::Filtering),
362 binding_types::sampler(SamplerBindingType::NonFiltering),
363 ),
364 ),
365 );
366
367 let color_sampler = render_device.create_sampler(&SamplerDescriptor {
370 label: "SSR color sampler".into(),
371 address_mode_u: AddressMode::ClampToEdge,
372 address_mode_v: AddressMode::ClampToEdge,
373 mag_filter: FilterMode::Linear,
374 min_filter: FilterMode::Linear,
375 ..default()
376 });
377
378 let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
379 label: "SSR depth linear sampler".into(),
380 address_mode_u: AddressMode::ClampToEdge,
381 address_mode_v: AddressMode::ClampToEdge,
382 mag_filter: FilterMode::Linear,
383 min_filter: FilterMode::Linear,
384 ..default()
385 });
386
387 let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
388 label: "SSR depth nearest sampler".into(),
389 address_mode_u: AddressMode::ClampToEdge,
390 address_mode_v: AddressMode::ClampToEdge,
391 mag_filter: FilterMode::Nearest,
392 min_filter: FilterMode::Nearest,
393 ..default()
394 });
395
396 commands.insert_resource(ScreenSpaceReflectionsPipeline {
397 mesh_view_layouts: mesh_view_layouts.clone(),
398 color_sampler,
399 depth_linear_sampler,
400 depth_nearest_sampler,
401 bind_group_layout,
402 binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
403 fullscreen_shader: fullscreen_shader.clone(),
404 fragment_shader: load_embedded_asset!(asset_server.as_ref(), "ssr.wgsl"),
407 });
408}
409
410pub fn prepare_ssr_pipelines(
412 mut commands: Commands,
413 pipeline_cache: Res<PipelineCache>,
414 mut pipelines: ResMut<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>,
415 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
416 views: Query<
417 (
418 Entity,
419 &ExtractedView,
420 Has<RenderViewLightProbes<EnvironmentMapLight>>,
421 Has<NormalPrepass>,
422 Has<MotionVectorPrepass>,
423 ),
424 (
425 With<ScreenSpaceReflectionsUniform>,
426 With<DepthPrepass>,
427 With<DeferredPrepass>,
428 ),
429 >,
430) {
431 for (
432 entity,
433 extracted_view,
434 has_environment_maps,
435 has_normal_prepass,
436 has_motion_vector_prepass,
437 ) in &views
438 {
439 let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(Msaa::Off)
442 | MeshPipelineViewLayoutKey::DEPTH_PREPASS
443 | MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
444 mesh_pipeline_view_key.set(
445 MeshPipelineViewLayoutKey::NORMAL_PREPASS,
446 has_normal_prepass,
447 );
448 mesh_pipeline_view_key.set(
449 MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS,
450 has_motion_vector_prepass,
451 );
452
453 let pipeline_id = pipelines.specialize(
455 &pipeline_cache,
456 &ssr_pipeline,
457 ScreenSpaceReflectionsPipelineKey {
458 mesh_pipeline_view_key,
459 is_hdr: extracted_view.hdr,
460 has_environment_maps,
461 },
462 );
463
464 commands
466 .entity(entity)
467 .insert(ScreenSpaceReflectionsPipelineId(pipeline_id));
468 }
469}
470
471pub fn prepare_ssr_settings(
474 mut commands: Commands,
475 views: Query<(Entity, Option<&ScreenSpaceReflectionsUniform>), With<ExtractedView>>,
476 mut ssr_settings_buffer: ResMut<ScreenSpaceReflectionsBuffer>,
477 render_device: Res<RenderDevice>,
478 render_queue: Res<RenderQueue>,
479) {
480 let Some(mut writer) =
481 ssr_settings_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
482 else {
483 return;
484 };
485
486 for (view, ssr_uniform) in views.iter() {
487 let uniform_offset = match ssr_uniform {
488 None => 0,
489 Some(ssr_uniform) => writer.write(ssr_uniform),
490 };
491 commands
492 .entity(view)
493 .insert(ViewScreenSpaceReflectionsUniformOffset(uniform_offset));
494 }
495}
496
497impl ExtractComponent for ScreenSpaceReflections {
498 type QueryData = Read<ScreenSpaceReflections>;
499
500 type QueryFilter = ();
501
502 type Out = ScreenSpaceReflectionsUniform;
503
504 fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
505 if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
506 once!(info!(
507 "Disabling screen-space reflections on this platform because depth textures \
508 aren't supported correctly"
509 ));
510 return None;
511 }
512
513 Some((*settings).into())
514 }
515}
516
517impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
518 type Key = ScreenSpaceReflectionsPipelineKey;
519
520 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
521 let layout = self
522 .mesh_view_layouts
523 .get_view_layout(key.mesh_pipeline_view_key);
524 let layout = vec![
525 layout.main_layout.clone(),
526 layout.binding_array_layout.clone(),
527 self.bind_group_layout.clone(),
528 ];
529
530 let mut shader_defs = vec![
531 "DEPTH_PREPASS".into(),
532 "DEFERRED_PREPASS".into(),
533 "SCREEN_SPACE_REFLECTIONS".into(),
534 ];
535
536 if key.has_environment_maps {
537 shader_defs.push("ENVIRONMENT_MAP".into());
538 }
539
540 if self.binding_arrays_are_usable {
541 shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
542 }
543
544 RenderPipelineDescriptor {
545 label: Some("SSR pipeline".into()),
546 layout,
547 vertex: self.fullscreen_shader.to_vertex_state(),
548 fragment: Some(FragmentState {
549 shader: self.fragment_shader.clone(),
550 shader_defs,
551 targets: vec![Some(ColorTargetState {
552 format: if key.is_hdr {
553 ViewTarget::TEXTURE_FORMAT_HDR
554 } else {
555 TextureFormat::bevy_default()
556 },
557 blend: None,
558 write_mask: ColorWrites::ALL,
559 })],
560 ..default()
561 }),
562 ..default()
563 }
564 }
565}
566
567impl From<ScreenSpaceReflections> for ScreenSpaceReflectionsUniform {
568 fn from(settings: ScreenSpaceReflections) -> Self {
569 Self {
570 perceptual_roughness_threshold: settings.perceptual_roughness_threshold,
571 thickness: settings.thickness,
572 linear_steps: settings.linear_steps,
573 linear_march_exponent: settings.linear_march_exponent,
574 bisection_steps: settings.bisection_steps,
575 use_secant: settings.use_secant as u32,
576 }
577 }
578}