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