Add FXAA postprocessing (#6393)

# Objective

- Add post processing passes for FXAA (Fast Approximate Anti-Aliasing)
- Add example comparing MSAA and FXAA

## Solution

When the FXAA plugin is added, passes for FXAA are inserted between the main pass and the tonemapping pass. Supports using either HDR or LDR output from the main pass.

---

## Changelog

- Add a new FXAANode that runs after the main pass when the FXAA plugin is added.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Griffin 2022-11-02 06:51:28 +00:00
parent 1f22d54489
commit 5640ec855e
8 changed files with 863 additions and 4 deletions

View file

@ -323,6 +323,16 @@ description = "Loads and renders a glTF file as a scene"
category = "3D Rendering"
wasm = true
[[example]]
name = "fxaa"
path = "examples/3d/fxaa.rs"
[package.metadata.example.fxaa]
name = "FXAA"
description = "Compares MSAA (Multi-Sample Anti-Aliasing) and FXAA (Fast Approximate Anti-Aliasing)"
category = "3D Rendering"
wasm = true
[[example]]
name = "msaa"
path = "examples/3d/msaa.rs"

View file

@ -0,0 +1,277 @@
// NVIDIA FXAA 3.11
// Original source code by TIMOTHY LOTTES
// https://gist.github.com/kosua20/0c506b81b3812ac900048059d2383126
//
// Cleaned version - https://github.com/kosua20/Rendu/blob/master/resources/common/shaders/screens/fxaa.frag
//
// Tweaks by mrDIMAS - https://github.com/FyroxEngine/Fyrox/blob/master/src/renderer/shaders/fxaa_fs.glsl
#import bevy_core_pipeline::fullscreen_vertex_shader
@group(0) @binding(0)
var screenTexture: texture_2d<f32>;
@group(0) @binding(1)
var samp: sampler;
// Trims the algorithm from processing darks.
#ifdef EDGE_THRESH_MIN_LOW
let EDGE_THRESHOLD_MIN: f32 = 0.0833;
#endif
#ifdef EDGE_THRESH_MIN_MEDIUM
let EDGE_THRESHOLD_MIN: f32 = 0.0625;
#endif
#ifdef EDGE_THRESH_MIN_HIGH
let EDGE_THRESHOLD_MIN: f32 = 0.0312;
#endif
#ifdef EDGE_THRESH_MIN_ULTRA
let EDGE_THRESHOLD_MIN: f32 = 0.0156;
#endif
#ifdef EDGE_THRESH_MIN_EXTREME
let EDGE_THRESHOLD_MIN: f32 = 0.0078;
#endif
// The minimum amount of local contrast required to apply algorithm.
#ifdef EDGE_THRESH_LOW
let EDGE_THRESHOLD_MAX: f32 = 0.250;
#endif
#ifdef EDGE_THRESH_MEDIUM
let EDGE_THRESHOLD_MAX: f32 = 0.166;
#endif
#ifdef EDGE_THRESH_HIGH
let EDGE_THRESHOLD_MAX: f32 = 0.125;
#endif
#ifdef EDGE_THRESH_ULTRA
let EDGE_THRESHOLD_MAX: f32 = 0.063;
#endif
#ifdef EDGE_THRESH_EXTREME
let EDGE_THRESHOLD_MAX: f32 = 0.031;
#endif
let ITERATIONS: i32 = 12; //default is 12
let SUBPIXEL_QUALITY: f32 = 0.75;
// #define QUALITY(q) ((q) < 5 ? 1.0 : ((q) > 5 ? ((q) < 10 ? 2.0 : ((q) < 11 ? 4.0 : 8.0)) : 1.5))
fn QUALITY(q: i32) -> f32 {
switch (q) {
//case 0, 1, 2, 3, 4: { return 1.0; }
default: { return 1.0; }
case 5: { return 1.5; }
case 6, 7, 8, 9: { return 2.0; }
case 10: { return 4.0; }
case 11: { return 8.0; }
}
}
fn rgb2luma(rgb: vec3<f32>) -> f32 {
return sqrt(dot(rgb, vec3<f32>(0.299, 0.587, 0.114)));
}
// Performs FXAA post-process anti-aliasing as described in the Nvidia FXAA white paper and the associated shader code.
@fragment
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let resolution = vec2<f32>(textureDimensions(screenTexture));
let fragCoord = in.position.xy;
let inverseScreenSize = 1.0 / resolution.xy;
let texCoord = in.position.xy * inverseScreenSize;
let centerSample = textureSampleLevel(screenTexture, samp, texCoord, 0.0);
let colorCenter = centerSample.rgb;
// Luma at the current fragment
let lumaCenter = rgb2luma(colorCenter);
// Luma at the four direct neighbours of the current fragment.
let lumaDown = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(0, -1)).rgb);
let lumaUp = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(0, 1)).rgb);
let lumaLeft = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(-1, 0)).rgb);
let lumaRight = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(1, 0)).rgb);
// Find the maximum and minimum luma around the current fragment.
let lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
let lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
// Compute the delta.
let lumaRange = lumaMax - lumaMin;
// If the luma variation is lower that a threshold (or if we are in a really dark area), we are not on an edge, don't perform any AA.
if (lumaRange < max(EDGE_THRESHOLD_MIN, lumaMax * EDGE_THRESHOLD_MAX)) {
return centerSample;
}
// Query the 4 remaining corners lumas.
let lumaDownLeft = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(-1, -1)).rgb);
let lumaUpRight = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(1, 1)).rgb);
let lumaUpLeft = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(-1, 1)).rgb);
let lumaDownRight = rgb2luma(textureSampleLevel(screenTexture, samp, texCoord, 0.0, vec2<i32>(1, -1)).rgb);
// Combine the four edges lumas (using intermediary variables for future computations with the same values).
let lumaDownUp = lumaDown + lumaUp;
let lumaLeftRight = lumaLeft + lumaRight;
// Same for corners
let lumaLeftCorners = lumaDownLeft + lumaUpLeft;
let lumaDownCorners = lumaDownLeft + lumaDownRight;
let lumaRightCorners = lumaDownRight + lumaUpRight;
let lumaUpCorners = lumaUpRight + lumaUpLeft;
// Compute an estimation of the gradient along the horizontal and vertical axis.
let edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners) +
abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 +
abs(-2.0 * lumaRight + lumaRightCorners);
let edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners) +
abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 +
abs(-2.0 * lumaDown + lumaDownCorners);
// Is the local edge horizontal or vertical ?
let isHorizontal = (edgeHorizontal >= edgeVertical);
// Choose the step size (one pixel) accordingly.
var stepLength = select(inverseScreenSize.x, inverseScreenSize.y, isHorizontal);
// Select the two neighboring texels lumas in the opposite direction to the local edge.
var luma1 = select(lumaLeft, lumaDown, isHorizontal);
var luma2 = select(lumaRight, lumaUp, isHorizontal);
// Compute gradients in this direction.
let gradient1 = luma1 - lumaCenter;
let gradient2 = luma2 - lumaCenter;
// Which direction is the steepest ?
let is1Steepest = abs(gradient1) >= abs(gradient2);
// Gradient in the corresponding direction, normalized.
let gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
// Average luma in the correct direction.
var lumaLocalAverage = 0.0;
if (is1Steepest) {
// Switch the direction
stepLength = -stepLength;
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
} else {
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
}
// Shift UV in the correct direction by half a pixel.
// Compute offset (for each iteration step) in the right direction.
var currentUv = texCoord;
var offset = vec2<f32>(0.0, 0.0);
if (isHorizontal) {
currentUv.y = currentUv.y + stepLength * 0.5;
offset.x = inverseScreenSize.x;
} else {
currentUv.x = currentUv.x + stepLength * 0.5;
offset.y = inverseScreenSize.y;
}
// Compute UVs to explore on each side of the edge, orthogonally. The QUALITY allows us to step faster.
var uv1 = currentUv - offset; // * QUALITY(0); // (quality 0 is 1.0)
var uv2 = currentUv + offset; // * QUALITY(0); // (quality 0 is 1.0)
// Read the lumas at both current extremities of the exploration segment, and compute the delta wrt to the local average luma.
var lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture, samp, uv1, 0.0).rgb);
var lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture, samp, uv2, 0.0).rgb);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
// If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge.
var reached1 = abs(lumaEnd1) >= gradientScaled;
var reached2 = abs(lumaEnd2) >= gradientScaled;
var reachedBoth = reached1 && reached2;
// If the side is not reached, we continue to explore in this direction.
uv1 = select(uv1 - offset, uv1, reached1); // * QUALITY(1); // (quality 1 is 1.0)
uv2 = select(uv2 - offset, uv2, reached2); // * QUALITY(1); // (quality 1 is 1.0)
// If both sides have not been reached, continue to explore.
if (!reachedBoth) {
for (var i: i32 = 2; i < ITERATIONS; i = i + 1) {
// If needed, read luma in 1st direction, compute delta.
if (!reached1) {
lumaEnd1 = rgb2luma(textureSampleLevel(screenTexture, samp, uv1, 0.0).rgb);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
}
// If needed, read luma in opposite direction, compute delta.
if (!reached2) {
lumaEnd2 = rgb2luma(textureSampleLevel(screenTexture, samp, uv2, 0.0).rgb);
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
}
// If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge.
reached1 = abs(lumaEnd1) >= gradientScaled;
reached2 = abs(lumaEnd2) >= gradientScaled;
reachedBoth = reached1 && reached2;
// If the side is not reached, we continue to explore in this direction, with a variable quality.
if (!reached1) {
uv1 = uv1 - offset * QUALITY(i);
}
if (!reached2) {
uv2 = uv2 + offset * QUALITY(i);
}
// If both sides have been reached, stop the exploration.
if (reachedBoth) {
break;
}
}
}
// Compute the distances to each side edge of the edge (!).
var distance1 = select(texCoord.y - uv1.y, texCoord.x - uv1.x, isHorizontal);
var distance2 = select(uv2.y - texCoord.y, uv2.x - texCoord.x, isHorizontal);
// In which direction is the side of the edge closer ?
let isDirection1 = distance1 < distance2;
let distanceFinal = min(distance1, distance2);
// Thickness of the edge.
let edgeThickness = (distance1 + distance2);
// Is the luma at center smaller than the local average ?
let isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
// If the luma at center is smaller than at its neighbour, the delta luma at each end should be positive (same variation).
let correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
let correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
// Only keep the result in the direction of the closer side of the edge.
var correctVariation = select(correctVariation2, correctVariation1, isDirection1);
// UV offset: read in the direction of the closest side of the edge.
let pixelOffset = - distanceFinal / edgeThickness + 0.5;
// If the luma variation is incorrect, do not offset.
var finalOffset = select(0.0, pixelOffset, correctVariation);
// Sub-pixel shifting
// Full weighted average of the luma over the 3x3 neighborhood.
let lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
// Ratio of the delta between the global average and the center luma, over the luma range in the 3x3 neighborhood.
let subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
let subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
// Compute a sub-pixel offset based on this delta.
let subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * SUBPIXEL_QUALITY;
// Pick the biggest of the two offsets.
finalOffset = max(finalOffset, subPixelOffsetFinal);
// Compute the final UV coordinates.
var finalUv = texCoord;
if (isHorizontal) {
finalUv.y = finalUv.y + finalOffset * stepLength;
} else {
finalUv.x = finalUv.x + finalOffset * stepLength;
}
// Read the color at the new UV coordinates, and use it.
var finalColor = textureSampleLevel(screenTexture, samp, finalUv, 0.0).rgb;
return vec4<f32>(finalColor, centerSample.a);
}

View file

@ -0,0 +1,249 @@
mod node;
use crate::{
core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state,
fxaa::node::FxaaNode,
};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, HandleUntyped};
use bevy_derive::Deref;
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_reflect::TypeUuid;
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
prelude::Camera,
render_graph::RenderGraph,
render_resource::*,
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, ViewTarget},
RenderApp, RenderStage,
};
#[derive(Eq, PartialEq, Hash, Clone, Copy)]
pub enum Sensitivity {
Low,
Medium,
High,
Ultra,
Extreme,
}
impl Sensitivity {
pub fn get_str(&self) -> &str {
match self {
Sensitivity::Low => "LOW",
Sensitivity::Medium => "MEDIUM",
Sensitivity::High => "HIGH",
Sensitivity::Ultra => "ULTRA",
Sensitivity::Extreme => "EXTREME",
}
}
}
#[derive(Component, Clone)]
pub struct Fxaa {
/// Enable render passes for FXAA.
pub enabled: bool,
/// Use lower sensitivity for a sharper, faster, result.
/// Use higher sensitivity for a slower, smoother, result.
/// Ultra and Turbo settings can result in significant smearing and loss of detail.
/// The minimum amount of local contrast required to apply algorithm.
pub edge_threshold: Sensitivity,
/// Trims the algorithm from processing darks.
pub edge_threshold_min: Sensitivity,
}
impl Default for Fxaa {
fn default() -> Self {
Fxaa {
enabled: true,
edge_threshold: Sensitivity::High,
edge_threshold_min: Sensitivity::High,
}
}
}
impl ExtractComponent for Fxaa {
type Query = &'static Self;
type Filter = With<Camera>;
fn extract_component(item: QueryItem<Self::Query>) -> Self {
item.clone()
}
}
const FXAA_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4182761465141723543);
pub const FXAA_NODE_3D: &str = "fxaa_node_3d";
pub const FXAA_NODE_2D: &str = "fxaa_node_2d";
/// Adds support for Fast Approximate Anti-Aliasing (FXAA)
pub struct FxaaPlugin;
impl Plugin for FxaaPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl);
app.add_plugin(ExtractComponentPlugin::<Fxaa>::default());
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app
.init_resource::<FxaaPipeline>()
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_system_to_stage(RenderStage::Prepare, prepare_fxaa_pipelines);
{
let fxaa_node = FxaaNode::new(&mut render_app.world);
let mut binding = render_app.world.resource_mut::<RenderGraph>();
let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap();
graph.add_node(FXAA_NODE_3D, fxaa_node);
graph
.add_slot_edge(
graph.input_node().unwrap().id,
core_3d::graph::input::VIEW_ENTITY,
FXAA_NODE_3D,
FxaaNode::IN_VIEW,
)
.unwrap();
graph
.add_node_edge(core_3d::graph::node::TONEMAPPING, FXAA_NODE_3D)
.unwrap();
}
{
let fxaa_node = FxaaNode::new(&mut render_app.world);
let mut binding = render_app.world.resource_mut::<RenderGraph>();
let graph = binding.get_sub_graph_mut(core_2d::graph::NAME).unwrap();
graph.add_node(FXAA_NODE_2D, fxaa_node);
graph
.add_slot_edge(
graph.input_node().unwrap().id,
core_2d::graph::input::VIEW_ENTITY,
FXAA_NODE_2D,
FxaaNode::IN_VIEW,
)
.unwrap();
graph
.add_node_edge(core_2d::graph::node::TONEMAPPING, FXAA_NODE_2D)
.unwrap();
}
}
}
#[derive(Resource, Deref)]
pub struct FxaaPipeline {
texture_bind_group: BindGroupLayout,
}
impl FromWorld for FxaaPipeline {
fn from_world(render_world: &mut World) -> Self {
let texture_bind_group = render_world
.resource::<RenderDevice>()
.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("fxaa_texture_bind_group_layout"),
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
});
FxaaPipeline { texture_bind_group }
}
}
#[derive(Component)]
pub struct CameraFxaaPipeline {
pub pipeline_id: CachedRenderPipelineId,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct FxaaPipelineKey {
edge_threshold: Sensitivity,
edge_threshold_min: Sensitivity,
texture_format: TextureFormat,
}
impl SpecializedRenderPipeline for FxaaPipeline {
type Key = FxaaPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
label: Some("fxaa".into()),
layout: Some(vec![self.texture_bind_group.clone()]),
vertex: fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader: FXAA_SHADER_HANDLE.typed(),
shader_defs: vec![
format!("EDGE_THRESH_{}", key.edge_threshold.get_str()),
format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()),
],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: key.texture_format,
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
}
}
}
pub fn prepare_fxaa_pipelines(
mut commands: Commands,
mut pipeline_cache: ResMut<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<FxaaPipeline>>,
fxaa_pipeline: Res<FxaaPipeline>,
views: Query<(Entity, &ExtractedView, &Fxaa)>,
) {
for (entity, view, fxaa) in &views {
if !fxaa.enabled {
continue;
}
let pipeline_id = pipelines.specialize(
&mut pipeline_cache,
&fxaa_pipeline,
FxaaPipelineKey {
edge_threshold: fxaa.edge_threshold,
edge_threshold_min: fxaa.edge_threshold_min,
texture_format: if view.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
},
);
commands
.entity(entity)
.insert(CameraFxaaPipeline { pipeline_id });
}
}

View file

@ -0,0 +1,132 @@
use std::sync::Mutex;
use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline};
use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryState;
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, FilterMode, Operations,
PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor,
TextureViewId,
},
renderer::RenderContext,
view::{ExtractedView, ViewTarget},
};
use bevy_utils::default;
pub struct FxaaNode {
query: QueryState<
(
&'static ViewTarget,
&'static CameraFxaaPipeline,
&'static Fxaa,
),
With<ExtractedView>,
>,
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
}
impl FxaaNode {
pub const IN_VIEW: &'static str = "view";
pub fn new(world: &mut World) -> Self {
Self {
query: QueryState::new(world),
cached_texture_bind_group: Mutex::new(None),
}
}
}
impl Node for FxaaNode {
fn input(&self) -> Vec<SlotInfo> {
vec![SlotInfo::new(FxaaNode::IN_VIEW, SlotType::Entity)]
}
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let pipeline_cache = world.resource::<PipelineCache>();
let fxaa_pipeline = world.resource::<FxaaPipeline>();
let (target, pipeline, fxaa) = match self.query.get_manual(world, view_entity) {
Ok(result) => result,
Err(_) => return Ok(()),
};
if !fxaa.enabled {
return Ok(());
};
let pipeline = pipeline_cache
.get_render_pipeline(pipeline.pipeline_id)
.unwrap();
let post_process = target.post_process_write();
let source = post_process.source;
let destination = post_process.destination;
let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap();
let bind_group = match &mut *cached_bind_group {
Some((id, bind_group)) if source.id() == *id => bind_group,
cached_bind_group => {
let sampler = render_context
.render_device
.create_sampler(&SamplerDescriptor {
mipmap_filter: FilterMode::Linear,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});
let bind_group =
render_context
.render_device
.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &fxaa_pipeline.texture_bind_group,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(source),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&sampler),
},
],
});
let (_, bind_group) = cached_bind_group.insert((source.id(), bind_group));
bind_group
}
};
let pass_descriptor = RenderPassDescriptor {
label: Some("fxaa_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: destination,
resolve_target: None,
ops: Operations::default(),
})],
depth_stencil_attachment: None,
};
let mut render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, bind_group, &[]);
render_pass.draw(0..3, 0..1);
Ok(())
}
}

View file

@ -2,6 +2,7 @@ pub mod clear_color;
pub mod core_2d;
pub mod core_3d;
pub mod fullscreen_vertex_shader;
pub mod fxaa;
pub mod tonemapping;
pub mod upscaling;
@ -19,6 +20,7 @@ use crate::{
core_2d::Core2dPlugin,
core_3d::Core3dPlugin,
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
fxaa::FxaaPlugin,
tonemapping::TonemappingPlugin,
upscaling::UpscalingPlugin,
};
@ -45,6 +47,7 @@ impl Plugin for CorePipelinePlugin {
.add_plugin(TonemappingPlugin)
.add_plugin(UpscalingPlugin)
.add_plugin(Core2dPlugin)
.add_plugin(Core3dPlugin);
.add_plugin(Core3dPlugin)
.add_plugin(FxaaPlugin);
}
}

View file

@ -88,12 +88,12 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
);
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic);
output_color = pbr(pbr_input);
#ifdef TONEMAP_IN_SHADER
output_color = tone_mapping(output_color);
#endif
} else {
output_color = alpha_discard(material, output_color);
}
#ifdef TONEMAP_IN_SHADER
output_color = tone_mapping(output_color);
#endif
return output_color;
}

187
examples/3d/fxaa.rs Normal file
View file

@ -0,0 +1,187 @@
//! This examples compares MSAA (Multi-Sample Anti-Aliasing) and FXAA (Fast Approximate Anti-Aliasing).
use std::f32::consts::PI;
use bevy::{
core_pipeline::fxaa::{Fxaa, Sensitivity},
prelude::*,
render::{
render_resource::{Extent3d, SamplerDescriptor, TextureDimension, TextureFormat},
texture::ImageSampler,
},
};
fn main() {
App::new()
// Disable MSAA be default
.insert_resource(Msaa { samples: 1 })
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(toggle_fxaa)
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
asset_server: Res<AssetServer>,
) {
println!("Toggle with:");
println!("1 - NO AA");
println!("2 - MSAA 4");
println!("3 - FXAA (default)");
println!("Threshold:");
println!("6 - LOW");
println!("7 - MEDIUM");
println!("8 - HIGH (default)");
println!("9 - ULTRA");
println!("0 - EXTREME");
// plane
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
let cube_material = materials.add(StandardMaterial {
base_color_texture: Some(images.add(uv_debug_texture())),
..default()
});
// cubes
for i in 0..5 {
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.25 })),
material: cube_material.clone(),
transform: Transform::from_xyz(i as f32 * 0.25 - 1.0, 0.125, -i as f32 * 0.5),
..default()
});
}
// Flight Helmet
commands.spawn(SceneBundle {
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
..default()
});
// light
const HALF_SIZE: f32 = 2.0;
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
shadow_projection: OrthographicProjection {
left: -HALF_SIZE,
right: HALF_SIZE,
bottom: -HALF_SIZE,
top: HALF_SIZE,
near: -10.0 * HALF_SIZE,
far: 10.0 * HALF_SIZE,
..default()
},
shadows_enabled: true,
..default()
},
transform: Transform::from_rotation(Quat::from_euler(
EulerRot::ZYX,
0.0,
PI * -0.15,
PI * -0.15,
)),
..default()
});
// camera
commands
.spawn(Camera3dBundle {
camera: Camera {
hdr: false, // Works with and without hdr
..default()
},
transform: Transform::from_xyz(0.7, 0.7, 1.0)
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
..default()
})
.insert(Fxaa::default());
}
fn toggle_fxaa(keys: Res<Input<KeyCode>>, mut query: Query<&mut Fxaa>, mut msaa: ResMut<Msaa>) {
let set_no_aa = keys.just_pressed(KeyCode::Key1);
let set_msaa = keys.just_pressed(KeyCode::Key2);
let set_fxaa = keys.just_pressed(KeyCode::Key3);
let fxaa_low = keys.just_pressed(KeyCode::Key6);
let fxaa_med = keys.just_pressed(KeyCode::Key7);
let fxaa_high = keys.just_pressed(KeyCode::Key8);
let fxaa_ultra = keys.just_pressed(KeyCode::Key9);
let fxaa_extreme = keys.just_pressed(KeyCode::Key0);
let set_fxaa = set_fxaa | fxaa_low | fxaa_med | fxaa_high | fxaa_ultra | fxaa_extreme;
for mut fxaa in &mut query {
if set_msaa {
fxaa.enabled = false;
msaa.samples = 4;
info!("MSAA 4x");
}
if set_no_aa {
fxaa.enabled = false;
msaa.samples = 1;
info!("NO AA");
}
if set_no_aa | set_fxaa {
msaa.samples = 1;
}
if fxaa_low {
fxaa.edge_threshold = Sensitivity::Low;
fxaa.edge_threshold_min = Sensitivity::Low;
} else if fxaa_med {
fxaa.edge_threshold = Sensitivity::Medium;
fxaa.edge_threshold_min = Sensitivity::Medium;
} else if fxaa_high {
fxaa.edge_threshold = Sensitivity::High;
fxaa.edge_threshold_min = Sensitivity::High;
} else if fxaa_ultra {
fxaa.edge_threshold = Sensitivity::Ultra;
fxaa.edge_threshold_min = Sensitivity::Ultra;
} else if fxaa_extreme {
fxaa.edge_threshold = Sensitivity::Extreme;
fxaa.edge_threshold_min = Sensitivity::Extreme;
}
if set_fxaa {
fxaa.enabled = true;
msaa.samples = 1;
info!("FXAA {}", fxaa.edge_threshold.get_str());
}
}
}
/// Creates a colorful test pattern
fn uv_debug_texture() -> Image {
const TEXTURE_SIZE: usize = 8;
let mut palette: [u8; 32] = [
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
];
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
for y in 0..TEXTURE_SIZE {
let offset = TEXTURE_SIZE * y * 4;
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
palette.rotate_right(4);
}
let mut img = Image::new_fill(
Extent3d {
width: TEXTURE_SIZE as u32,
height: TEXTURE_SIZE as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&texture_data,
TextureFormat::Rgba8UnormSrgb,
);
img.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor::default());
img
}

View file

@ -106,6 +106,7 @@ Example | Description
--- | ---
[3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting
[3D Shapes](../examples/3d/3d_shapes.rs) | A scene showcasing the built-in 3D shapes
[FXAA](../examples/3d/fxaa.rs) | Compares MSAA (Multi-Sample Anti-Aliasing) and FXAA (Fast Approximate Anti-Aliasing)
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene