diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 3c41ca716e..9991da1ee2 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -177,7 +177,9 @@ impl ViewNode for BloomNode { BindGroupEntry { binding: 0, // Read from main texture directly - resource: BindingResource::TextureView(view_target.main_texture()), + resource: BindingResource::TextureView( + view_target.main_texture_view(), + ), }, BindGroupEntry { binding: 1, diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index a0d299bf88..6d3502212d 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -67,9 +67,10 @@ impl ViewNode for PrepassNode { view: &view_motion_vectors_texture.default_view, resolve_target: None, ops: Operations { - // Blue channel doesn't matter, but set to 1.0 for possible faster clear + // Red and Green channels are X and Y components of the motion vectors + // Blue channel doesn't matter, but set to 0.0 for possible faster clear // https://gpuopen.com/performance/#clears - load: LoadOp::Clear(Color::rgb_linear(1.0, 1.0, 1.0).into()), + load: LoadOp::Clear(Color::rgb_linear(0.0, 0.0, 0.0).into()), store: true, }, }, diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index ae20f46a02..76ff1d195c 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -47,7 +47,7 @@ impl ViewNode for UpscalingNode { LoadOp::Clear(Default::default()) }; - let upscaled_texture = target.main_texture(); + let upscaled_texture = target.main_texture_view(); let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); let bind_group = match &mut *cached_bind_group { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e9dbce227e..e5db9bee18 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -7,6 +7,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, + experimental::taa::TemporalAntiAliasSettings, prepass::NormalPrepass, tonemapping::{DebandDither, Tonemapping}, }; @@ -387,6 +388,7 @@ pub fn queue_material_meshes( Option<&DebandDither>, Option<&EnvironmentMapLight>, Option<&NormalPrepass>, + Option<&TemporalAntiAliasSettings>, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, @@ -401,6 +403,7 @@ pub fn queue_material_meshes( dither, environment_map, normal_prepass, + taa_settings, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase, @@ -417,6 +420,10 @@ pub fn queue_material_meshes( view_key |= MeshPipelineKey::NORMAL_PREPASS; } + if taa_settings.is_some() { + view_key |= MeshPipelineKey::TAA; + } + let environment_map_loaded = match environment_map { Some(environment_map) => environment_map.is_loaded(&images), None => false, diff --git a/crates/bevy_pbr/src/render/fog.wgsl b/crates/bevy_pbr/src/render/fog.wgsl index 852265eefb..e6c1416d93 100644 --- a/crates/bevy_pbr/src/render/fog.wgsl +++ b/crates/bevy_pbr/src/render/fog.wgsl @@ -6,61 +6,66 @@ // https://iquilezles.org/articles/fog/ (Atmospheric Fog and Scattering) fn scattering_adjusted_fog_color( + fog_params: Fog, scattering: vec3, ) -> vec4 { - if (fog.directional_light_color.a > 0.0) { + if (fog_params.directional_light_color.a > 0.0) { return vec4( - fog.base_color.rgb - + scattering * fog.directional_light_color.rgb * fog.directional_light_color.a, - fog.base_color.a, + fog_params.base_color.rgb + + scattering * fog_params.directional_light_color.rgb * fog_params.directional_light_color.a, + fog_params.base_color.a, ); } else { - return fog.base_color; + return fog_params.base_color; } } fn linear_fog( + fog_params: Fog, input_color: vec4, distance: f32, scattering: vec3, ) -> vec4 { - var fog_color = scattering_adjusted_fog_color(scattering); - let start = fog.be.x; - let end = fog.be.y; + var fog_color = scattering_adjusted_fog_color(fog_params, scattering); + let start = fog_params.be.x; + let end = fog_params.be.y; fog_color.a *= 1.0 - clamp((end - distance) / (end - start), 0.0, 1.0); return vec4(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a); } fn exponential_fog( + fog_params: Fog, input_color: vec4, distance: f32, scattering: vec3, ) -> vec4 { - var fog_color = scattering_adjusted_fog_color(scattering); - let density = fog.be.x; + var fog_color = scattering_adjusted_fog_color(fog_params, scattering); + let density = fog_params.be.x; fog_color.a *= 1.0 - 1.0 / exp(distance * density); return vec4(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a); } fn exponential_squared_fog( + fog_params: Fog, input_color: vec4, distance: f32, scattering: vec3, ) -> vec4 { - var fog_color = scattering_adjusted_fog_color(scattering); - let distance_times_density = distance * fog.be.x; + var fog_color = scattering_adjusted_fog_color(fog_params, scattering); + let distance_times_density = distance * fog_params.be.x; fog_color.a *= 1.0 - 1.0 / exp(distance_times_density * distance_times_density); return vec4(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a); } fn atmospheric_fog( + fog_params: Fog, input_color: vec4, distance: f32, scattering: vec3, ) -> vec4 { - var fog_color = scattering_adjusted_fog_color(scattering); - let extinction_factor = 1.0 - 1.0 / exp(distance * fog.be); - let inscattering_factor = 1.0 - 1.0 / exp(distance * fog.bi); + var fog_color = scattering_adjusted_fog_color(fog_params, scattering); + let extinction_factor = 1.0 - 1.0 / exp(distance * fog_params.be); + let inscattering_factor = 1.0 - 1.0 / exp(distance * fog_params.bi); return vec4( input_color.rgb * (1.0 - extinction_factor * fog_color.a) + fog_color.rgb * inscattering_factor * fog_color.a, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3a3834a813..f94c75b370 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -586,6 +586,7 @@ bitflags::bitflags! { // See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test const ENVIRONMENT_MAP = (1 << 7); const DEPTH_CLAMP_ORTHO = (1 << 8); + const TAA = (1 << 9); const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3 const BLEND_PREMULTIPLIED_ALPHA = (1 << Self::BLEND_SHIFT_BITS); // @@ -804,6 +805,10 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("ENVIRONMENT_MAP".into()); } + if key.contains(MeshPipelineKey::TAA) { + shader_defs.push("TAA".into()); + } + let format = if key.contains(MeshPipelineKey::HDR) { ViewTarget::TEXTURE_FORMAT_HDR } else { diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index f058a83ff8..7043c4e92a 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -133,7 +133,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { // fog if (fog.mode != FOG_MODE_OFF && (material.flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) { - output_color = apply_fog(output_color, in.world_position.xyz, view.world_position.xyz); + output_color = apply_fog(fog, output_color, in.world_position.xyz, view.world_position.xyz); } #ifdef TONEMAP_IN_SHADER diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 56e3d17773..ee6ac3a79c 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -275,7 +275,7 @@ fn pbr( #endif // PREPASS_FRAGMENT #ifndef PREPASS_FRAGMENT -fn apply_fog(input_color: vec4, fragment_world_position: vec3, view_world_position: vec3) -> vec4 { +fn apply_fog(fog_params: Fog, input_color: vec4, fragment_world_position: vec3, view_world_position: vec3) -> vec4 { let view_to_world = fragment_world_position.xyz - view_world_position.xyz; // `length()` is used here instead of just `view_to_world.z` since that produces more @@ -285,7 +285,7 @@ fn apply_fog(input_color: vec4, fragment_world_position: vec3, view_wo let distance = length(view_to_world); var scattering = vec3(0.0); - if fog.directional_light_color.a > 0.0 { + if fog_params.directional_light_color.a > 0.0 { let view_to_world_normalized = view_to_world / distance; let n_directional_lights = lights.n_directional_lights; for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { @@ -295,19 +295,19 @@ fn apply_fog(input_color: vec4, fragment_world_position: vec3, view_wo dot(view_to_world_normalized, light.direction_to_light), 0.0 ), - fog.directional_light_exponent + fog_params.directional_light_exponent ) * light.color.rgb; } } - if fog.mode == FOG_MODE_LINEAR { - return linear_fog(input_color, distance, scattering); - } else if fog.mode == FOG_MODE_EXPONENTIAL { - return exponential_fog(input_color, distance, scattering); - } else if fog.mode == FOG_MODE_EXPONENTIAL_SQUARED { - return exponential_squared_fog(input_color, distance, scattering); - } else if fog.mode == FOG_MODE_ATMOSPHERIC { - return atmospheric_fog(input_color, distance, scattering); + if fog_params.mode == FOG_MODE_LINEAR { + return linear_fog(fog_params, input_color, distance, scattering); + } else if fog_params.mode == FOG_MODE_EXPONENTIAL { + return exponential_fog(fog_params, input_color, distance, scattering); + } else if fog_params.mode == FOG_MODE_EXPONENTIAL_SQUARED { + return exponential_squared_fog(fog_params, input_color, distance, scattering); + } else if fog_params.mode == FOG_MODE_ATMOSPHERIC { + return atmospheric_fog(fog_params, input_color, distance, scattering); } else { return input_color; } diff --git a/crates/bevy_pbr/src/render/utils.wgsl b/crates/bevy_pbr/src/render/utils.wgsl index 379c87e5e4..cb63273171 100644 --- a/crates/bevy_pbr/src/render/utils.wgsl +++ b/crates/bevy_pbr/src/render/utils.wgsl @@ -1,6 +1,7 @@ #define_import_path bevy_pbr::utils const PI: f32 = 3.141592653589793; +const E: f32 = 2.718281828459045; fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3 { let rgb = clamp( diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 7c7ad41a5e..64a66e7a53 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -89,6 +89,29 @@ impl RenderPhase { draw_function.draw(world, render_pass, view, item); } } + + /// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions. + pub fn render_range<'w>( + &self, + render_pass: &mut TrackedRenderPass<'w>, + world: &'w World, + view: Entity, + range: Range, + ) { + let draw_functions = world.resource::>(); + let mut draw_functions = draw_functions.write(); + draw_functions.prepare(world); + + for item in self + .items + .get(range) + .expect("`Range` provided to `render_range()` is out of bounds") + .iter() + { + let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); + draw_function.draw(world, render_pass, view, item); + } + } } impl RenderPhase { diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index cb3bec1420..dedb266343 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -17,10 +17,21 @@ use crate::{ /// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image", /// which can be used in situations where an image was not explicitly defined. The most common /// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures. -/// [`FallbackImage`] defaults to a 1x1 fully white texture, making blending colors with it a no-op. +/// +/// Defaults to a 1x1 fully opaque white texture, (1.0, 1.0, 1.0, 1.0) which makes multiplying +/// it with other colors a no-op. #[derive(Resource, Deref)] pub struct FallbackImage(GpuImage); +/// A [`RenderApp`](crate::RenderApp) resource that contains a _zero-filled_ "fallback image", +/// which can be used in place of [`FallbackImage`], when a fully transparent or black fallback +/// is required instead of fully opaque white. +/// +/// Defaults to a 1x1 fully transparent black texture, (0.0, 0.0, 0.0, 0.0) which makes adding +/// or alpha-blending it to other colors a no-op. +#[derive(Resource, Deref)] +pub struct FallbackImageZero(GpuImage); + /// A [`RenderApp`](crate::RenderApp) resource that contains a "cubemap fallback image", /// which can be used in situations where an image was not explicitly defined. The most common /// use case is [`AsBindGroup`] implementations (such as materials) that support optional textures. @@ -34,9 +45,10 @@ fn fallback_image_new( format: TextureFormat, dimension: TextureViewDimension, samples: u32, + value: u8, ) -> GpuImage { - // TODO make this configurable - let data = vec![255; format.pixel_size()]; + // TODO make this configurable per channel + let data = vec![value; format.pixel_size()]; let extents = Extent3d { width: 1, @@ -92,6 +104,24 @@ impl FromWorld for FallbackImage { TextureFormat::bevy_default(), TextureViewDimension::D2, 1, + 255, + )) + } +} + +impl FromWorld for FallbackImageZero { + fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { + let render_device = world.resource::(); + let render_queue = world.resource::(); + let default_sampler = world.resource::(); + Self(fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::D2, + 1, + 0, )) } } @@ -108,6 +138,7 @@ impl FromWorld for FallbackImageCubemap { TextureFormat::bevy_default(), TextureViewDimension::Cube, 1, + 255, )) } } @@ -148,6 +179,7 @@ impl<'w> FallbackImagesMsaa<'w> { TextureFormat::bevy_default(), TextureViewDimension::D2, sample_count, + 255, ) }) } @@ -171,6 +203,7 @@ impl<'w> FallbackImagesDepth<'w> { TextureFormat::Depth32Float, TextureViewDimension::D2, sample_count, + 255, ) }) } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 131ca51988..bba263beaa 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -120,6 +120,7 @@ impl Plugin for ImagePlugin { render_app .insert_resource(DefaultImageSampler(default_sampler)) .init_resource::() + .init_resource::() .init_resource::() .init_resource::() .init_resource::(); diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 0ab2dd1ed7..e3c66a5519 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -13,7 +13,7 @@ use crate::{ render_phase::ViewRangefinder3d, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, TextureCache}, + texture::{BevyDefault, CachedTexture, TextureCache}, Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; @@ -202,13 +202,16 @@ pub struct PostProcessWrite<'a> { impl ViewTarget { pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; - /// Retrieve this target's color attachment. This will use [`Self::sampled_main_texture`] and resolve to [`Self::main_texture`] if + /// Retrieve this target's color attachment. This will use [`Self::sampled_main_texture_view`] and resolve to [`Self::main_texture`] if /// the target has sampling enabled. Otherwise it will use [`Self::main_texture`] directly. pub fn get_color_attachment(&self, ops: Operations) -> RenderPassColorAttachment { match &self.main_textures.sampled { - Some(sampled_texture) => RenderPassColorAttachment { - view: sampled_texture, - resolve_target: Some(self.main_texture()), + Some(CachedTexture { + default_view: sampled_texture_view, + .. + }) => RenderPassColorAttachment { + view: sampled_texture_view, + resolve_target: Some(self.main_texture_view()), ops, }, None => self.get_unsampled_color_attachment(ops), @@ -221,18 +224,18 @@ impl ViewTarget { ops: Operations, ) -> RenderPassColorAttachment { RenderPassColorAttachment { - view: self.main_texture(), + view: self.main_texture_view(), resolve_target: None, ops, } } /// The "main" unsampled texture. - pub fn main_texture(&self) -> &TextureView { + pub fn main_texture(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.a + &self.main_textures.a.texture } else { - &self.main_textures.b + &self.main_textures.b.texture } } @@ -242,17 +245,51 @@ impl ViewTarget { /// /// A use case for this is to be able to prepare a bind group for all main textures /// ahead of time. - pub fn main_texture_other(&self) -> &TextureView { + pub fn main_texture_other(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { - &self.main_textures.b + &self.main_textures.b.texture } else { - &self.main_textures.a + &self.main_textures.a.texture + } + } + + /// The "main" unsampled texture. + pub fn main_texture_view(&self) -> &TextureView { + if self.main_texture.load(Ordering::SeqCst) == 0 { + &self.main_textures.a.default_view + } else { + &self.main_textures.b.default_view + } + } + + /// The _other_ "main" unsampled texture view. + /// In most cases you should use [`Self::main_texture_view`] instead and never this. + /// The textures will naturally be swapped when [`Self::post_process_write`] is called. + /// + /// A use case for this is to be able to prepare a bind group for all main textures + /// ahead of time. + pub fn main_texture_other_view(&self) -> &TextureView { + if self.main_texture.load(Ordering::SeqCst) == 0 { + &self.main_textures.b.default_view + } else { + &self.main_textures.a.default_view } } /// The "main" sampled texture. - pub fn sampled_main_texture(&self) -> Option<&TextureView> { - self.main_textures.sampled.as_ref() + pub fn sampled_main_texture(&self) -> Option<&Texture> { + self.main_textures + .sampled + .as_ref() + .map(|sampled| &sampled.texture) + } + + /// The "main" sampled texture view. + pub fn sampled_main_texture_view(&self) -> Option<&TextureView> { + self.main_textures + .sampled + .as_ref() + .map(|sampled| &sampled.default_view) } #[inline] @@ -290,13 +327,13 @@ impl ViewTarget { // if the old main texture is a, then the post processing must write from a to b if old_is_a_main_texture == 0 { PostProcessWrite { - source: &self.main_textures.a, - destination: &self.main_textures.b, + source: &self.main_textures.a.default_view, + destination: &self.main_textures.b.default_view, } } else { PostProcessWrite { - source: &self.main_textures.b, - destination: &self.main_textures.a, + source: &self.main_textures.b.default_view, + destination: &self.main_textures.a.default_view, } } } @@ -357,9 +394,9 @@ pub fn prepare_view_uniforms( #[derive(Clone)] struct MainTargetTextures { - a: TextureView, - b: TextureView, - sampled: Option, + a: CachedTexture, + b: CachedTexture, + sampled: Option, /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target main_texture: Arc, @@ -405,49 +442,50 @@ fn prepare_view_targets( dimension: TextureDimension::D2, format: main_texture_format, usage: TextureUsages::RENDER_ATTACHMENT - | TextureUsages::TEXTURE_BINDING, + | TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_SRC, view_formats: match main_texture_format { TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb], TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb], _ => &[], }, }; + let a = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture_a"), + ..descriptor + }, + ); + let b = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture_b"), + ..descriptor + }, + ); + let sampled = if msaa.samples() > 1 { + let sampled = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture_sampled"), + size, + mip_level_count: 1, + sample_count: msaa.samples(), + dimension: TextureDimension::D2, + format: main_texture_format, + usage: TextureUsages::RENDER_ATTACHMENT, + view_formats: descriptor.view_formats, + }, + ); + Some(sampled) + } else { + None + }; MainTargetTextures { - a: texture_cache - .get( - &render_device, - TextureDescriptor { - label: Some("main_texture_a"), - ..descriptor - }, - ) - .default_view, - b: texture_cache - .get( - &render_device, - TextureDescriptor { - label: Some("main_texture_b"), - ..descriptor - }, - ) - .default_view, - sampled: (msaa.samples() > 1).then(|| { - texture_cache - .get( - &render_device, - TextureDescriptor { - label: Some("main_texture_sampled"), - size, - mip_level_count: 1, - sample_count: msaa.samples(), - dimension: TextureDimension::D2, - format: main_texture_format, - usage: TextureUsages::RENDER_ATTACHMENT, - view_formats: descriptor.view_formats, - }, - ) - .default_view - }), + a, + b, + sampled, main_texture: Arc::new(AtomicUsize::new(0)), } });