diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 15d13e2987da8..746f22ded9d66 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -10,14 +10,14 @@ use bevy_core::FrameCount; use bevy_ecs::{ prelude::{Bundle, Component, Entity}, query::{QueryItem, With}, - schedule::IntoSystemConfigs, + schedule::{apply_deferred, IntoSystemConfigs}, system::{Commands, Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; use bevy_math::vec2; use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::{ - camera::{ExtractedCamera, TemporalJitter}, + camera::{ExtractedCamera, MipBias, TemporalJitter}, prelude::{Camera, Projection}, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, render_resource::{ @@ -65,7 +65,8 @@ impl Plugin for TemporalAntiAliasPlugin { .add_systems( Render, ( - prepare_taa_jitter + (prepare_taa_jitter_and_mip_bias, apply_deferred) + .chain() .before(prepare_view_uniforms) .in_set(RenderSet::Prepare), prepare_taa_history_textures.in_set(RenderSet::Prepare), @@ -140,6 +141,8 @@ pub struct TemporalAntiAliasBundle { /// are added using a third party library, the library must either: /// 1. Write particle motion vectors to the motion vectors prepass texture /// 2. Render particles after TAA +/// +/// If no [`MipBias`] component is attached to the camera, TAA will add a MipBias(-1.0) component. #[derive(Component, Reflect, Clone)] pub struct TemporalAntiAliasSettings { /// Set to true to delete the saved temporal history (past frames). @@ -436,9 +439,13 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut, - mut query: Query<&mut TemporalJitter, With>, + mut query: Query< + (Entity, &mut TemporalJitter, Option<&MipBias>), + With, + >, + mut commands: Commands, ) { // Halton sequence (2, 3) - 0.5, skipping i = 0 let halton_sequence = [ @@ -454,8 +461,12 @@ fn prepare_taa_jitter( let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()]; - for mut jitter in &mut query { + for (entity, mut jitter, mip_bias) in &mut query { jitter.offset = offset; + + if mip_bias.is_none() { + commands.entity(entity).insert(MipBias(-1.0)); + } } } diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index eb84895aa6fd3..5fa1e7b8ba8c8 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -55,7 +55,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { #endif #ifdef VERTEX_UVS if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { - output_color = output_color * textureSample(base_color_texture, base_color_sampler, uv); + output_color = output_color * textureSampleBias(base_color_texture, base_color_sampler, uv, view.mip_bias); } #endif @@ -74,7 +74,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { var emissive: vec4 = material.emissive; #ifdef VERTEX_UVS if ((material.flags & STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { - emissive = vec4(emissive.rgb * textureSample(emissive_texture, emissive_sampler, uv).rgb, 1.0); + emissive = vec4(emissive.rgb * textureSampleBias(emissive_texture, emissive_sampler, uv, view.mip_bias).rgb, 1.0); } #endif pbr_input.material.emissive = emissive; @@ -83,7 +83,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { var perceptual_roughness: f32 = material.perceptual_roughness; #ifdef VERTEX_UVS if ((material.flags & STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { - let metallic_roughness = textureSample(metallic_roughness_texture, metallic_roughness_sampler, uv); + let metallic_roughness = textureSampleBias(metallic_roughness_texture, metallic_roughness_sampler, uv, view.mip_bias); // Sampling from GLTF standard channels for now metallic = metallic * metallic_roughness.b; perceptual_roughness = perceptual_roughness * metallic_roughness.g; @@ -96,7 +96,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { var occlusion: vec3 = vec3(1.0); #ifdef VERTEX_UVS if ((material.flags & STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { - occlusion = vec3(textureSample(occlusion_texture, occlusion_sampler, in.uv).r); + occlusion = vec3(textureSampleBias(occlusion_texture, occlusion_sampler, in.uv, view.mip_bias).r); } #endif #ifdef SCREEN_SPACE_AMBIENT_OCCLUSION diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 993efe4f1b95b..6f7b2a080d512 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -82,7 +82,7 @@ fn apply_normal_mapping( #ifdef VERTEX_UVS #ifdef STANDARDMATERIAL_NORMAL_MAP // Nt is the tangent-space normal. - var Nt = textureSample(normal_map_texture, normal_map_sampler, uv).rgb; + var Nt = textureSampleBias(normal_map_texture, normal_map_sampler, uv, view.mip_bias).rgb; if (standard_material_flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u { // Only use the xy components and derive z for 2-component normal maps. Nt = vec3(Nt.rg * 2.0 - 1.0, 0.0); diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index bb2d1dfdd97a8..4b967f67ed997 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -39,7 +39,7 @@ fn prepass_alpha_discard(in: FragmentInput) { #ifdef VERTEX_UVS if (material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { - output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv); + output_color = output_color * textureSampleBias(base_color_texture, base_color_sampler, in.uv, view.mip_bias); } #endif // VERTEX_UVS diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 787f479355608..f8050ac90cee8 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -771,3 +771,9 @@ impl TemporalJitter { projection.z_axis.y += jitter.y; } } + +/// Camera component specifying a mip bias to apply when sampling from material textures. +/// +/// Often used in conjunction with antialiasing post-process effects to reduce textures blurriness. +#[derive(Component)] +pub struct MipBias(pub f32); diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 9c09fb8bf6e51..b2b773787c6e4 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -6,7 +6,7 @@ pub use visibility::*; pub use window::*; use crate::{ - camera::{ExtractedCamera, ManualTextureViews, TemporalJitter}, + camera::{ExtractedCamera, ManualTextureViews, MipBias, TemporalJitter}, extract_resource::{ExtractResource, ExtractResourcePlugin}, prelude::{Image, Shader}, render_asset::RenderAssets, @@ -173,6 +173,7 @@ pub struct ViewUniform { // viewport(x_origin, y_origin, width, height) viewport: Vec4, color_grading: ColorGrading, + mip_bias: f32, } #[derive(Resource, Default)] @@ -352,11 +353,16 @@ pub fn prepare_view_uniforms( render_device: Res, render_queue: Res, mut view_uniforms: ResMut, - views: Query<(Entity, &ExtractedView, Option<&TemporalJitter>)>, + views: Query<( + Entity, + &ExtractedView, + Option<&TemporalJitter>, + Option<&MipBias>, + )>, ) { view_uniforms.uniforms.clear(); - for (entity, camera, temporal_jitter) in &views { + for (entity, camera, temporal_jitter, mip_bias) in &views { let viewport = camera.viewport.as_vec4(); let unjittered_projection = camera.projection; let mut projection = unjittered_projection; @@ -383,6 +389,7 @@ pub fn prepare_view_uniforms( world_position: camera.transform.translation(), viewport, color_grading: camera.color_grading, + mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0, }), }; diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index ce50833e66f80..86ae3359f72cc 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -19,4 +19,5 @@ struct View { // viewport(x_origin, y_origin, width, height) viewport: vec4, color_grading: ColorGrading, + mip_bias: f32, };