1#![expect(deprecated)]
2
3use crate::NodePbr;
4use bevy_app::{App, Plugin};
5use bevy_asset::{load_internal_asset, Handle};
6use bevy_core_pipeline::{
7 core_3d::graph::{Core3d, Node3d},
8 prelude::Camera3d,
9 prepass::{DepthPrepass, NormalPrepass, ViewPrepassTextures},
10};
11use bevy_ecs::{
12 prelude::{Bundle, Component, Entity},
13 query::{Has, QueryItem, With},
14 reflect::ReflectComponent,
15 schedule::IntoSystemConfigs,
16 system::{Commands, Query, Res, ResMut, Resource},
17 world::{FromWorld, World},
18};
19use bevy_reflect::{std_traits::ReflectDefault, Reflect};
20use bevy_render::{
21 camera::{ExtractedCamera, TemporalJitter},
22 extract_component::ExtractComponent,
23 globals::{GlobalsBuffer, GlobalsUniform},
24 prelude::Camera,
25 render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
26 render_resource::{
27 binding_types::{
28 sampler, texture_2d, texture_depth_2d, texture_storage_2d, uniform_buffer,
29 },
30 *,
31 },
32 renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
33 sync_component::SyncComponentPlugin,
34 sync_world::RenderEntity,
35 texture::{CachedTexture, TextureCache},
36 view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms},
37 Extract, ExtractSchedule, Render, RenderApp, RenderSet,
38};
39use bevy_utils::{
40 prelude::default,
41 tracing::{error, warn},
42};
43use core::mem;
44
45const PREPROCESS_DEPTH_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(102258915420479);
46const SSAO_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(253938746510568);
47const SPATIAL_DENOISE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(466162052558226);
48const SSAO_UTILS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(366465052568786);
49
50pub struct ScreenSpaceAmbientOcclusionPlugin;
52
53impl Plugin for ScreenSpaceAmbientOcclusionPlugin {
54 fn build(&self, app: &mut App) {
55 load_internal_asset!(
56 app,
57 PREPROCESS_DEPTH_SHADER_HANDLE,
58 "preprocess_depth.wgsl",
59 Shader::from_wgsl
60 );
61 load_internal_asset!(app, SSAO_SHADER_HANDLE, "ssao.wgsl", Shader::from_wgsl);
62 load_internal_asset!(
63 app,
64 SPATIAL_DENOISE_SHADER_HANDLE,
65 "spatial_denoise.wgsl",
66 Shader::from_wgsl
67 );
68 load_internal_asset!(
69 app,
70 SSAO_UTILS_SHADER_HANDLE,
71 "ssao_utils.wgsl",
72 Shader::from_wgsl
73 );
74
75 app.register_type::<ScreenSpaceAmbientOcclusion>();
76
77 app.add_plugins(SyncComponentPlugin::<ScreenSpaceAmbientOcclusion>::default());
78 }
79
80 fn finish(&self, app: &mut App) {
81 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
82 return;
83 };
84
85 if !render_app
86 .world()
87 .resource::<RenderAdapter>()
88 .get_texture_format_features(TextureFormat::R16Float)
89 .allowed_usages
90 .contains(TextureUsages::STORAGE_BINDING)
91 {
92 warn!("ScreenSpaceAmbientOcclusionPlugin not loaded. GPU lacks support: TextureFormat::R16Float does not support TextureUsages::STORAGE_BINDING.");
93 return;
94 }
95
96 if render_app
97 .world()
98 .resource::<RenderDevice>()
99 .limits()
100 .max_storage_textures_per_shader_stage
101 < 5
102 {
103 warn!("ScreenSpaceAmbientOcclusionPlugin not loaded. GPU lacks support: Limits::max_storage_textures_per_shader_stage is less than 5.");
104 return;
105 }
106
107 render_app
108 .init_resource::<SsaoPipelines>()
109 .init_resource::<SpecializedComputePipelines<SsaoPipelines>>()
110 .add_systems(ExtractSchedule, extract_ssao_settings)
111 .add_systems(
112 Render,
113 (
114 prepare_ssao_pipelines.in_set(RenderSet::Prepare),
115 prepare_ssao_textures.in_set(RenderSet::PrepareResources),
116 prepare_ssao_bind_groups.in_set(RenderSet::PrepareBindGroups),
117 ),
118 )
119 .add_render_graph_node::<ViewNodeRunner<SsaoNode>>(
120 Core3d,
121 NodePbr::ScreenSpaceAmbientOcclusion,
122 )
123 .add_render_graph_edges(
124 Core3d,
125 (
126 Node3d::EndPrepasses,
128 NodePbr::ScreenSpaceAmbientOcclusion,
129 Node3d::StartMainPass,
130 ),
131 );
132 }
133}
134
135#[derive(Bundle, Default, Clone)]
137#[deprecated(
138 since = "0.15.0",
139 note = "Use the `ScreenSpaceAmbientOcclusion` component instead. Inserting it will now also insert the other components required by it automatically."
140)]
141pub struct ScreenSpaceAmbientOcclusionBundle {
142 pub settings: ScreenSpaceAmbientOcclusion,
143 pub depth_prepass: DepthPrepass,
144 pub normal_prepass: NormalPrepass,
145}
146
147#[derive(Component, ExtractComponent, Reflect, PartialEq, Clone, Debug)]
166#[reflect(Component, Debug, Default, PartialEq)]
167#[require(DepthPrepass, NormalPrepass)]
168#[doc(alias = "Ssao")]
169pub struct ScreenSpaceAmbientOcclusion {
170 pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
172 pub constant_object_thickness: f32,
177}
178
179impl Default for ScreenSpaceAmbientOcclusion {
180 fn default() -> Self {
181 Self {
182 quality_level: ScreenSpaceAmbientOcclusionQualityLevel::default(),
183 constant_object_thickness: 0.25,
184 }
185 }
186}
187
188#[deprecated(since = "0.15.0", note = "Renamed to `ScreenSpaceAmbientOcclusion`")]
189pub type ScreenSpaceAmbientOcclusionSettings = ScreenSpaceAmbientOcclusion;
190
191#[derive(Reflect, PartialEq, Eq, Hash, Clone, Copy, Default, Debug)]
192pub enum ScreenSpaceAmbientOcclusionQualityLevel {
193 Low,
194 Medium,
195 #[default]
196 High,
197 Ultra,
198 Custom {
199 slice_count: u32,
201 samples_per_slice_side: u32,
203 },
204}
205
206impl ScreenSpaceAmbientOcclusionQualityLevel {
207 fn sample_counts(&self) -> (u32, u32) {
208 match self {
209 Self::Low => (1, 2), Self::Medium => (2, 2), Self::High => (3, 3), Self::Ultra => (9, 3), Self::Custom {
214 slice_count: slices,
215 samples_per_slice_side,
216 } => (*slices, *samples_per_slice_side),
217 }
218 }
219}
220
221#[derive(Default)]
222struct SsaoNode {}
223
224impl ViewNode for SsaoNode {
225 type ViewQuery = (
226 &'static ExtractedCamera,
227 &'static SsaoPipelineId,
228 &'static SsaoBindGroups,
229 &'static ViewUniformOffset,
230 );
231
232 fn run(
233 &self,
234 _graph: &mut RenderGraphContext,
235 render_context: &mut RenderContext,
236 (camera, pipeline_id, bind_groups, view_uniform_offset): QueryItem<Self::ViewQuery>,
237 world: &World,
238 ) -> Result<(), NodeRunError> {
239 let pipelines = world.resource::<SsaoPipelines>();
240 let pipeline_cache = world.resource::<PipelineCache>();
241 let (
242 Some(camera_size),
243 Some(preprocess_depth_pipeline),
244 Some(spatial_denoise_pipeline),
245 Some(ssao_pipeline),
246 ) = (
247 camera.physical_viewport_size,
248 pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline),
249 pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline),
250 pipeline_cache.get_compute_pipeline(pipeline_id.0),
251 )
252 else {
253 return Ok(());
254 };
255
256 render_context.command_encoder().push_debug_group("ssao");
257
258 {
259 let mut preprocess_depth_pass =
260 render_context
261 .command_encoder()
262 .begin_compute_pass(&ComputePassDescriptor {
263 label: Some("ssao_preprocess_depth_pass"),
264 timestamp_writes: None,
265 });
266 preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline);
267 preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]);
268 preprocess_depth_pass.set_bind_group(
269 1,
270 &bind_groups.common_bind_group,
271 &[view_uniform_offset.offset],
272 );
273 preprocess_depth_pass.dispatch_workgroups(
274 camera_size.x.div_ceil(16),
275 camera_size.y.div_ceil(16),
276 1,
277 );
278 }
279
280 {
281 let mut ssao_pass =
282 render_context
283 .command_encoder()
284 .begin_compute_pass(&ComputePassDescriptor {
285 label: Some("ssao_ssao_pass"),
286 timestamp_writes: None,
287 });
288 ssao_pass.set_pipeline(ssao_pipeline);
289 ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]);
290 ssao_pass.set_bind_group(
291 1,
292 &bind_groups.common_bind_group,
293 &[view_uniform_offset.offset],
294 );
295 ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1);
296 }
297
298 {
299 let mut spatial_denoise_pass =
300 render_context
301 .command_encoder()
302 .begin_compute_pass(&ComputePassDescriptor {
303 label: Some("ssao_spatial_denoise_pass"),
304 timestamp_writes: None,
305 });
306 spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline);
307 spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]);
308 spatial_denoise_pass.set_bind_group(
309 1,
310 &bind_groups.common_bind_group,
311 &[view_uniform_offset.offset],
312 );
313 spatial_denoise_pass.dispatch_workgroups(
314 camera_size.x.div_ceil(8),
315 camera_size.y.div_ceil(8),
316 1,
317 );
318 }
319
320 render_context.command_encoder().pop_debug_group();
321 Ok(())
322 }
323}
324
325#[derive(Resource)]
326struct SsaoPipelines {
327 preprocess_depth_pipeline: CachedComputePipelineId,
328 spatial_denoise_pipeline: CachedComputePipelineId,
329
330 common_bind_group_layout: BindGroupLayout,
331 preprocess_depth_bind_group_layout: BindGroupLayout,
332 ssao_bind_group_layout: BindGroupLayout,
333 spatial_denoise_bind_group_layout: BindGroupLayout,
334
335 hilbert_index_lut: TextureView,
336 point_clamp_sampler: Sampler,
337 linear_clamp_sampler: Sampler,
338}
339
340impl FromWorld for SsaoPipelines {
341 fn from_world(world: &mut World) -> Self {
342 let render_device = world.resource::<RenderDevice>();
343 let render_queue = world.resource::<RenderQueue>();
344 let pipeline_cache = world.resource::<PipelineCache>();
345
346 let hilbert_index_lut = render_device
347 .create_texture_with_data(
348 render_queue,
349 &(TextureDescriptor {
350 label: Some("ssao_hilbert_index_lut"),
351 size: Extent3d {
352 width: HILBERT_WIDTH as u32,
353 height: HILBERT_WIDTH as u32,
354 depth_or_array_layers: 1,
355 },
356 mip_level_count: 1,
357 sample_count: 1,
358 dimension: TextureDimension::D2,
359 format: TextureFormat::R16Uint,
360 usage: TextureUsages::TEXTURE_BINDING,
361 view_formats: &[],
362 }),
363 TextureDataOrder::default(),
364 bytemuck::cast_slice(&generate_hilbert_index_lut()),
365 )
366 .create_view(&TextureViewDescriptor::default());
367
368 let point_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
369 min_filter: FilterMode::Nearest,
370 mag_filter: FilterMode::Nearest,
371 mipmap_filter: FilterMode::Nearest,
372 address_mode_u: AddressMode::ClampToEdge,
373 address_mode_v: AddressMode::ClampToEdge,
374 ..Default::default()
375 });
376 let linear_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
377 min_filter: FilterMode::Linear,
378 mag_filter: FilterMode::Linear,
379 mipmap_filter: FilterMode::Nearest,
380 address_mode_u: AddressMode::ClampToEdge,
381 address_mode_v: AddressMode::ClampToEdge,
382 ..Default::default()
383 });
384
385 let common_bind_group_layout = render_device.create_bind_group_layout(
386 "ssao_common_bind_group_layout",
387 &BindGroupLayoutEntries::sequential(
388 ShaderStages::COMPUTE,
389 (
390 sampler(SamplerBindingType::NonFiltering),
391 sampler(SamplerBindingType::Filtering),
392 uniform_buffer::<ViewUniform>(true),
393 ),
394 ),
395 );
396
397 let preprocess_depth_bind_group_layout = render_device.create_bind_group_layout(
398 "ssao_preprocess_depth_bind_group_layout",
399 &BindGroupLayoutEntries::sequential(
400 ShaderStages::COMPUTE,
401 (
402 texture_depth_2d(),
403 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
404 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
405 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
406 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
407 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
408 ),
409 ),
410 );
411
412 let ssao_bind_group_layout = render_device.create_bind_group_layout(
413 "ssao_ssao_bind_group_layout",
414 &BindGroupLayoutEntries::sequential(
415 ShaderStages::COMPUTE,
416 (
417 texture_2d(TextureSampleType::Float { filterable: true }),
418 texture_2d(TextureSampleType::Float { filterable: false }),
419 texture_2d(TextureSampleType::Uint),
420 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
421 texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::WriteOnly),
422 uniform_buffer::<GlobalsUniform>(false),
423 uniform_buffer::<f32>(false),
424 ),
425 ),
426 );
427
428 let spatial_denoise_bind_group_layout = render_device.create_bind_group_layout(
429 "ssao_spatial_denoise_bind_group_layout",
430 &BindGroupLayoutEntries::sequential(
431 ShaderStages::COMPUTE,
432 (
433 texture_2d(TextureSampleType::Float { filterable: false }),
434 texture_2d(TextureSampleType::Uint),
435 texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
436 ),
437 ),
438 );
439
440 let preprocess_depth_pipeline =
441 pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
442 label: Some("ssao_preprocess_depth_pipeline".into()),
443 layout: vec![
444 preprocess_depth_bind_group_layout.clone(),
445 common_bind_group_layout.clone(),
446 ],
447 push_constant_ranges: vec![],
448 shader: PREPROCESS_DEPTH_SHADER_HANDLE,
449 shader_defs: Vec::new(),
450 entry_point: "preprocess_depth".into(),
451 zero_initialize_workgroup_memory: false,
452 });
453
454 let spatial_denoise_pipeline =
455 pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
456 label: Some("ssao_spatial_denoise_pipeline".into()),
457 layout: vec![
458 spatial_denoise_bind_group_layout.clone(),
459 common_bind_group_layout.clone(),
460 ],
461 push_constant_ranges: vec![],
462 shader: SPATIAL_DENOISE_SHADER_HANDLE,
463 shader_defs: Vec::new(),
464 entry_point: "spatial_denoise".into(),
465 zero_initialize_workgroup_memory: false,
466 });
467
468 Self {
469 preprocess_depth_pipeline,
470 spatial_denoise_pipeline,
471
472 common_bind_group_layout,
473 preprocess_depth_bind_group_layout,
474 ssao_bind_group_layout,
475 spatial_denoise_bind_group_layout,
476
477 hilbert_index_lut,
478 point_clamp_sampler,
479 linear_clamp_sampler,
480 }
481 }
482}
483
484#[derive(PartialEq, Eq, Hash, Clone)]
485struct SsaoPipelineKey {
486 quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
487 temporal_jitter: bool,
488}
489
490impl SpecializedComputePipeline for SsaoPipelines {
491 type Key = SsaoPipelineKey;
492
493 fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
494 let (slice_count, samples_per_slice_side) = key.quality_level.sample_counts();
495
496 let mut shader_defs = vec![
497 ShaderDefVal::Int("SLICE_COUNT".to_string(), slice_count as i32),
498 ShaderDefVal::Int(
499 "SAMPLES_PER_SLICE_SIDE".to_string(),
500 samples_per_slice_side as i32,
501 ),
502 ];
503
504 if key.temporal_jitter {
505 shader_defs.push("TEMPORAL_JITTER".into());
506 }
507
508 ComputePipelineDescriptor {
509 label: Some("ssao_ssao_pipeline".into()),
510 layout: vec![
511 self.ssao_bind_group_layout.clone(),
512 self.common_bind_group_layout.clone(),
513 ],
514 push_constant_ranges: vec![],
515 shader: SSAO_SHADER_HANDLE,
516 shader_defs,
517 entry_point: "ssao".into(),
518 zero_initialize_workgroup_memory: false,
519 }
520 }
521}
522
523fn extract_ssao_settings(
524 mut commands: Commands,
525 cameras: Extract<
526 Query<
527 (RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
528 (With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>),
529 >,
530 >,
531) {
532 for (entity, camera, ssao_settings, msaa) in &cameras {
533 if *msaa != Msaa::Off {
534 error!(
535 "SSAO is being used which requires Msaa::Off, but Msaa is currently set to Msaa::{:?}",
536 *msaa
537 );
538 return;
539 }
540 let mut entity_commands = commands
541 .get_entity(entity)
542 .expect("SSAO entity wasn't synced.");
543 if camera.is_active {
544 entity_commands.insert(ssao_settings.clone());
545 } else {
546 entity_commands.remove::<ScreenSpaceAmbientOcclusion>();
547 }
548 }
549}
550
551#[derive(Component)]
552pub struct ScreenSpaceAmbientOcclusionResources {
553 preprocessed_depth_texture: CachedTexture,
554 ssao_noisy_texture: CachedTexture, pub screen_space_ambient_occlusion_texture: CachedTexture, depth_differences_texture: CachedTexture,
557 thickness_buffer: Buffer,
558}
559
560fn prepare_ssao_textures(
561 mut commands: Commands,
562 mut texture_cache: ResMut<TextureCache>,
563 render_device: Res<RenderDevice>,
564 views: Query<(Entity, &ExtractedCamera, &ScreenSpaceAmbientOcclusion)>,
565) {
566 for (entity, camera, ssao_settings) in &views {
567 let Some(physical_viewport_size) = camera.physical_viewport_size else {
568 continue;
569 };
570 let size = Extent3d {
571 width: physical_viewport_size.x,
572 height: physical_viewport_size.y,
573 depth_or_array_layers: 1,
574 };
575
576 let preprocessed_depth_texture = texture_cache.get(
577 &render_device,
578 TextureDescriptor {
579 label: Some("ssao_preprocessed_depth_texture"),
580 size,
581 mip_level_count: 5,
582 sample_count: 1,
583 dimension: TextureDimension::D2,
584 format: TextureFormat::R16Float,
585 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
586 view_formats: &[],
587 },
588 );
589
590 let ssao_noisy_texture = texture_cache.get(
591 &render_device,
592 TextureDescriptor {
593 label: Some("ssao_noisy_texture"),
594 size,
595 mip_level_count: 1,
596 sample_count: 1,
597 dimension: TextureDimension::D2,
598 format: TextureFormat::R16Float,
599 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
600 view_formats: &[],
601 },
602 );
603
604 let ssao_texture = texture_cache.get(
605 &render_device,
606 TextureDescriptor {
607 label: Some("ssao_texture"),
608 size,
609 mip_level_count: 1,
610 sample_count: 1,
611 dimension: TextureDimension::D2,
612 format: TextureFormat::R16Float,
613 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
614 view_formats: &[],
615 },
616 );
617
618 let depth_differences_texture = texture_cache.get(
619 &render_device,
620 TextureDescriptor {
621 label: Some("ssao_depth_differences_texture"),
622 size,
623 mip_level_count: 1,
624 sample_count: 1,
625 dimension: TextureDimension::D2,
626 format: TextureFormat::R32Uint,
627 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
628 view_formats: &[],
629 },
630 );
631
632 let thickness_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
633 label: Some("thickness_buffer"),
634 contents: &ssao_settings.constant_object_thickness.to_le_bytes(),
635 usage: BufferUsages::UNIFORM,
636 });
637
638 commands
639 .entity(entity)
640 .insert(ScreenSpaceAmbientOcclusionResources {
641 preprocessed_depth_texture,
642 ssao_noisy_texture,
643 screen_space_ambient_occlusion_texture: ssao_texture,
644 depth_differences_texture,
645 thickness_buffer,
646 });
647 }
648}
649
650#[derive(Component)]
651struct SsaoPipelineId(CachedComputePipelineId);
652
653fn prepare_ssao_pipelines(
654 mut commands: Commands,
655 pipeline_cache: Res<PipelineCache>,
656 mut pipelines: ResMut<SpecializedComputePipelines<SsaoPipelines>>,
657 pipeline: Res<SsaoPipelines>,
658 views: Query<(Entity, &ScreenSpaceAmbientOcclusion, Has<TemporalJitter>)>,
659) {
660 for (entity, ssao_settings, temporal_jitter) in &views {
661 let pipeline_id = pipelines.specialize(
662 &pipeline_cache,
663 &pipeline,
664 SsaoPipelineKey {
665 quality_level: ssao_settings.quality_level,
666 temporal_jitter,
667 },
668 );
669
670 commands.entity(entity).insert(SsaoPipelineId(pipeline_id));
671 }
672}
673
674#[derive(Component)]
675struct SsaoBindGroups {
676 common_bind_group: BindGroup,
677 preprocess_depth_bind_group: BindGroup,
678 ssao_bind_group: BindGroup,
679 spatial_denoise_bind_group: BindGroup,
680}
681
682fn prepare_ssao_bind_groups(
683 mut commands: Commands,
684 render_device: Res<RenderDevice>,
685 pipelines: Res<SsaoPipelines>,
686 view_uniforms: Res<ViewUniforms>,
687 global_uniforms: Res<GlobalsBuffer>,
688 views: Query<(
689 Entity,
690 &ScreenSpaceAmbientOcclusionResources,
691 &ViewPrepassTextures,
692 )>,
693) {
694 let (Some(view_uniforms), Some(globals_uniforms)) = (
695 view_uniforms.uniforms.binding(),
696 global_uniforms.buffer.binding(),
697 ) else {
698 return;
699 };
700
701 for (entity, ssao_resources, prepass_textures) in &views {
702 let common_bind_group = render_device.create_bind_group(
703 "ssao_common_bind_group",
704 &pipelines.common_bind_group_layout,
705 &BindGroupEntries::sequential((
706 &pipelines.point_clamp_sampler,
707 &pipelines.linear_clamp_sampler,
708 view_uniforms.clone(),
709 )),
710 );
711
712 let create_depth_view = |mip_level| {
713 ssao_resources
714 .preprocessed_depth_texture
715 .texture
716 .create_view(&TextureViewDescriptor {
717 label: Some("ssao_preprocessed_depth_texture_mip_view"),
718 base_mip_level: mip_level,
719 format: Some(TextureFormat::R16Float),
720 dimension: Some(TextureViewDimension::D2),
721 mip_level_count: Some(1),
722 ..default()
723 })
724 };
725
726 let preprocess_depth_bind_group = render_device.create_bind_group(
727 "ssao_preprocess_depth_bind_group",
728 &pipelines.preprocess_depth_bind_group_layout,
729 &BindGroupEntries::sequential((
730 prepass_textures.depth_view().unwrap(),
731 &create_depth_view(0),
732 &create_depth_view(1),
733 &create_depth_view(2),
734 &create_depth_view(3),
735 &create_depth_view(4),
736 )),
737 );
738
739 let ssao_bind_group = render_device.create_bind_group(
740 "ssao_ssao_bind_group",
741 &pipelines.ssao_bind_group_layout,
742 &BindGroupEntries::sequential((
743 &ssao_resources.preprocessed_depth_texture.default_view,
744 prepass_textures.normal_view().unwrap(),
745 &pipelines.hilbert_index_lut,
746 &ssao_resources.ssao_noisy_texture.default_view,
747 &ssao_resources.depth_differences_texture.default_view,
748 globals_uniforms.clone(),
749 ssao_resources.thickness_buffer.as_entire_binding(),
750 )),
751 );
752
753 let spatial_denoise_bind_group = render_device.create_bind_group(
754 "ssao_spatial_denoise_bind_group",
755 &pipelines.spatial_denoise_bind_group_layout,
756 &BindGroupEntries::sequential((
757 &ssao_resources.ssao_noisy_texture.default_view,
758 &ssao_resources.depth_differences_texture.default_view,
759 &ssao_resources
760 .screen_space_ambient_occlusion_texture
761 .default_view,
762 )),
763 );
764
765 commands.entity(entity).insert(SsaoBindGroups {
766 common_bind_group,
767 preprocess_depth_bind_group,
768 ssao_bind_group,
769 spatial_denoise_bind_group,
770 });
771 }
772}
773
774#[allow(clippy::needless_range_loop)]
775fn generate_hilbert_index_lut() -> [[u16; 64]; 64] {
776 let mut t = [[0; 64]; 64];
777
778 for x in 0..64 {
779 for y in 0..64 {
780 t[x][y] = hilbert_index(x as u16, y as u16);
781 }
782 }
783
784 t
785}
786
787const HILBERT_WIDTH: u16 = 64;
789fn hilbert_index(mut x: u16, mut y: u16) -> u16 {
790 let mut index = 0;
791
792 let mut level: u16 = HILBERT_WIDTH / 2;
793 while level > 0 {
794 let region_x = (x & level > 0) as u16;
795 let region_y = (y & level > 0) as u16;
796 index += level * level * ((3 * region_x) ^ region_y);
797
798 if region_y == 0 {
799 if region_x == 1 {
800 x = HILBERT_WIDTH - 1 - x;
801 y = HILBERT_WIDTH - 1 - y;
802 }
803
804 mem::swap(&mut x, &mut y);
805 }
806
807 level /= 2;
808 }
809
810 index
811}