1use bevy_app::{App, Plugin};
4use bevy_asset::{load_internal_asset, weak_handle, Handle};
5use bevy_core_pipeline::{
6 core_3d::{
7 graph::{Core3d, Node3d},
8 DEPTH_TEXTURE_SAMPLING_SUPPORTED,
9 },
10 fullscreen_vertex_shader,
11 prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
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::{FromWorld, World},
23};
24use bevy_image::BevyDefault as _;
25use bevy_reflect::{std_traits::ReflectDefault, Reflect};
26use bevy_render::render_graph::RenderGraph;
27use bevy_render::{
28 extract_component::{ExtractComponent, ExtractComponentPlugin},
29 render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
30 render_resource::{
31 binding_types, AddressMode, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
32 CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer, FilterMode,
33 FragmentState, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
34 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
35 ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines,
36 TextureFormat, TextureSampleType,
37 },
38 renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
39 view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset},
40 Render, RenderApp, RenderSet,
41};
42use bevy_utils::{once, prelude::default};
43use tracing::info;
44
45use crate::{
46 binding_arrays_are_usable, graph::NodePbr, prelude::EnvironmentMapLight,
47 MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes,
48 ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset,
49 ViewLightsUniformOffset,
50};
51
52const SSR_SHADER_HANDLE: Handle<Shader> = weak_handle!("0b559df2-0d61-4f53-bf62-aea16cf32787");
53const RAYMARCH_SHADER_HANDLE: Handle<Shader> = weak_handle!("798cc6fc-6072-4b6c-ab4f-83905fa4a19e");
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}
162
163#[derive(Resource, Default, Deref, DerefMut)]
165pub struct ScreenSpaceReflectionsBuffer(pub DynamicUniformBuffer<ScreenSpaceReflectionsUniform>);
166
167#[derive(Component, Default, Deref, DerefMut)]
170pub struct ViewScreenSpaceReflectionsUniformOffset(u32);
171
172#[derive(Clone, Copy, PartialEq, Eq, Hash)]
174pub struct ScreenSpaceReflectionsPipelineKey {
175 mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
176 is_hdr: bool,
177 has_environment_maps: bool,
178}
179
180impl Plugin for ScreenSpaceReflectionsPlugin {
181 fn build(&self, app: &mut App) {
182 load_internal_asset!(app, SSR_SHADER_HANDLE, "ssr.wgsl", Shader::from_wgsl);
183 load_internal_asset!(
184 app,
185 RAYMARCH_SHADER_HANDLE,
186 "raymarch.wgsl",
187 Shader::from_wgsl
188 );
189
190 app.register_type::<ScreenSpaceReflections>()
191 .add_plugins(ExtractComponentPlugin::<ScreenSpaceReflections>::default());
192
193 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
194 return;
195 };
196
197 render_app
198 .init_resource::<ScreenSpaceReflectionsBuffer>()
199 .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSet::Prepare))
200 .add_systems(
201 Render,
202 prepare_ssr_settings.in_set(RenderSet::PrepareResources),
203 )
204 .add_render_graph_node::<ViewNodeRunner<ScreenSpaceReflectionsNode>>(
205 Core3d,
206 NodePbr::ScreenSpaceReflections,
207 );
208 }
209
210 fn finish(&self, app: &mut App) {
211 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
212 return;
213 };
214
215 render_app
216 .init_resource::<ScreenSpaceReflectionsPipeline>()
217 .init_resource::<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>();
218
219 let has_default_deferred_lighting_pass = render_app
222 .world_mut()
223 .resource_mut::<RenderGraph>()
224 .sub_graph(Core3d)
225 .get_node_state(NodePbr::DeferredLightingPass)
226 .is_ok();
227
228 if has_default_deferred_lighting_pass {
229 render_app.add_render_graph_edges(
230 Core3d,
231 (
232 NodePbr::DeferredLightingPass,
233 NodePbr::ScreenSpaceReflections,
234 Node3d::MainOpaquePass,
235 ),
236 );
237 } else {
238 render_app.add_render_graph_edges(
239 Core3d,
240 (NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass),
241 );
242 }
243 }
244}
245
246impl Default for ScreenSpaceReflections {
247 fn default() -> Self {
252 Self {
253 perceptual_roughness_threshold: 0.1,
254 linear_steps: 16,
255 bisection_steps: 4,
256 use_secant: true,
257 thickness: 0.25,
258 linear_march_exponent: 1.0,
259 }
260 }
261}
262
263impl ViewNode for ScreenSpaceReflectionsNode {
264 type ViewQuery = (
265 Read<ViewTarget>,
266 Read<ViewUniformOffset>,
267 Read<ViewLightsUniformOffset>,
268 Read<ViewFogUniformOffset>,
269 Read<ViewLightProbesUniformOffset>,
270 Read<ViewScreenSpaceReflectionsUniformOffset>,
271 Read<ViewEnvironmentMapUniformOffset>,
272 Read<MeshViewBindGroup>,
273 Read<ScreenSpaceReflectionsPipelineId>,
274 );
275
276 fn run<'w>(
277 &self,
278 _: &mut RenderGraphContext,
279 render_context: &mut RenderContext<'w>,
280 (
281 view_target,
282 view_uniform_offset,
283 view_lights_offset,
284 view_fog_offset,
285 view_light_probes_offset,
286 view_ssr_offset,
287 view_environment_map_offset,
288 view_bind_group,
289 ssr_pipeline_id,
290 ): QueryItem<'w, Self::ViewQuery>,
291 world: &'w World,
292 ) -> Result<(), NodeRunError> {
293 let pipeline_cache = world.resource::<PipelineCache>();
295 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else {
296 return Ok(());
297 };
298
299 let postprocess = view_target.post_process_write();
301
302 let ssr_pipeline = world.resource::<ScreenSpaceReflectionsPipeline>();
304 let ssr_bind_group = render_context.render_device().create_bind_group(
305 "SSR bind group",
306 &ssr_pipeline.bind_group_layout,
307 &BindGroupEntries::sequential((
308 postprocess.source,
309 &ssr_pipeline.color_sampler,
310 &ssr_pipeline.depth_linear_sampler,
311 &ssr_pipeline.depth_nearest_sampler,
312 )),
313 );
314
315 let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
317 label: Some("SSR pass"),
318 color_attachments: &[Some(RenderPassColorAttachment {
319 view: postprocess.destination,
320 resolve_target: None,
321 ops: Operations::default(),
322 })],
323 depth_stencil_attachment: None,
324 timestamp_writes: None,
325 occlusion_query_set: None,
326 });
327
328 render_pass.set_render_pipeline(render_pipeline);
330 render_pass.set_bind_group(
331 0,
332 &view_bind_group.value,
333 &[
334 view_uniform_offset.offset,
335 view_lights_offset.offset,
336 view_fog_offset.offset,
337 **view_light_probes_offset,
338 **view_ssr_offset,
339 **view_environment_map_offset,
340 ],
341 );
342
343 render_pass.set_bind_group(1, &ssr_bind_group, &[]);
345 render_pass.draw(0..3, 0..1);
346
347 Ok(())
348 }
349}
350
351impl FromWorld for ScreenSpaceReflectionsPipeline {
352 fn from_world(world: &mut World) -> Self {
353 let mesh_view_layouts = world.resource::<MeshPipelineViewLayouts>().clone();
354 let render_device = world.resource::<RenderDevice>();
355 let render_adapter = world.resource::<RenderAdapter>();
356
357 let bind_group_layout = render_device.create_bind_group_layout(
359 "SSR bind group layout",
360 &BindGroupLayoutEntries::sequential(
361 ShaderStages::FRAGMENT,
362 (
363 binding_types::texture_2d(TextureSampleType::Float { filterable: true }),
364 binding_types::sampler(SamplerBindingType::Filtering),
365 binding_types::sampler(SamplerBindingType::Filtering),
366 binding_types::sampler(SamplerBindingType::NonFiltering),
367 ),
368 ),
369 );
370
371 let color_sampler = render_device.create_sampler(&SamplerDescriptor {
374 label: "SSR color sampler".into(),
375 address_mode_u: AddressMode::ClampToEdge,
376 address_mode_v: AddressMode::ClampToEdge,
377 mag_filter: FilterMode::Linear,
378 min_filter: FilterMode::Linear,
379 ..default()
380 });
381
382 let depth_linear_sampler = render_device.create_sampler(&SamplerDescriptor {
383 label: "SSR depth linear sampler".into(),
384 address_mode_u: AddressMode::ClampToEdge,
385 address_mode_v: AddressMode::ClampToEdge,
386 mag_filter: FilterMode::Linear,
387 min_filter: FilterMode::Linear,
388 ..default()
389 });
390
391 let depth_nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
392 label: "SSR depth nearest sampler".into(),
393 address_mode_u: AddressMode::ClampToEdge,
394 address_mode_v: AddressMode::ClampToEdge,
395 mag_filter: FilterMode::Nearest,
396 min_filter: FilterMode::Nearest,
397 ..default()
398 });
399
400 Self {
401 mesh_view_layouts,
402 color_sampler,
403 depth_linear_sampler,
404 depth_nearest_sampler,
405 bind_group_layout,
406 binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter),
407 }
408 }
409}
410
411pub fn prepare_ssr_pipelines(
413 mut commands: Commands,
414 pipeline_cache: Res<PipelineCache>,
415 mut pipelines: ResMut<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>,
416 ssr_pipeline: Res<ScreenSpaceReflectionsPipeline>,
417 views: Query<
418 (
419 Entity,
420 &ExtractedView,
421 Has<RenderViewLightProbes<EnvironmentMapLight>>,
422 Has<NormalPrepass>,
423 Has<MotionVectorPrepass>,
424 ),
425 (
426 With<ScreenSpaceReflectionsUniform>,
427 With<DepthPrepass>,
428 With<DeferredPrepass>,
429 ),
430 >,
431) {
432 for (
433 entity,
434 extracted_view,
435 has_environment_maps,
436 has_normal_prepass,
437 has_motion_vector_prepass,
438 ) in &views
439 {
440 let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(Msaa::Off)
443 | MeshPipelineViewLayoutKey::DEPTH_PREPASS
444 | MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
445 mesh_pipeline_view_key.set(
446 MeshPipelineViewLayoutKey::NORMAL_PREPASS,
447 has_normal_prepass,
448 );
449 mesh_pipeline_view_key.set(
450 MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS,
451 has_motion_vector_prepass,
452 );
453
454 let pipeline_id = pipelines.specialize(
456 &pipeline_cache,
457 &ssr_pipeline,
458 ScreenSpaceReflectionsPipelineKey {
459 mesh_pipeline_view_key,
460 is_hdr: extracted_view.hdr,
461 has_environment_maps,
462 },
463 );
464
465 commands
467 .entity(entity)
468 .insert(ScreenSpaceReflectionsPipelineId(pipeline_id));
469 }
470}
471
472pub fn prepare_ssr_settings(
475 mut commands: Commands,
476 views: Query<(Entity, Option<&ScreenSpaceReflectionsUniform>), With<ExtractedView>>,
477 mut ssr_settings_buffer: ResMut<ScreenSpaceReflectionsBuffer>,
478 render_device: Res<RenderDevice>,
479 render_queue: Res<RenderQueue>,
480) {
481 let Some(mut writer) =
482 ssr_settings_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
483 else {
484 return;
485 };
486
487 for (view, ssr_uniform) in views.iter() {
488 let uniform_offset = match ssr_uniform {
489 None => 0,
490 Some(ssr_uniform) => writer.write(ssr_uniform),
491 };
492 commands
493 .entity(view)
494 .insert(ViewScreenSpaceReflectionsUniformOffset(uniform_offset));
495 }
496}
497
498impl ExtractComponent for ScreenSpaceReflections {
499 type QueryData = Read<ScreenSpaceReflections>;
500
501 type QueryFilter = ();
502
503 type Out = ScreenSpaceReflectionsUniform;
504
505 fn extract_component(settings: QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
506 if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
507 once!(info!(
508 "Disabling screen-space reflections on this platform because depth textures \
509 aren't supported correctly"
510 ));
511 return None;
512 }
513
514 Some((*settings).into())
515 }
516}
517
518impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
519 type Key = ScreenSpaceReflectionsPipelineKey;
520
521 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
522 let mesh_view_layout = self
523 .mesh_view_layouts
524 .get_view_layout(key.mesh_pipeline_view_key);
525
526 let mut shader_defs = vec![
527 "DEPTH_PREPASS".into(),
528 "DEFERRED_PREPASS".into(),
529 "SCREEN_SPACE_REFLECTIONS".into(),
530 ];
531
532 if key.has_environment_maps {
533 shader_defs.push("ENVIRONMENT_MAP".into());
534 }
535
536 if self.binding_arrays_are_usable {
537 shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
538 }
539
540 RenderPipelineDescriptor {
541 label: Some("SSR pipeline".into()),
542 layout: vec![mesh_view_layout.clone(), self.bind_group_layout.clone()],
543 vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
544 fragment: Some(FragmentState {
545 shader: SSR_SHADER_HANDLE,
546 shader_defs,
547 entry_point: "fragment".into(),
548 targets: vec![Some(ColorTargetState {
549 format: if key.is_hdr {
550 ViewTarget::TEXTURE_FORMAT_HDR
551 } else {
552 TextureFormat::bevy_default()
553 },
554 blend: None,
555 write_mask: ColorWrites::ALL,
556 })],
557 }),
558 push_constant_ranges: vec![],
559 primitive: default(),
560 depth_stencil: None,
561 multisample: default(),
562 zero_initialize_workgroup_memory: false,
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}