Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/ssr/mod.rs
6604 views
1
//! Screen space reflections implemented via raymarching.
2
3
use bevy_app::{App, Plugin};
4
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
5
use 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
};
13
use bevy_derive::{Deref, DerefMut};
14
use 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
};
24
use bevy_image::BevyDefault as _;
25
use bevy_light::EnvironmentMapLight;
26
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
27
use 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
};
45
use bevy_shader::{load_shader_library, Shader};
46
use bevy_utils::{once, prelude::default};
47
use tracing::info;
48
49
use crate::{
50
binding_arrays_are_usable, graph::NodePbr, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts,
51
MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset,
52
ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset,
53
};
54
55
/// Enables screen-space reflections for a camera.
56
///
57
/// Screen-space reflections are currently only supported with deferred rendering.
58
pub struct ScreenSpaceReflectionsPlugin;
59
60
/// Add this component to a camera to enable *screen-space reflections* (SSR).
61
///
62
/// Screen-space reflections currently require deferred rendering in order to
63
/// appear. Therefore, they also need the [`DepthPrepass`] and [`DeferredPrepass`]
64
/// components, which are inserted automatically.
65
///
66
/// SSR currently performs no roughness filtering for glossy reflections, so
67
/// only very smooth surfaces will reflect objects in screen space. You can
68
/// adjust the `perceptual_roughness_threshold` in order to tune the threshold
69
/// below which screen-space reflections will be traced.
70
///
71
/// As with all screen-space techniques, SSR can only reflect objects on screen.
72
/// When objects leave the camera, they will disappear from reflections.
73
/// An alternative that doesn't suffer from this problem is the combination of
74
/// a [`LightProbe`](bevy_light::LightProbe) and [`EnvironmentMapLight`]. The advantage of SSR is
75
/// that it can reflect all objects, not just static ones.
76
///
77
/// SSR is an approximation technique and produces artifacts in some situations.
78
/// Hand-tuning the settings in this component will likely be useful.
79
///
80
/// Screen-space reflections are presently unsupported on WebGL 2 because of a
81
/// bug whereby Naga doesn't generate correct GLSL when sampling depth buffers,
82
/// which is required for screen-space raymarching.
83
#[derive(Clone, Copy, Component, Reflect)]
84
#[reflect(Component, Default, Clone)]
85
#[require(DepthPrepass, DeferredPrepass)]
86
#[doc(alias = "Ssr")]
87
pub struct ScreenSpaceReflections {
88
/// The maximum PBR roughness level that will enable screen space
89
/// reflections.
90
pub perceptual_roughness_threshold: f32,
91
92
/// When marching the depth buffer, we only have 2.5D information and don't
93
/// know how thick surfaces are. We shall assume that the depth buffer
94
/// fragments are cuboids with a constant thickness defined by this
95
/// parameter.
96
pub thickness: f32,
97
98
/// The number of steps to be taken at regular intervals to find an initial
99
/// intersection. Must not be zero.
100
///
101
/// Higher values result in higher-quality reflections, because the
102
/// raymarching shader is less likely to miss objects. However, they take
103
/// more GPU time.
104
pub linear_steps: u32,
105
106
/// Exponent to be applied in the linear part of the march.
107
///
108
/// A value of 1.0 will result in equidistant steps, and higher values will
109
/// compress the earlier steps, and expand the later ones. This might be
110
/// desirable in order to get more detail close to objects.
111
///
112
/// For optimal performance, this should be a small unsigned integer, such
113
/// as 1 or 2.
114
pub linear_march_exponent: f32,
115
116
/// Number of steps in a bisection (binary search) to perform once the
117
/// linear search has found an intersection. Helps narrow down the hit,
118
/// increasing the chance of the secant method finding an accurate hit
119
/// point.
120
pub bisection_steps: u32,
121
122
/// Approximate the root position using the secant method—by solving for
123
/// line-line intersection between the ray approach rate and the surface
124
/// gradient.
125
pub use_secant: bool,
126
}
127
128
/// A version of [`ScreenSpaceReflections`] for upload to the GPU.
129
///
130
/// For more information on these fields, see the corresponding documentation in
131
/// [`ScreenSpaceReflections`].
132
#[derive(Clone, Copy, Component, ShaderType)]
133
pub struct ScreenSpaceReflectionsUniform {
134
perceptual_roughness_threshold: f32,
135
thickness: f32,
136
linear_steps: u32,
137
linear_march_exponent: f32,
138
bisection_steps: u32,
139
/// A boolean converted to a `u32`.
140
use_secant: u32,
141
}
142
143
/// The node in the render graph that traces screen space reflections.
144
#[derive(Default)]
145
pub struct ScreenSpaceReflectionsNode;
146
147
/// Identifies which screen space reflections render pipeline a view needs.
148
#[derive(Component, Deref, DerefMut)]
149
pub struct ScreenSpaceReflectionsPipelineId(pub CachedRenderPipelineId);
150
151
/// Information relating to the render pipeline for the screen space reflections
152
/// shader.
153
#[derive(Resource)]
154
pub 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
/// A GPU buffer that stores the screen space reflection settings for each view.
166
#[derive(Resource, Default, Deref, DerefMut)]
167
pub struct ScreenSpaceReflectionsBuffer(pub DynamicUniformBuffer<ScreenSpaceReflectionsUniform>);
168
169
/// A component that stores the offset within the
170
/// [`ScreenSpaceReflectionsBuffer`] for each view.
171
#[derive(Component, Default, Deref, DerefMut)]
172
pub struct ViewScreenSpaceReflectionsUniformOffset(u32);
173
174
/// Identifies a specific configuration of the SSR pipeline shader.
175
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
176
pub struct ScreenSpaceReflectionsPipelineKey {
177
mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
178
is_hdr: bool,
179
has_environment_maps: bool,
180
}
181
182
impl 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
// Note: we add this node here but then we add edges in
209
// `add_screen_space_reflections_render_graph_edges`.
210
.add_render_graph_node::<ViewNodeRunner<ScreenSpaceReflectionsNode>>(
211
Core3d,
212
NodePbr::ScreenSpaceReflections,
213
);
214
}
215
}
216
217
fn 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
233
impl Default for ScreenSpaceReflections {
234
// Reasonable default values.
235
//
236
// These are from
237
// <https://gist.github.com/h3r2tic/9c8356bdaefbe80b1a22ae0aaee192db?permalink_comment_id=4552149#gistcomment-4552149>.
238
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
250
impl 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
// Grab the render pipeline.
281
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
// Set up a standard pair of postprocessing textures.
289
let postprocess = view_target.post_process_write();
290
291
// Create the bind group for this view.
292
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
// Build the SSR render pass.
305
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
// Set bind groups.
320
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
// Perform the SSR render pass.
336
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
345
pub 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
// Create the bind group layout.
354
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
// Create the samplers we need.
368
369
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
// Even though ssr was loaded using load_shader_library, we can still access it like a
405
// normal embedded asset (so we can use it as both a library or a kernel).
406
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "ssr.wgsl"),
407
});
408
}
409
410
/// Sets up screen space reflection pipelines for each applicable view.
411
pub 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
// SSR is only supported in the deferred pipeline, which has no MSAA
440
// support. Thus we can assume MSAA is off.
441
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
// Build the pipeline.
454
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
// Note which pipeline ID was used.
465
commands
466
.entity(entity)
467
.insert(ScreenSpaceReflectionsPipelineId(pipeline_id));
468
}
469
}
470
471
/// Gathers up screen space reflection settings for each applicable view and
472
/// writes them into a GPU buffer.
473
pub 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
497
impl 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
517
impl 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
567
impl 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
}
579
580