From a616ffa8acb70dda973b66dc90ad16196201074f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Homolya?= Date: Sun, 6 Apr 2025 13:06:55 -0700 Subject: [PATCH] Web support for atmosphere (#18582) # Objective Add web support to atmosphere by gating dual source blending and using a macro to determine the target platform. The main objective of this PR is to ensure that users of Bevy's atmosphere feature can also run it in a web-based context where WebGPU support is enabled. ## Solution - Make use of the `#[cfg(not(target_arch = "wasm32"))]` macro to gate the dual source blending, as this is not (yet) supported in web browsers. - Rename the function `sample_sun_illuminance` to `sample_sun_radiance` and move calls out of conditionals to ensure the shader compiles and runs in both native and web-based contexts. - Moved the multiplication of the transmittance out when calculating the sun color, because calling the `sample_sun_illuminance` function was causing issues in web. Overall this results in cleaner code and more readable. ## Testing - Tested by building a wasm target and loading it in a web page with Vite dev server using `mate-h/bevy-webgpu` repo template. - Tested the native build with `cargo run --example atmosphere` to ensure it still works with dual source blending. --- ## Showcase Screenshots show the atmosphere example running in two different contexts: atmosphere-web-showcase --------- Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com> --- crates/bevy_pbr/src/atmosphere/functions.wgsl | 8 ++++---- crates/bevy_pbr/src/atmosphere/mod.rs | 15 ++++----------- crates/bevy_pbr/src/atmosphere/render_sky.wgsl | 15 +++++++++++++-- crates/bevy_pbr/src/atmosphere/resources.rs | 16 +++++++++++++++- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index c168d019f5..c1f02fc921 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -277,11 +277,11 @@ fn sample_local_inscattering(local_atmosphere: AtmosphereSample, ray_dir: vec3, transmittance: vec3) -> vec3 { +fn sample_sun_radiance(ray_dir_ws: vec3) -> vec3 { let r = view_radius(); let mu_view = ray_dir_ws.y; let shadow_factor = f32(!ray_intersects_ground(r, mu_view)); - var sun_illuminance = vec3(0.0); + var sun_radiance = vec3(0.0); for (var light_i: u32 = 0u; light_i < lights.n_directional_lights; light_i++) { let light = &lights.directional_lights[light_i]; let neg_LdotV = dot((*light).direction_to_light, ray_dir_ws); @@ -289,9 +289,9 @@ fn sample_sun_illuminance(ray_dir_ws: vec3, transmittance: vec3) -> ve let pixel_size = fwidth(angle_to_sun); let factor = smoothstep(0.0, -pixel_size * ROOT_2, angle_to_sun - SUN_ANGULAR_SIZE * 0.5); let sun_solid_angle = (SUN_ANGULAR_SIZE * SUN_ANGULAR_SIZE) * 4.0 * FRAC_PI; - sun_illuminance += ((*light).color.rgb / sun_solid_angle) * factor * shadow_factor; + sun_radiance += ((*light).color.rgb / sun_solid_angle) * factor * shadow_factor; } - return sun_illuminance * transmittance * view.exposure; + return sun_radiance; } // TRANSFORM UTILITIES diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index e445a5adc8..e7f17f0e1e 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -25,6 +25,10 @@ //! at once is untested, and might not be physically accurate. These may be //! integrated into a single module in the future. //! +//! On web platforms, atmosphere rendering will look slightly different. Specifically, when calculating how light travels +//! through the atmosphere, we use a simpler averaging technique instead of the more +//! complex blending operations. This difference will be resolved for WebGPU in a future release. +//! //! [Shadertoy]: https://www.shadertoy.com/view/slSXRW //! //! [Unreal Engine Implementation]: https://github.com/sebh/UnrealEngineSkyAtmosphere @@ -46,8 +50,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::UniformComponentPlugin, render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines}, - renderer::RenderDevice, - settings::WgpuFeatures, }; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, @@ -159,15 +161,6 @@ impl Plugin for AtmospherePlugin { }; let render_adapter = render_app.world().resource::(); - let render_device = render_app.world().resource::(); - - if !render_device - .features() - .contains(WgpuFeatures::DUAL_SOURCE_BLENDING) - { - warn!("AtmospherePlugin not loaded. GPU lacks support for dual-source blending."); - return; - } if !render_adapter .get_downlevel_capabilities() diff --git a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl index 314037f887..e488656df4 100644 --- a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl +++ b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl @@ -5,7 +5,7 @@ sample_transmittance_lut, sample_transmittance_lut_segment, sample_sky_view_lut, direction_world_to_atmosphere, uv_to_ray_direction, uv_to_ndc, sample_aerial_view_lut, - view_radius, sample_sun_illuminance, ndc_to_camera_dist + view_radius, sample_sun_radiance, ndc_to_camera_dist }, }; #import bevy_render::view::View; @@ -20,7 +20,9 @@ struct RenderSkyOutput { @location(0) inscattering: vec4, +#ifdef DUAL_SOURCE_BLENDING @location(0) @second_blend_source transmittance: vec4, +#endif } @fragment @@ -33,15 +35,24 @@ fn main(in: FullscreenVertexOutput) -> RenderSkyOutput { var transmittance: vec3; var inscattering: vec3; + + let sun_radiance = sample_sun_radiance(ray_dir_ws.xyz); + if depth == 0.0 { let ray_dir_as = direction_world_to_atmosphere(ray_dir_ws.xyz); transmittance = sample_transmittance_lut(r, mu); inscattering += sample_sky_view_lut(r, ray_dir_as); - inscattering += sample_sun_illuminance(ray_dir_ws.xyz, transmittance); + inscattering += sun_radiance * transmittance * view.exposure; } else { let t = ndc_to_camera_dist(vec3(uv_to_ndc(in.uv), depth)); inscattering = sample_aerial_view_lut(in.uv, t); transmittance = sample_transmittance_lut_segment(r, mu, t); } +#ifdef DUAL_SOURCE_BLENDING return RenderSkyOutput(vec4(inscattering, 0.0), vec4(transmittance, 1.0)); +#else + let mean_transmittance = (transmittance.r + transmittance.g + transmittance.b) / 3.0; + return RenderSkyOutput(vec4(inscattering, mean_transmittance)); +#endif + } diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index 71bf26da2d..b872916619 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -326,6 +326,7 @@ pub(crate) struct RenderSkyPipelineId(pub CachedRenderPipelineId); pub(crate) struct RenderSkyPipelineKey { pub msaa_samples: u32, pub hdr: bool, + pub dual_source_blending: bool, } impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { @@ -340,6 +341,15 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { if key.hdr { shader_defs.push("TONEMAP_IN_SHADER".into()); } + if key.dual_source_blending { + shader_defs.push("DUAL_SOURCE_BLENDING".into()); + } + + let dst_factor = if key.dual_source_blending { + BlendFactor::Src1 + } else { + BlendFactor::SrcAlpha + }; RenderPipelineDescriptor { label: Some(format!("render_sky_pipeline_{}", key.msaa_samples).into()), @@ -367,7 +377,7 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { blend: Some(BlendState { color: BlendComponent { src_factor: BlendFactor::One, - dst_factor: BlendFactor::Src1, + dst_factor, operation: BlendOperation::Add, }, alpha: BlendComponent { @@ -388,6 +398,7 @@ pub(super) fn queue_render_sky_pipelines( pipeline_cache: Res, layouts: Res, mut specializer: ResMut>, + render_device: Res, mut commands: Commands, ) { for (entity, camera, msaa) in &views { @@ -397,6 +408,9 @@ pub(super) fn queue_render_sky_pipelines( RenderSkyPipelineKey { msaa_samples: msaa.samples(), hdr: camera.hdr, + dual_source_blending: render_device + .features() + .contains(WgpuFeatures::DUAL_SOURCE_BLENDING), }, ); commands.entity(entity).insert(RenderSkyPipelineId(id));