Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8670786
fix specular?
aevyrie Dec 31, 2025
bfb6384
Attempt to fix specular dimming and roughness on smooth materials
aevyrie Jan 4, 2026
c4a4fb1
Attempted pbr ssr following h3r3tic's example
aevyrie Jan 4, 2026
49768ab
Bump roughness cutoff now that rough materials are supported
aevyrie Jan 4, 2026
547b46f
Clean up comments
aevyrie Jan 4, 2026
e304987
Use STBN for SSR ray jitter
aevyrie Jan 4, 2026
a62b883
Merge branch 'specular-fix' into pbr-ssr
aevyrie Jan 4, 2026
3a9a55f
Fix crawling in fallback noise
aevyrie Jan 4, 2026
1319c47
Revert "Attempt to fix specular dimming and roughness on smooth mater…
aevyrie Jan 4, 2026
ac937a3
Revert "fix specular?"
aevyrie Jan 4, 2026
e28fa6e
Remove unwrap
aevyrie Jan 4, 2026
ce59be1
Update SSR docs
aevyrie Jan 4, 2026
b6a7643
Fix issue with pbr skipping both diffuse and specular for ssr'd mater…
aevyrie Jan 5, 2026
16f7a1e
Update related examples
aevyrie Jan 5, 2026
3ad15ac
Improve SSR roughness handling with fading ranges
aevyrie Jan 5, 2026
c62f290
Add edge fadeout configuration for SSR
aevyrie Jan 5, 2026
b358fa8
Merge remote-tracking branch 'origin/main' into pbr-ssr
aevyrie Jan 6, 2026
89ff31a
Review feedback
aevyrie Jan 6, 2026
4787f3f
Merge branch 'main' into pbr-ssr
aevyrie Jan 8, 2026
f104b5a
Merge branch 'main' into pbr-ssr
aevyrie Jan 8, 2026
f575205
Merge branch 'main' into pbr-ssr
aevyrie Jan 8, 2026
249d48b
Merge branch 'main' into pbr-ssr
aevyrie Jan 8, 2026
60637d3
Merge branch 'main' into pbr-ssr
aevyrie Jan 9, 2026
ddcd363
Merge branch 'main' into pbr-ssr
aevyrie Jan 10, 2026
0277fb2
Blue noise binding fixes
aevyrie Jan 11, 2026
4b9fcba
Blue noise binding fixes
aevyrie Jan 11, 2026
810a16f
Oops
aevyrie Jan 11, 2026
bab05fd
Oops
aevyrie Jan 11, 2026
33a96f5
Fix and document small bug in envmap sampling
aevyrie Jan 12, 2026
157f0a4
Update defaults to better match other engines and test in scenes.
aevyrie Jan 12, 2026
19110d6
Early exit SSR for too smooth materials
aevyrie Jan 12, 2026
ed5e6d3
Remove ssao from atmosphere example
aevyrie Jan 12, 2026
db68d34
Review feedback
aevyrie Jan 12, 2026
025577f
Merge branch 'main' into pbr-ssr
aevyrie Jan 12, 2026
9e574ef
Merge remote-tracking branch 'origin/main' into pbr-ssr
aevyrie Jan 14, 2026
28f1949
Replace help text with interactive UI for managing SSR and app settings
aevyrie Jan 14, 2026
7593eae
add early exit for zero fade and adjust indirect light calculations
aevyrie Jan 14, 2026
c7137cf
Make skybox match envmap light
aevyrie Jan 14, 2026
e995a9a
Merge remote-tracking branch 'origin/main' into pbr-ssr
aevyrie Jan 15, 2026
7b14d72
Preserve fragment alpha in ssr
aevyrie Jan 15, 2026
b994af0
Fix pipeline key issues with ssr and atmosphere
aevyrie Jan 15, 2026
13a61dc
Fix edge fadeout effect on envmap
aevyrie Jan 15, 2026
32cb87e
Add different bases for debugging SSR output
aevyrie Jan 15, 2026
9966739
Lints
aevyrie Jan 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4645,6 +4645,7 @@ name = "ssr"
path = "examples/3d/ssr.rs"
# Causes an ICE on docs.rs
doc-scrape-examples = false
required-features = ["bluenoise_texture"]

[package.metadata.example.ssr]
name = "Screen Space Reflections"
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_pbr/src/render/mesh_view_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,19 @@ struct LightProbes {
// For more information on these settings, see the documentation for
// `bevy_pbr::ssr::ScreenSpaceReflections`.
struct ScreenSpaceReflectionsSettings {
perceptual_roughness_threshold: f32,
min_perceptual_roughness: f32,
min_perceptual_roughness_fully_active: f32,
max_perceptual_roughness_starts_to_fade: f32,
max_perceptual_roughness: f32,
edge_fadeout_fully_active: f32,
edge_fadeout_no_longer_active: f32,
thickness: f32,
linear_steps: u32,
linear_march_exponent: f32,
bisection_steps: u32,
use_secant: u32,
pad_a: u32,
pad_b: u32,
};

struct EnvironmentMapUniform {
Expand Down
33 changes: 17 additions & 16 deletions crates/bevy_pbr/src/render/pbr_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -604,32 +604,33 @@ fn apply_pbr_lighting(

// Environment map light (indirect)
#ifdef ENVIRONMENT_MAP
// If screen space reflections are going to be used for this material, don't
// accumulate environment map light yet. The SSR shader will do it.
// If screen space reflections are going to be used for this material, only
// accumulate the diffuse part of the environment map light. The SSR shader
// will accumulate the specular part.
#ifdef SCREEN_SPACE_REFLECTIONS
let use_ssr = perceptual_roughness <=
view_bindings::ssr_settings.perceptual_roughness_threshold;
view_bindings::ssr_settings.max_perceptual_roughness;
#else // SCREEN_SPACE_REFLECTIONS
let use_ssr = false;
#endif // SCREEN_SPACE_REFLECTIONS

if (!use_ssr) {

#ifdef STANDARD_MATERIAL_ANISOTROPY
var bent_normal_lighting_input = lighting_input;
bend_normal_for_anisotropy(&bent_normal_lighting_input);
let environment_map_lighting_input = &bent_normal_lighting_input;
var bent_normal_lighting_input = lighting_input;
bend_normal_for_anisotropy(&bent_normal_lighting_input);
let environment_map_lighting_input = &bent_normal_lighting_input;
#else // STANDARD_MATERIAL_ANISOTROPY
let environment_map_lighting_input = &lighting_input;
let environment_map_lighting_input = &lighting_input;
#endif // STANDARD_MATERIAL_ANISOTROPY

let environment_light = environment_map::environment_map_light(
environment_map_lighting_input,
&clusterable_object_index_ranges,
found_diffuse_indirect,
);
let environment_light = environment_map::environment_map_light(
environment_map_lighting_input,
&clusterable_object_index_ranges,
found_diffuse_indirect,
);

indirect_light += environment_light.diffuse * diffuse_occlusion +
environment_light.specular * specular_occlusion;
indirect_light += environment_light.diffuse * diffuse_occlusion;
if (!use_ssr) {
indirect_light += environment_light.specular * specular_occlusion;
}
#endif // ENVIRONMENT_MAP

Expand Down
77 changes: 62 additions & 15 deletions crates/bevy_pbr/src/ssr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Screen space reflections implemented via raymarching.

use core::ops::Range;

use bevy_app::{App, Plugin};
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
use bevy_core_pipeline::{
Expand Down Expand Up @@ -27,6 +29,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
diagnostic::RecordDiagnostics,
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_asset::RenderAssets,
render_graph::{
NodeRunError, RenderGraph, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner,
},
Expand All @@ -36,9 +39,11 @@ use bevy_render::{
DynamicUniformBuffer, FilterMode, FragmentState, Operations, PipelineCache,
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType, SpecializedRenderPipeline,
SpecializedRenderPipelines, TextureFormat, TextureSampleType,
SpecializedRenderPipelines, TextureFormat, TextureSampleType, TextureViewDescriptor,
TextureViewDimension,
},
renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
texture::GpuImage,
view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset},
Render, RenderApp, RenderStartup, RenderSystems,
};
Expand All @@ -47,8 +52,8 @@ use bevy_utils::{once, prelude::default};
use tracing::info;

use crate::{
binding_arrays_are_usable, graph::NodePbr, ExtractedAtmosphere, MeshPipelineViewLayoutKey,
MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes,
binding_arrays_are_usable, graph::NodePbr, Bluenoise, ExtractedAtmosphere,
MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes,
ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset,
ViewLightsUniformOffset,
};
Expand All @@ -65,10 +70,7 @@ pub struct ScreenSpaceReflectionsPlugin;
/// components, which are inserted automatically,
/// but deferred rendering itself is not automatically enabled.
///
/// SSR currently performs no roughness filtering for glossy reflections, so
/// only very smooth surfaces will reflect objects in screen space. You can
/// adjust the `perceptual_roughness_threshold` in order to tune the threshold
/// below which screen-space reflections will be traced.
/// Enable the `bluenoise_texture` feature to improve the quality of noise on rough reflections.
///
/// As with all screen-space techniques, SSR can only reflect objects on screen.
/// When objects leave the camera, they will disappear from reflections.
Expand All @@ -82,14 +84,22 @@ pub struct ScreenSpaceReflectionsPlugin;
/// Screen-space reflections are presently unsupported on WebGL 2 because of a
/// bug whereby Naga doesn't generate correct GLSL when sampling depth buffers,
/// which is required for screen-space raymarching.
#[derive(Clone, Copy, Component, Reflect)]
#[derive(Clone, Component, Reflect)]
#[reflect(Component, Default, Clone)]
#[require(DepthPrepass, DeferredPrepass)]
#[doc(alias = "Ssr")]
pub struct ScreenSpaceReflections {
/// The maximum PBR roughness level that will enable screen space
/// reflections.
pub perceptual_roughness_threshold: f32,
/// The perceptual roughness range over which SSR begins to fade in.
///
/// The first value is the roughness at which SSR begins to appear; the
/// second value is the roughness at which SSR is fully active.
pub min_perceptual_roughness: Range<f32>,

/// The perceptual roughness range over which SSR begins to fade out.
///
/// The first value is the roughness at which SSR begins to fade out; the
/// second value is the roughness at which SSR is no longer active.
pub max_perceptual_roughness: Range<f32>,

/// When marching the depth buffer, we only have 2.5D information and don't
/// know how thick surfaces are. We shall assume that the depth buffer
Expand All @@ -115,6 +125,14 @@ pub struct ScreenSpaceReflections {
/// as 1 or 2.
pub linear_march_exponent: f32,

/// The range over which SSR begins to fade out at the edges of the screen,
/// in terms of a percentage of the screen dimensions.
///
/// The first value is the percentage from the edge at which SSR is no
/// longer active; the second value is the percentage at which SSR is fully
/// active.
pub edge_fadeout: Range<f32>,

/// Number of steps in a bisection (binary search) to perform once the
/// linear search has found an intersection. Helps narrow down the hit,
/// increasing the chance of the secant method finding an accurate hit
Expand All @@ -133,13 +151,20 @@ pub struct ScreenSpaceReflections {
/// [`ScreenSpaceReflections`].
#[derive(Clone, Copy, Component, ShaderType)]
pub struct ScreenSpaceReflectionsUniform {
perceptual_roughness_threshold: f32,
min_perceptual_roughness: f32,
min_perceptual_roughness_fully_active: f32,
max_perceptual_roughness_starts_to_fade: f32,
max_perceptual_roughness: f32,
edge_fadeout_fully_active: f32,
edge_fadeout_no_longer_active: f32,
thickness: f32,
linear_steps: u32,
linear_march_exponent: f32,
bisection_steps: u32,
/// A boolean converted to a `u32`.
use_secant: u32,
pad_a: u32,
pad_b: u32,
}

/// The node in the render graph that traces screen space reflections.
Expand Down Expand Up @@ -240,12 +265,14 @@ impl Default for ScreenSpaceReflections {
// <https://gist.github.com/h3r2tic/9c8356bdaefbe80b1a22ae0aaee192db?permalink_comment_id=4552149#gistcomment-4552149>.
fn default() -> Self {
Self {
perceptual_roughness_threshold: 0.1,
min_perceptual_roughness: 0.08..0.12,
max_perceptual_roughness: 0.55..0.7,
linear_steps: 16,
bisection_steps: 4,
use_secant: true,
thickness: 0.25,
linear_march_exponent: 1.0,
edge_fadeout: 0.0..0.0,
}
}
}
Expand Down Expand Up @@ -293,6 +320,17 @@ impl ViewNode for ScreenSpaceReflectionsNode {

// Create the bind group for this view.
let ssr_pipeline = world.resource::<ScreenSpaceReflectionsPipeline>();
let bluenoise = world.resource::<Bluenoise>();
let render_images = world.resource::<RenderAssets<GpuImage>>();
let Some(stbn_texture) = render_images.get(&bluenoise.texture) else {
return Ok(());
};
let stbn_view = stbn_texture.texture.create_view(&TextureViewDescriptor {
label: Some("ssr_stbn_view"),
dimension: Some(TextureViewDimension::D2Array),
..default()
});

let ssr_bind_group = render_context.render_device().create_bind_group(
"SSR bind group",
&pipeline_cache.get_bind_group_layout(&ssr_pipeline.bind_group_layout),
Expand All @@ -301,6 +339,7 @@ impl ViewNode for ScreenSpaceReflectionsNode {
&ssr_pipeline.color_sampler,
&ssr_pipeline.depth_linear_sampler,
&ssr_pipeline.depth_nearest_sampler,
&stbn_view,
)),
);

Expand Down Expand Up @@ -363,6 +402,7 @@ pub fn init_screen_space_reflections_pipeline(
binding_types::sampler(SamplerBindingType::Filtering),
binding_types::sampler(SamplerBindingType::Filtering),
binding_types::sampler(SamplerBindingType::NonFiltering),
binding_types::texture_2d_array(TextureSampleType::Float { filterable: false }),
),
),
);
Expand Down Expand Up @@ -517,7 +557,7 @@ impl ExtractComponent for ScreenSpaceReflections {
return None;
}

Some((*settings).into())
Some(settings.clone().into())
}
}

Expand Down Expand Up @@ -581,12 +621,19 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
impl From<ScreenSpaceReflections> for ScreenSpaceReflectionsUniform {
fn from(settings: ScreenSpaceReflections) -> Self {
Self {
perceptual_roughness_threshold: settings.perceptual_roughness_threshold,
min_perceptual_roughness: settings.min_perceptual_roughness.start,
min_perceptual_roughness_fully_active: settings.min_perceptual_roughness.end,
max_perceptual_roughness_starts_to_fade: settings.max_perceptual_roughness.start,
max_perceptual_roughness: settings.max_perceptual_roughness.end,
edge_fadeout_no_longer_active: settings.edge_fadeout.start,
edge_fadeout_fully_active: settings.edge_fadeout.end,
thickness: settings.thickness,
linear_steps: settings.linear_steps,
linear_march_exponent: settings.linear_march_exponent,
bisection_steps: settings.bisection_steps,
use_secant: settings.use_secant as u32,
pad_a: 0,
pad_b: 0,
}
}
}
Loading