Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/ssao/mod.rs
6604 views
1
use crate::NodePbr;
2
use bevy_app::{App, Plugin};
3
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
4
use bevy_camera::{Camera, Camera3d};
5
use bevy_core_pipeline::{
6
core_3d::graph::{Core3d, Node3d},
7
prepass::{DepthPrepass, NormalPrepass, ViewPrepassTextures},
8
};
9
use bevy_ecs::{
10
prelude::{Component, Entity},
11
query::{Has, QueryItem, With},
12
reflect::ReflectComponent,
13
resource::Resource,
14
schedule::IntoScheduleConfigs,
15
system::{Commands, Query, Res, ResMut},
16
world::{FromWorld, World},
17
};
18
use bevy_image::ToExtents;
19
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
20
use bevy_render::{
21
camera::{ExtractedCamera, TemporalJitter},
22
diagnostic::RecordDiagnostics,
23
extract_component::ExtractComponent,
24
globals::{GlobalsBuffer, GlobalsUniform},
25
render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, 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, RenderSystems,
38
};
39
use bevy_shader::{load_shader_library, Shader, ShaderDefVal};
40
use bevy_utils::prelude::default;
41
use core::mem;
42
use tracing::{error, warn};
43
44
/// Plugin for screen space ambient occlusion.
45
pub struct ScreenSpaceAmbientOcclusionPlugin;
46
47
impl Plugin for ScreenSpaceAmbientOcclusionPlugin {
48
fn build(&self, app: &mut App) {
49
load_shader_library!(app, "ssao_utils.wgsl");
50
51
embedded_asset!(app, "preprocess_depth.wgsl");
52
embedded_asset!(app, "ssao.wgsl");
53
embedded_asset!(app, "spatial_denoise.wgsl");
54
55
app.add_plugins(SyncComponentPlugin::<ScreenSpaceAmbientOcclusion>::default());
56
}
57
58
fn finish(&self, app: &mut App) {
59
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
60
return;
61
};
62
63
if !render_app
64
.world()
65
.resource::<RenderAdapter>()
66
.get_texture_format_features(TextureFormat::R16Float)
67
.allowed_usages
68
.contains(TextureUsages::STORAGE_BINDING)
69
{
70
warn!("ScreenSpaceAmbientOcclusionPlugin not loaded. GPU lacks support: TextureFormat::R16Float does not support TextureUsages::STORAGE_BINDING.");
71
return;
72
}
73
74
if render_app
75
.world()
76
.resource::<RenderDevice>()
77
.limits()
78
.max_storage_textures_per_shader_stage
79
< 5
80
{
81
warn!("ScreenSpaceAmbientOcclusionPlugin not loaded. GPU lacks support: Limits::max_storage_textures_per_shader_stage is less than 5.");
82
return;
83
}
84
85
render_app
86
.init_resource::<SsaoPipelines>()
87
.init_resource::<SpecializedComputePipelines<SsaoPipelines>>()
88
.add_systems(ExtractSchedule, extract_ssao_settings)
89
.add_systems(
90
Render,
91
(
92
prepare_ssao_pipelines.in_set(RenderSystems::Prepare),
93
prepare_ssao_textures.in_set(RenderSystems::PrepareResources),
94
prepare_ssao_bind_groups.in_set(RenderSystems::PrepareBindGroups),
95
),
96
)
97
.add_render_graph_node::<ViewNodeRunner<SsaoNode>>(
98
Core3d,
99
NodePbr::ScreenSpaceAmbientOcclusion,
100
)
101
.add_render_graph_edges(
102
Core3d,
103
(
104
// END_PRE_PASSES -> SCREEN_SPACE_AMBIENT_OCCLUSION -> MAIN_PASS
105
Node3d::EndPrepasses,
106
NodePbr::ScreenSpaceAmbientOcclusion,
107
Node3d::StartMainPass,
108
),
109
);
110
}
111
}
112
113
/// Component to apply screen space ambient occlusion to a 3d camera.
114
///
115
/// Screen space ambient occlusion (SSAO) approximates small-scale,
116
/// local occlusion of _indirect_ diffuse light between objects, based on what's visible on-screen.
117
/// SSAO does not apply to direct lighting, such as point or directional lights.
118
///
119
/// This darkens creases, e.g. on staircases, and gives nice contact shadows
120
/// where objects meet, giving entities a more "grounded" feel.
121
///
122
/// # Usage Notes
123
///
124
/// Requires that you add [`ScreenSpaceAmbientOcclusionPlugin`] to your app.
125
///
126
/// It strongly recommended that you use SSAO in conjunction with
127
/// TAA (`TemporalAntiAliasing`).
128
/// Doing so greatly reduces SSAO noise.
129
///
130
/// SSAO is not supported on `WebGL2`, and is not currently supported on `WebGPU`.
131
#[derive(Component, ExtractComponent, Reflect, PartialEq, Clone, Debug)]
132
#[reflect(Component, Debug, Default, PartialEq, Clone)]
133
#[require(DepthPrepass, NormalPrepass)]
134
#[doc(alias = "Ssao")]
135
pub struct ScreenSpaceAmbientOcclusion {
136
/// Quality of the SSAO effect.
137
pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
138
/// A constant estimated thickness of objects.
139
///
140
/// This value is used to decide how far behind an object a ray of light needs to be in order
141
/// to pass behind it. Any ray closer than that will be occluded.
142
pub constant_object_thickness: f32,
143
}
144
145
impl Default for ScreenSpaceAmbientOcclusion {
146
fn default() -> Self {
147
Self {
148
quality_level: ScreenSpaceAmbientOcclusionQualityLevel::default(),
149
constant_object_thickness: 0.25,
150
}
151
}
152
}
153
154
#[derive(Reflect, PartialEq, Eq, Hash, Clone, Copy, Default, Debug)]
155
#[reflect(PartialEq, Hash, Clone, Default)]
156
pub enum ScreenSpaceAmbientOcclusionQualityLevel {
157
Low,
158
Medium,
159
#[default]
160
High,
161
Ultra,
162
Custom {
163
/// Higher slice count means less noise, but worse performance.
164
slice_count: u32,
165
/// Samples per slice side is also tweakable, but recommended to be left at 2 or 3.
166
samples_per_slice_side: u32,
167
},
168
}
169
170
impl ScreenSpaceAmbientOcclusionQualityLevel {
171
fn sample_counts(&self) -> (u32, u32) {
172
match self {
173
Self::Low => (1, 2), // 4 spp (1 * (2 * 2)), plus optional temporal samples
174
Self::Medium => (2, 2), // 8 spp (2 * (2 * 2)), plus optional temporal samples
175
Self::High => (3, 3), // 18 spp (3 * (3 * 2)), plus optional temporal samples
176
Self::Ultra => (9, 3), // 54 spp (9 * (3 * 2)), plus optional temporal samples
177
Self::Custom {
178
slice_count: slices,
179
samples_per_slice_side,
180
} => (*slices, *samples_per_slice_side),
181
}
182
}
183
}
184
185
#[derive(Default)]
186
struct SsaoNode {}
187
188
impl ViewNode for SsaoNode {
189
type ViewQuery = (
190
&'static ExtractedCamera,
191
&'static SsaoPipelineId,
192
&'static SsaoBindGroups,
193
&'static ViewUniformOffset,
194
);
195
196
fn run(
197
&self,
198
_graph: &mut RenderGraphContext,
199
render_context: &mut RenderContext,
200
(camera, pipeline_id, bind_groups, view_uniform_offset): QueryItem<Self::ViewQuery>,
201
world: &World,
202
) -> Result<(), NodeRunError> {
203
let pipelines = world.resource::<SsaoPipelines>();
204
let pipeline_cache = world.resource::<PipelineCache>();
205
let (
206
Some(camera_size),
207
Some(preprocess_depth_pipeline),
208
Some(spatial_denoise_pipeline),
209
Some(ssao_pipeline),
210
) = (
211
camera.physical_viewport_size,
212
pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline),
213
pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline),
214
pipeline_cache.get_compute_pipeline(pipeline_id.0),
215
)
216
else {
217
return Ok(());
218
};
219
220
let diagnostics = render_context.diagnostic_recorder();
221
222
let command_encoder = render_context.command_encoder();
223
command_encoder.push_debug_group("ssao");
224
let time_span = diagnostics.time_span(command_encoder, "ssao");
225
226
{
227
let mut preprocess_depth_pass =
228
command_encoder.begin_compute_pass(&ComputePassDescriptor {
229
label: Some("ssao_preprocess_depth"),
230
timestamp_writes: None,
231
});
232
preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline);
233
preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]);
234
preprocess_depth_pass.set_bind_group(
235
1,
236
&bind_groups.common_bind_group,
237
&[view_uniform_offset.offset],
238
);
239
preprocess_depth_pass.dispatch_workgroups(
240
camera_size.x.div_ceil(16),
241
camera_size.y.div_ceil(16),
242
1,
243
);
244
}
245
246
{
247
let mut ssao_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
248
label: Some("ssao"),
249
timestamp_writes: None,
250
});
251
ssao_pass.set_pipeline(ssao_pipeline);
252
ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]);
253
ssao_pass.set_bind_group(
254
1,
255
&bind_groups.common_bind_group,
256
&[view_uniform_offset.offset],
257
);
258
ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1);
259
}
260
261
{
262
let mut spatial_denoise_pass =
263
command_encoder.begin_compute_pass(&ComputePassDescriptor {
264
label: Some("ssao_spatial_denoise"),
265
timestamp_writes: None,
266
});
267
spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline);
268
spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]);
269
spatial_denoise_pass.set_bind_group(
270
1,
271
&bind_groups.common_bind_group,
272
&[view_uniform_offset.offset],
273
);
274
spatial_denoise_pass.dispatch_workgroups(
275
camera_size.x.div_ceil(8),
276
camera_size.y.div_ceil(8),
277
1,
278
);
279
}
280
281
time_span.end(command_encoder);
282
command_encoder.pop_debug_group();
283
Ok(())
284
}
285
}
286
287
#[derive(Resource)]
288
struct SsaoPipelines {
289
preprocess_depth_pipeline: CachedComputePipelineId,
290
spatial_denoise_pipeline: CachedComputePipelineId,
291
292
common_bind_group_layout: BindGroupLayout,
293
preprocess_depth_bind_group_layout: BindGroupLayout,
294
ssao_bind_group_layout: BindGroupLayout,
295
spatial_denoise_bind_group_layout: BindGroupLayout,
296
297
hilbert_index_lut: TextureView,
298
point_clamp_sampler: Sampler,
299
linear_clamp_sampler: Sampler,
300
301
shader: Handle<Shader>,
302
}
303
304
impl FromWorld for SsaoPipelines {
305
fn from_world(world: &mut World) -> Self {
306
let render_device = world.resource::<RenderDevice>();
307
let render_queue = world.resource::<RenderQueue>();
308
let pipeline_cache = world.resource::<PipelineCache>();
309
310
let hilbert_index_lut = render_device
311
.create_texture_with_data(
312
render_queue,
313
&(TextureDescriptor {
314
label: Some("ssao_hilbert_index_lut"),
315
size: Extent3d {
316
width: HILBERT_WIDTH as u32,
317
height: HILBERT_WIDTH as u32,
318
depth_or_array_layers: 1,
319
},
320
mip_level_count: 1,
321
sample_count: 1,
322
dimension: TextureDimension::D2,
323
format: TextureFormat::R16Uint,
324
usage: TextureUsages::TEXTURE_BINDING,
325
view_formats: &[],
326
}),
327
TextureDataOrder::default(),
328
bytemuck::cast_slice(&generate_hilbert_index_lut()),
329
)
330
.create_view(&TextureViewDescriptor::default());
331
332
let point_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
333
min_filter: FilterMode::Nearest,
334
mag_filter: FilterMode::Nearest,
335
mipmap_filter: FilterMode::Nearest,
336
address_mode_u: AddressMode::ClampToEdge,
337
address_mode_v: AddressMode::ClampToEdge,
338
..Default::default()
339
});
340
let linear_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
341
min_filter: FilterMode::Linear,
342
mag_filter: FilterMode::Linear,
343
mipmap_filter: FilterMode::Nearest,
344
address_mode_u: AddressMode::ClampToEdge,
345
address_mode_v: AddressMode::ClampToEdge,
346
..Default::default()
347
});
348
349
let common_bind_group_layout = render_device.create_bind_group_layout(
350
"ssao_common_bind_group_layout",
351
&BindGroupLayoutEntries::sequential(
352
ShaderStages::COMPUTE,
353
(
354
sampler(SamplerBindingType::NonFiltering),
355
sampler(SamplerBindingType::Filtering),
356
uniform_buffer::<ViewUniform>(true),
357
),
358
),
359
);
360
361
let preprocess_depth_bind_group_layout = render_device.create_bind_group_layout(
362
"ssao_preprocess_depth_bind_group_layout",
363
&BindGroupLayoutEntries::sequential(
364
ShaderStages::COMPUTE,
365
(
366
texture_depth_2d(),
367
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
368
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
369
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
370
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
371
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
372
),
373
),
374
);
375
376
let ssao_bind_group_layout = render_device.create_bind_group_layout(
377
"ssao_ssao_bind_group_layout",
378
&BindGroupLayoutEntries::sequential(
379
ShaderStages::COMPUTE,
380
(
381
texture_2d(TextureSampleType::Float { filterable: true }),
382
texture_2d(TextureSampleType::Float { filterable: false }),
383
texture_2d(TextureSampleType::Uint),
384
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
385
texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::WriteOnly),
386
uniform_buffer::<GlobalsUniform>(false),
387
uniform_buffer::<f32>(false),
388
),
389
),
390
);
391
392
let spatial_denoise_bind_group_layout = render_device.create_bind_group_layout(
393
"ssao_spatial_denoise_bind_group_layout",
394
&BindGroupLayoutEntries::sequential(
395
ShaderStages::COMPUTE,
396
(
397
texture_2d(TextureSampleType::Float { filterable: false }),
398
texture_2d(TextureSampleType::Uint),
399
texture_storage_2d(TextureFormat::R16Float, StorageTextureAccess::WriteOnly),
400
),
401
),
402
);
403
404
let preprocess_depth_pipeline =
405
pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
406
label: Some("ssao_preprocess_depth_pipeline".into()),
407
layout: vec![
408
preprocess_depth_bind_group_layout.clone(),
409
common_bind_group_layout.clone(),
410
],
411
shader: load_embedded_asset!(world, "preprocess_depth.wgsl"),
412
..default()
413
});
414
415
let spatial_denoise_pipeline =
416
pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
417
label: Some("ssao_spatial_denoise_pipeline".into()),
418
layout: vec![
419
spatial_denoise_bind_group_layout.clone(),
420
common_bind_group_layout.clone(),
421
],
422
shader: load_embedded_asset!(world, "spatial_denoise.wgsl"),
423
..default()
424
});
425
426
Self {
427
preprocess_depth_pipeline,
428
spatial_denoise_pipeline,
429
430
common_bind_group_layout,
431
preprocess_depth_bind_group_layout,
432
ssao_bind_group_layout,
433
spatial_denoise_bind_group_layout,
434
435
hilbert_index_lut,
436
point_clamp_sampler,
437
linear_clamp_sampler,
438
439
shader: load_embedded_asset!(world, "ssao.wgsl"),
440
}
441
}
442
}
443
444
#[derive(PartialEq, Eq, Hash, Clone)]
445
struct SsaoPipelineKey {
446
quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
447
temporal_jitter: bool,
448
}
449
450
impl SpecializedComputePipeline for SsaoPipelines {
451
type Key = SsaoPipelineKey;
452
453
fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
454
let (slice_count, samples_per_slice_side) = key.quality_level.sample_counts();
455
456
let mut shader_defs = vec![
457
ShaderDefVal::Int("SLICE_COUNT".to_string(), slice_count as i32),
458
ShaderDefVal::Int(
459
"SAMPLES_PER_SLICE_SIDE".to_string(),
460
samples_per_slice_side as i32,
461
),
462
];
463
464
if key.temporal_jitter {
465
shader_defs.push("TEMPORAL_JITTER".into());
466
}
467
468
ComputePipelineDescriptor {
469
label: Some("ssao_ssao_pipeline".into()),
470
layout: vec![
471
self.ssao_bind_group_layout.clone(),
472
self.common_bind_group_layout.clone(),
473
],
474
shader: self.shader.clone(),
475
shader_defs,
476
..default()
477
}
478
}
479
}
480
481
fn extract_ssao_settings(
482
mut commands: Commands,
483
cameras: Extract<
484
Query<
485
(RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
486
(With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>),
487
>,
488
>,
489
) {
490
for (entity, camera, ssao_settings, msaa) in &cameras {
491
if *msaa != Msaa::Off {
492
error!(
493
"SSAO is being used which requires Msaa::Off, but Msaa is currently set to Msaa::{:?}",
494
*msaa
495
);
496
return;
497
}
498
let mut entity_commands = commands
499
.get_entity(entity)
500
.expect("SSAO entity wasn't synced.");
501
if camera.is_active {
502
entity_commands.insert(ssao_settings.clone());
503
} else {
504
entity_commands.remove::<ScreenSpaceAmbientOcclusion>();
505
}
506
}
507
}
508
509
#[derive(Component)]
510
pub struct ScreenSpaceAmbientOcclusionResources {
511
preprocessed_depth_texture: CachedTexture,
512
ssao_noisy_texture: CachedTexture, // Pre-spatially denoised texture
513
pub screen_space_ambient_occlusion_texture: CachedTexture, // Spatially denoised texture
514
depth_differences_texture: CachedTexture,
515
thickness_buffer: Buffer,
516
}
517
518
fn prepare_ssao_textures(
519
mut commands: Commands,
520
mut texture_cache: ResMut<TextureCache>,
521
render_device: Res<RenderDevice>,
522
views: Query<(Entity, &ExtractedCamera, &ScreenSpaceAmbientOcclusion)>,
523
) {
524
for (entity, camera, ssao_settings) in &views {
525
let Some(physical_viewport_size) = camera.physical_viewport_size else {
526
continue;
527
};
528
let size = physical_viewport_size.to_extents();
529
530
let preprocessed_depth_texture = texture_cache.get(
531
&render_device,
532
TextureDescriptor {
533
label: Some("ssao_preprocessed_depth_texture"),
534
size,
535
mip_level_count: 5,
536
sample_count: 1,
537
dimension: TextureDimension::D2,
538
format: TextureFormat::R16Float,
539
usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
540
view_formats: &[],
541
},
542
);
543
544
let ssao_noisy_texture = texture_cache.get(
545
&render_device,
546
TextureDescriptor {
547
label: Some("ssao_noisy_texture"),
548
size,
549
mip_level_count: 1,
550
sample_count: 1,
551
dimension: TextureDimension::D2,
552
format: TextureFormat::R16Float,
553
usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
554
view_formats: &[],
555
},
556
);
557
558
let ssao_texture = texture_cache.get(
559
&render_device,
560
TextureDescriptor {
561
label: Some("ssao_texture"),
562
size,
563
mip_level_count: 1,
564
sample_count: 1,
565
dimension: TextureDimension::D2,
566
format: TextureFormat::R16Float,
567
usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
568
view_formats: &[],
569
},
570
);
571
572
let depth_differences_texture = texture_cache.get(
573
&render_device,
574
TextureDescriptor {
575
label: Some("ssao_depth_differences_texture"),
576
size,
577
mip_level_count: 1,
578
sample_count: 1,
579
dimension: TextureDimension::D2,
580
format: TextureFormat::R32Uint,
581
usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
582
view_formats: &[],
583
},
584
);
585
586
let thickness_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
587
label: Some("thickness_buffer"),
588
contents: &ssao_settings.constant_object_thickness.to_le_bytes(),
589
usage: BufferUsages::UNIFORM,
590
});
591
592
commands
593
.entity(entity)
594
.insert(ScreenSpaceAmbientOcclusionResources {
595
preprocessed_depth_texture,
596
ssao_noisy_texture,
597
screen_space_ambient_occlusion_texture: ssao_texture,
598
depth_differences_texture,
599
thickness_buffer,
600
});
601
}
602
}
603
604
#[derive(Component)]
605
struct SsaoPipelineId(CachedComputePipelineId);
606
607
fn prepare_ssao_pipelines(
608
mut commands: Commands,
609
pipeline_cache: Res<PipelineCache>,
610
mut pipelines: ResMut<SpecializedComputePipelines<SsaoPipelines>>,
611
pipeline: Res<SsaoPipelines>,
612
views: Query<(Entity, &ScreenSpaceAmbientOcclusion, Has<TemporalJitter>)>,
613
) {
614
for (entity, ssao_settings, temporal_jitter) in &views {
615
let pipeline_id = pipelines.specialize(
616
&pipeline_cache,
617
&pipeline,
618
SsaoPipelineKey {
619
quality_level: ssao_settings.quality_level,
620
temporal_jitter,
621
},
622
);
623
624
commands.entity(entity).insert(SsaoPipelineId(pipeline_id));
625
}
626
}
627
628
#[derive(Component)]
629
struct SsaoBindGroups {
630
common_bind_group: BindGroup,
631
preprocess_depth_bind_group: BindGroup,
632
ssao_bind_group: BindGroup,
633
spatial_denoise_bind_group: BindGroup,
634
}
635
636
fn prepare_ssao_bind_groups(
637
mut commands: Commands,
638
render_device: Res<RenderDevice>,
639
pipelines: Res<SsaoPipelines>,
640
view_uniforms: Res<ViewUniforms>,
641
global_uniforms: Res<GlobalsBuffer>,
642
views: Query<(
643
Entity,
644
&ScreenSpaceAmbientOcclusionResources,
645
&ViewPrepassTextures,
646
)>,
647
) {
648
let (Some(view_uniforms), Some(globals_uniforms)) = (
649
view_uniforms.uniforms.binding(),
650
global_uniforms.buffer.binding(),
651
) else {
652
return;
653
};
654
655
for (entity, ssao_resources, prepass_textures) in &views {
656
let common_bind_group = render_device.create_bind_group(
657
"ssao_common_bind_group",
658
&pipelines.common_bind_group_layout,
659
&BindGroupEntries::sequential((
660
&pipelines.point_clamp_sampler,
661
&pipelines.linear_clamp_sampler,
662
view_uniforms.clone(),
663
)),
664
);
665
666
let create_depth_view = |mip_level| {
667
ssao_resources
668
.preprocessed_depth_texture
669
.texture
670
.create_view(&TextureViewDescriptor {
671
label: Some("ssao_preprocessed_depth_texture_mip_view"),
672
base_mip_level: mip_level,
673
format: Some(TextureFormat::R16Float),
674
dimension: Some(TextureViewDimension::D2),
675
mip_level_count: Some(1),
676
..default()
677
})
678
};
679
680
let preprocess_depth_bind_group = render_device.create_bind_group(
681
"ssao_preprocess_depth_bind_group",
682
&pipelines.preprocess_depth_bind_group_layout,
683
&BindGroupEntries::sequential((
684
prepass_textures.depth_view().unwrap(),
685
&create_depth_view(0),
686
&create_depth_view(1),
687
&create_depth_view(2),
688
&create_depth_view(3),
689
&create_depth_view(4),
690
)),
691
);
692
693
let ssao_bind_group = render_device.create_bind_group(
694
"ssao_ssao_bind_group",
695
&pipelines.ssao_bind_group_layout,
696
&BindGroupEntries::sequential((
697
&ssao_resources.preprocessed_depth_texture.default_view,
698
prepass_textures.normal_view().unwrap(),
699
&pipelines.hilbert_index_lut,
700
&ssao_resources.ssao_noisy_texture.default_view,
701
&ssao_resources.depth_differences_texture.default_view,
702
globals_uniforms.clone(),
703
ssao_resources.thickness_buffer.as_entire_binding(),
704
)),
705
);
706
707
let spatial_denoise_bind_group = render_device.create_bind_group(
708
"ssao_spatial_denoise_bind_group",
709
&pipelines.spatial_denoise_bind_group_layout,
710
&BindGroupEntries::sequential((
711
&ssao_resources.ssao_noisy_texture.default_view,
712
&ssao_resources.depth_differences_texture.default_view,
713
&ssao_resources
714
.screen_space_ambient_occlusion_texture
715
.default_view,
716
)),
717
);
718
719
commands.entity(entity).insert(SsaoBindGroups {
720
common_bind_group,
721
preprocess_depth_bind_group,
722
ssao_bind_group,
723
spatial_denoise_bind_group,
724
});
725
}
726
}
727
728
fn generate_hilbert_index_lut() -> [[u16; 64]; 64] {
729
use core::array::from_fn;
730
from_fn(|x| from_fn(|y| hilbert_index(x as u16, y as u16)))
731
}
732
733
// https://www.shadertoy.com/view/3tB3z3
734
const HILBERT_WIDTH: u16 = 64;
735
fn hilbert_index(mut x: u16, mut y: u16) -> u16 {
736
let mut index = 0;
737
738
let mut level: u16 = HILBERT_WIDTH / 2;
739
while level > 0 {
740
let region_x = (x & level > 0) as u16;
741
let region_y = (y & level > 0) as u16;
742
index += level * level * ((3 * region_x) ^ region_y);
743
744
if region_y == 0 {
745
if region_x == 1 {
746
x = HILBERT_WIDTH - 1 - x;
747
y = HILBERT_WIDTH - 1 - y;
748
}
749
750
mem::swap(&mut x, &mut y);
751
}
752
753
level /= 2;
754
}
755
756
index
757
}
758
759