Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub mod contact_shadows;
pub mod gltf;
use bevy_light::cluster::GlobalClusterSettings;
use bevy_render::{
renderer::{RenderAdapter, RenderDevice},
sync_component::SyncComponent,
view::{
RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin,
Expand Down Expand Up @@ -499,6 +500,25 @@ pub fn area_light_luts_placeholder() -> Image {
}
}

pub(crate) fn texture_format_contains_feature_flags(
format: TextureFormat,
render_device: &RenderDevice,
render_adapter: &RenderAdapter,
flags: wgpu_types::TextureFormatFeatureFlags,
) -> bool {
format
.guaranteed_format_features(render_device.features())
.flags
.contains(flags)
|| (render_device
.features()
.contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
&& render_adapter
.get_texture_format_features(format)
.flags
.contains(flags))
}

impl SyncComponent<RenderApp, PbrPlugin> for DirectionalLight {
type Target = (
Self,
Expand Down
14 changes: 12 additions & 2 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,9 @@ pub struct MeshPipeline {
/// Whether mesh metadata will use uniform buffers on account of storage buffers
/// being unavailable on this platform.
pub metadata_use_uniform_buffers: bool,

/// Whether depth texture is filterable.
pub depth_filterable: bool,
}

fn init_mesh_pipeline(
Expand Down Expand Up @@ -2765,6 +2768,12 @@ fn init_mesh_pipeline(
metadata_use_uniform_buffers: bevy_render::storage_buffers_are_unsupported(
&render_device.limits(),
),
depth_filterable: texture_format_contains_feature_flags(
CORE_3D_DEPTH_FORMAT,
&render_device,
&render_adapter,
TextureFormatFeatureFlags::FILTERABLE,
),
};

commands.insert_resource(res);
Expand Down Expand Up @@ -3570,8 +3579,9 @@ impl SpecializedMeshPipeline for MeshPipeline {
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
shader_defs.push("WEBGL2".into());

#[cfg(feature = "experimental_pbr_pcss")]
shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into());
if cfg!(feature = "experimental_pbr_pcss") && self.depth_filterable {
shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into());
}

if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
shader_defs.push("TONEMAP_IN_SHADER".into());
Expand Down
18 changes: 18 additions & 0 deletions crates/bevy_pbr/src/render/utils.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,21 @@ fn dir_to_cube_uv(dir: vec3f) -> CubeUV {
fn porter_duff_over(bg: vec4<f32>, fg: vec4<f32>) -> vec4<f32> {
return vec4<f32>(mix(bg.rgb * bg.a, fg.rgb, fg.a), bg.a + fg.a * (1.0 - bg.a));
}

// Weights can be used in `dot(textureGather(..), bilinear_weights(texel_coord))`
// to emulate linear sampling for unfilterable textures.
fn bilinear_weights(texel_coord: vec2f) -> vec4f {
let a: vec2f = fract(texel_coord - vec2f(0.5));
let b: vec2f = vec2f(1.0) - a;
// +-----------+-----------+
// | W=b.x*b.y | Z=a.x*b.y |
// +-----------+-----------+
// | X=b.x*a.y | Y=a.x*a.y |
// +-----------+-----------+
return vec4f(
b.x * a.y, // x
a.x * a.y, // y
a.x * b.y, // z
b.x * b.y, // w
);
}
22 changes: 16 additions & 6 deletions crates/bevy_pbr/src/ssr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use core::ops::Range;
use bevy_app::{App, Plugin};
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
use bevy_core_pipeline::{
core_3d::{main_opaque_pass_3d, DEPTH_PREPASS_TEXTURE_SUPPORTED},
core_3d::{main_opaque_pass_3d, CORE_3D_DEPTH_FORMAT, DEPTH_PREPASS_TEXTURE_SUPPORTED},
prepass::{DeferredPrepass, DepthPrepass},
schedule::{Core3d, Core3dSystems},
FullscreenShader,
Expand Down Expand Up @@ -45,8 +45,9 @@ use bevy_utils::{once, prelude::default};
use tracing::info;

use crate::{
binding_arrays_are_usable, deferred::deferred_lighting, Bluenoise, MeshPipelineSystems,
MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, ViewKeyCache,
binding_arrays_are_usable, deferred::deferred_lighting, texture_format_contains_feature_flags,
Bluenoise, MeshPipelineSystems, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts,
MeshViewBindGroup, ViewKeyCache,
};

/// Enables screen-space reflections for a camera.
Expand Down Expand Up @@ -188,6 +189,7 @@ pub struct ViewScreenSpaceReflectionsUniformOffset(u32);
pub struct ScreenSpaceReflectionsPipelineKey {
mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
target_format: TextureFormat,
depth_filterable: bool,
}

impl Plugin for ScreenSpaceReflectionsPlugin {
Expand Down Expand Up @@ -388,6 +390,8 @@ pub fn init_screen_space_reflections_pipeline(
/// Sets up screen space reflection pipelines for each applicable view.
pub fn prepare_ssr_pipelines(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_adapter: Res<RenderAdapter>,
pipeline_cache: Res<PipelineCache>,
view_key_cache: Res<ViewKeyCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<ScreenSpaceReflectionsPipeline>>,
Expand All @@ -412,6 +416,12 @@ pub fn prepare_ssr_pipelines(
ScreenSpaceReflectionsPipelineKey {
mesh_pipeline_view_key: (*view_key).into(),
target_format: extracted_view.target_format,
depth_filterable: texture_format_contains_feature_flags(
CORE_3D_DEPTH_FORMAT,
&render_device,
&render_adapter,
wgpu_types::TextureFormatFeatureFlags::FILTERABLE,
),
},
);

Expand Down Expand Up @@ -518,9 +528,9 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
shader_defs.push("AREA_LIGHT_LUTS".into());
}

#[cfg(not(target_arch = "wasm32"))]
shader_defs.push("USE_DEPTH_SAMPLERS".into());

if key.depth_filterable {
shader_defs.push("USE_DEPTH_SAMPLERS".into());
}
RenderPipelineDescriptor {
label: Some("SSR pipeline".into()),
layout,
Expand Down
44 changes: 7 additions & 37 deletions crates/bevy_pbr/src/ssr/raymarch.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -27,56 +27,26 @@
#ifdef USE_DEPTH_SAMPLERS
// Allows us to sample from the depth buffer with bilinear filtering.
@group(2) @binding(2) var depth_linear_sampler: sampler;
#endif

// Allows us to sample from the depth buffer with nearest-neighbor filtering.
@group(2) @binding(3) var depth_nearest_sampler: sampler;
#endif

// Manual depth fetch helpers used on WebGPU where depth + filtering sampler is invalid.
#ifndef USE_DEPTH_SAMPLERS
fn depth_texel_clamped(texel: vec2<i32>) -> f32 {
let dims = textureDimensions(depth_prepass_texture);
let max_coord = vec2<i32>(i32(dims.x) - 1, i32(dims.y) - 1);
let clamped = clamp(texel, vec2<i32>(0), max_coord);
return textureLoad(depth_prepass_texture, clamped, 0);
}

fn depth_sample_nearest_clamped(uv: vec2<f32>, tex_size: vec2<f32>) -> f32 {
// Match nearest sampling by snapping to the closest texel center.
let coord = uv * tex_size - vec2(0.5);
return depth_texel_clamped(vec2<i32>(floor(coord + vec2(0.5))));
}

fn depth_sample_bilinear_clamped(uv: vec2<f32>, tex_size: vec2<f32>) -> f32 {
let coord = uv * tex_size - vec2(0.5);
let base = vec2<i32>(floor(coord));
let frac = coord - floor(coord);

let d00 = depth_texel_clamped(base);
let d10 = depth_texel_clamped(base + vec2(1, 0));
let d01 = depth_texel_clamped(base + vec2(0, 1));
let d11 = depth_texel_clamped(base + vec2(1, 1));

let d0 = mix(d00, d10, frac.x);
let d1 = mix(d01, d11, frac.x);
return mix(d0, d1, frac.y);
}
#endif

fn depth_sample_linear(uv: vec2<f32>, tex_size: vec2<f32>) -> f32 {
#ifdef USE_DEPTH_SAMPLERS
return textureSampleLevel(depth_prepass_texture, depth_linear_sampler, uv, 0u);
#else
return depth_sample_bilinear_clamped(uv, tex_size);
// Manual depth fetch helpers used on WebGPU where depth + filtering sampler is invalid.
let texel_coord = uv * vec2f(tex_size);
// load the 4 texels
let texel = textureGather(depth_prepass_texture, depth_nearest_sampler, uv);
let m = bevy_pbr::utils::bilinear_weights(texel_coord);
return dot(texel, m);
#endif
}

fn depth_sample_nearest(uv: vec2<f32>, tex_size: vec2<f32>) -> f32 {
#ifdef USE_DEPTH_SAMPLERS
return textureSampleLevel(depth_prepass_texture, depth_nearest_sampler, uv, 0u);
#else
return depth_sample_nearest_clamped(uv, tex_size);
#endif
}

// Main code
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl Default for WgpuSettings {
backends,
power_preference,
priority,
features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
features: wgpu::Features::empty(),
disabled_features: None,
limits,
constrained_limits: None,
Expand Down
18 changes: 14 additions & 4 deletions examples/3d/ssr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::fmt;
use std::ops::Range;

use bevy::render::renderer::RenderDevice;
use bevy::{
anti_alias::taa::TemporalAntiAliasing,
camera::Hdr,
Expand Down Expand Up @@ -233,6 +234,7 @@ fn setup(
mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, Water>>>,
asset_server: Res<AssetServer>,
app_settings: Res<AppSettings>,
render_device: Res<RenderDevice>,
) {
spawn_cube(
&mut commands,
Expand All @@ -250,7 +252,7 @@ fn setup(
&mut meshes,
&mut water_materials,
);
spawn_camera(&mut commands, &asset_server, &app_settings);
spawn_camera(&mut commands, &asset_server, &app_settings, &render_device);
spawn_buttons(&mut commands, &app_settings);
}

Expand Down Expand Up @@ -409,12 +411,17 @@ fn spawn_water(
}

// Spawns the camera.
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_settings: &AppSettings) {
fn spawn_camera(
commands: &mut Commands,
asset_server: &AssetServer,
app_settings: &AppSettings,
render_device: &RenderDevice,
) {
// Create the camera. Add an environment map and skybox so the water has
// something interesting to reflect, other than the cube. Enable deferred
// rendering by adding depth and deferred prepasses. Turn on FXAA to make
// the scene look a little nicer. Finally, add screen space reflections.
commands.spawn((
let mut entity = commands.spawn((
Camera3d::default(),
Transform::from_translation(vec3(-1.25, 2.25, 4.5)).looking_at(Vec3::ZERO, Vec3::Y),
Hdr,
Expand All @@ -426,7 +433,6 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_setting
edge_fadeout: app_settings.edge_fadeout.clone(),
..default()
},
ScreenSpaceAmbientOcclusion::default(),
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
Expand All @@ -439,6 +445,10 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_setting
..default()
},
));

if render_device.limits().max_sampled_textures_per_shader_stage >= 17 {
entity.insert(ScreenSpaceAmbientOcclusion::default());
}
}

fn spawn_buttons(commands: &mut Commands, app_settings: &AppSettings) {
Expand Down