mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
Start a built-in postprocessing stack, and implement chromatic aberration in it. (#13695)
This commit creates a new built-in postprocessing shader that's designed to hold miscellaneous postprocessing effects, and starts it off with chromatic aberration. Possible future effects include vignette, film grain, and lens distortion. [Chromatic aberration] is a common postprocessing effect that simulates lenses that fail to focus all colors of light to a single point. It's often used for impact effects and/or horror games. This patch uses the technique from *Inside* ([Gjøl & Svendsen 2016]), which allows the developer to customize the particular color pattern to achieve different effects. Unity HDRP uses the same technique, while Unreal has a hard-wired fixed color pattern. A new example, `post_processing`, has been added, in order to demonstrate the technique. The existing `post_processing` shader has been renamed to `custom_post_processing`, for clarity. [Chromatic aberration]: https://en.wikipedia.org/wiki/Chromatic_aberration [Gjøl & Svendsen 2016]: https://github.com/playdeadgames/publications/blob/master/INSIDE/rendering_inside_gdc2016.pdf ![Screenshot 2024-06-04 180304](https://github.com/bevyengine/bevy/assets/157897/3631c64f-a615-44fe-91ca-7f04df0a54b2) ![Screenshot 2024-06-04 180743](https://github.com/bevyengine/bevy/assets/157897/ee055cbf-4314-49c5-8bfa-8d8a17bd52bb) ## Changelog ### Added * Chromatic aberration is now available as a built-in postprocessing effect. To use it, add `ChromaticAberration` to your camera.
This commit is contained in:
parent
ed2b8e0f35
commit
fcda67e894
10 changed files with 843 additions and 4 deletions
17
Cargo.toml
17
Cargo.toml
|
@ -2237,11 +2237,11 @@ category = "Shaders"
|
|||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "post_processing"
|
||||
path = "examples/shader/post_processing.rs"
|
||||
name = "custom_post_processing"
|
||||
path = "examples/shader/custom_post_processing.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.post_processing]
|
||||
[package.metadata.example.custom_post_processing]
|
||||
name = "Post Processing - Custom Render Pass"
|
||||
description = "A custom post processing effect, using a custom render pass that runs after the main pass"
|
||||
category = "Shaders"
|
||||
|
@ -3284,6 +3284,17 @@ description = "Handles input, physics, and rendering in an industry-standard way
|
|||
category = "Movement"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "post_processing"
|
||||
path = "examples/3d/post_processing.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.post_processing]
|
||||
name = "Built-in postprocessing"
|
||||
description = "Demonstrates the built-in postprocessing features"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
|
|
@ -18,6 +18,7 @@ pub mod graph {
|
|||
MainTransparentPass,
|
||||
EndMainPass,
|
||||
Bloom,
|
||||
PostProcessing,
|
||||
Tonemapping,
|
||||
Fxaa,
|
||||
Smaa,
|
||||
|
|
|
@ -30,6 +30,7 @@ pub mod graph {
|
|||
Bloom,
|
||||
AutoExposure,
|
||||
DepthOfField,
|
||||
PostProcessing,
|
||||
Tonemapping,
|
||||
Fxaa,
|
||||
Smaa,
|
||||
|
|
|
@ -19,6 +19,7 @@ pub mod fullscreen_vertex_shader;
|
|||
pub mod fxaa;
|
||||
pub mod motion_blur;
|
||||
pub mod msaa_writeback;
|
||||
pub mod post_process;
|
||||
pub mod prepass;
|
||||
mod skybox;
|
||||
pub mod smaa;
|
||||
|
@ -60,6 +61,7 @@ use crate::{
|
|||
fxaa::FxaaPlugin,
|
||||
motion_blur::MotionBlurPlugin,
|
||||
msaa_writeback::MsaaWritebackPlugin,
|
||||
post_process::PostProcessingPlugin,
|
||||
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
|
||||
smaa::SmaaPlugin,
|
||||
tonemapping::TonemappingPlugin,
|
||||
|
@ -99,6 +101,7 @@ impl Plugin for CorePipelinePlugin {
|
|||
MotionBlurPlugin,
|
||||
DepthOfFieldPlugin,
|
||||
SmaaPlugin,
|
||||
PostProcessingPlugin,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
// The chromatic aberration postprocessing effect.
|
||||
//
|
||||
// This makes edges of objects turn into multicolored streaks.
|
||||
|
||||
#define_import_path bevy_core_pipeline::post_processing::chromatic_aberration
|
||||
|
||||
// See `bevy_core_pipeline::post_process::ChromaticAberration` for more
|
||||
// information on these fields.
|
||||
struct ChromaticAberrationSettings {
|
||||
intensity: f32,
|
||||
max_samples: u32,
|
||||
unused_a: u32,
|
||||
unused_b: u32,
|
||||
}
|
||||
|
||||
// The source framebuffer texture.
|
||||
@group(0) @binding(0) var chromatic_aberration_source_texture: texture_2d<f32>;
|
||||
// The sampler used to sample the source framebuffer texture.
|
||||
@group(0) @binding(1) var chromatic_aberration_source_sampler: sampler;
|
||||
// The 1D lookup table for chromatic aberration.
|
||||
@group(0) @binding(2) var chromatic_aberration_lut_texture: texture_2d<f32>;
|
||||
// The sampler used to sample that lookup table.
|
||||
@group(0) @binding(3) var chromatic_aberration_lut_sampler: sampler;
|
||||
// The settings supplied by the developer.
|
||||
@group(0) @binding(4) var<uniform> chromatic_aberration_settings: ChromaticAberrationSettings;
|
||||
|
||||
fn chromatic_aberration(start_pos: vec2<f32>) -> vec3<f32> {
|
||||
// Radial chromatic aberration implemented using the *Inside* technique:
|
||||
//
|
||||
// <https://github.com/playdeadgames/publications/blob/master/INSIDE/rendering_inside_gdc2016.pdf>
|
||||
|
||||
let end_pos = mix(start_pos, vec2(0.5), chromatic_aberration_settings.intensity);
|
||||
|
||||
// Determine the number of samples. We aim for one sample per texel, unless
|
||||
// that's higher than the developer-specified maximum number of samples, in
|
||||
// which case we choose the maximum number of samples.
|
||||
let texel_length = length((end_pos - start_pos) *
|
||||
vec2<f32>(textureDimensions(chromatic_aberration_source_texture)));
|
||||
let sample_count = min(u32(ceil(texel_length)), chromatic_aberration_settings.max_samples);
|
||||
|
||||
var color: vec3<f32>;
|
||||
if (sample_count > 1u) {
|
||||
// The LUT texture is in clamp-to-edge mode, so we start at 0.5 texels
|
||||
// from the sides so that we have a nice gradient over the entire LUT
|
||||
// range.
|
||||
let lut_u_offset = 0.5 / f32(textureDimensions(chromatic_aberration_lut_texture).x);
|
||||
|
||||
var sample_sum = vec3(0.0);
|
||||
var modulate_sum = vec3(0.0);
|
||||
|
||||
// Start accumulating samples.
|
||||
for (var sample_index = 0u; sample_index < sample_count; sample_index += 1u) {
|
||||
let t = (f32(sample_index) + 0.5) / f32(sample_count);
|
||||
|
||||
// Sample the framebuffer.
|
||||
let sample_uv = mix(start_pos, end_pos, t);
|
||||
let sample = textureSampleLevel(
|
||||
chromatic_aberration_source_texture,
|
||||
chromatic_aberration_source_sampler,
|
||||
sample_uv,
|
||||
0.0,
|
||||
).rgb;
|
||||
|
||||
// Sample the LUT.
|
||||
let lut_u = mix(lut_u_offset, 1.0 - lut_u_offset, t);
|
||||
let modulate = textureSampleLevel(
|
||||
chromatic_aberration_lut_texture,
|
||||
chromatic_aberration_lut_sampler,
|
||||
vec2(lut_u, 0.5),
|
||||
0.0,
|
||||
).rgb;
|
||||
|
||||
// Modulate the sample by the LUT value.
|
||||
sample_sum += sample * modulate;
|
||||
modulate_sum += modulate;
|
||||
}
|
||||
|
||||
color = sample_sum / modulate_sum;
|
||||
} else {
|
||||
// If there's only one sample, don't do anything. If we don't do this,
|
||||
// then this shader will apply whatever tint is in the center of the LUT
|
||||
// texture to such pixels, which is wrong.
|
||||
color = textureSampleLevel(
|
||||
chromatic_aberration_source_texture,
|
||||
chromatic_aberration_source_sampler,
|
||||
start_pos,
|
||||
0.0,
|
||||
).rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
505
crates/bevy_core_pipeline/src/post_process/mod.rs
Normal file
505
crates/bevy_core_pipeline/src/post_process/mod.rs
Normal file
|
@ -0,0 +1,505 @@
|
|||
//! Miscellaneous built-in postprocessing effects.
|
||||
//!
|
||||
//! Currently, this consists only of chromatic aberration.
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
query::{QueryItem, With},
|
||||
reflect::ReflectComponent,
|
||||
schedule::IntoSystemConfigs as _,
|
||||
system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
camera::Camera,
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
render_asset::{RenderAssetUsages, RenderAssets},
|
||||
render_graph::{
|
||||
NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner,
|
||||
},
|
||||
render_resource::{
|
||||
binding_types::{sampler, texture_2d, uniform_buffer},
|
||||
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
|
||||
ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d, FilterMode, FragmentState,
|
||||
Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
|
||||
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
|
||||
ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines,
|
||||
TextureDimension, TextureFormat, TextureSampleType,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
texture::{BevyDefault, GpuImage, Image},
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::prelude::default;
|
||||
|
||||
use crate::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader,
|
||||
};
|
||||
|
||||
/// The handle to the built-in postprocessing shader `post_process.wgsl`.
|
||||
const POST_PROCESSING_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(14675654334038973533);
|
||||
/// The handle to the chromatic aberration shader `chromatic_aberration.wgsl`.
|
||||
const CHROMATIC_ABERRATION_SHADER_HANDLE: Handle<Shader> =
|
||||
Handle::weak_from_u128(10969893303667163833);
|
||||
|
||||
/// The handle to the default chromatic aberration lookup texture.
|
||||
///
|
||||
/// This is just a 3x1 image consisting of one red pixel, one green pixel, and
|
||||
/// one blue pixel, in that order.
|
||||
const DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE: Handle<Image> =
|
||||
Handle::weak_from_u128(2199972955136579180);
|
||||
|
||||
/// The default chromatic aberration intensity amount, in a fraction of the
|
||||
/// window size.
|
||||
const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02;
|
||||
|
||||
/// The default maximum number of samples for chromatic aberration.
|
||||
const DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES: u32 = 8;
|
||||
|
||||
/// The raw RGBA data for the default chromatic aberration gradient.
|
||||
///
|
||||
/// This consists of one red pixel, one green pixel, and one blue pixel, in that
|
||||
/// order.
|
||||
static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] =
|
||||
[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255];
|
||||
|
||||
/// A plugin that implements a built-in postprocessing stack with some common
|
||||
/// effects.
|
||||
///
|
||||
/// Currently, this only consists of chromatic aberration.
|
||||
pub struct PostProcessingPlugin;
|
||||
|
||||
/// Adds colored fringes to the edges of objects in the scene.
|
||||
///
|
||||
/// [Chromatic aberration] simulates the effect when lenses fail to focus all
|
||||
/// colors of light toward a single point. It causes rainbow-colored streaks to
|
||||
/// appear, which are especially apparent on the edges of objects. Chromatic
|
||||
/// aberration is commonly used for collision effects, especially in horror
|
||||
/// games.
|
||||
///
|
||||
/// Bevy's implementation is based on that of *Inside* ([Gjøl & Svendsen 2016]).
|
||||
/// It's based on a customizable lookup texture, which allows for changing the
|
||||
/// color pattern. By default, the color pattern is simply a 3×1 pixel texture
|
||||
/// consisting of red, green, and blue, in that order, but you can change it to
|
||||
/// any image in order to achieve different effects.
|
||||
///
|
||||
/// [Chromatic aberration]: https://en.wikipedia.org/wiki/Chromatic_aberration
|
||||
///
|
||||
/// [Gjøl & Svendsen 2016]: https://github.com/playdeadgames/publications/blob/master/INSIDE/rendering_inside_gdc2016.pdf
|
||||
#[derive(Reflect, Component, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct ChromaticAberration {
|
||||
/// The lookup texture that determines the color gradient.
|
||||
///
|
||||
/// By default, this is a 3×1 texel texture consisting of one red pixel, one
|
||||
/// green pixel, and one blue texel, in that order. This recreates the most
|
||||
/// typical chromatic aberration pattern. However, you can change it to
|
||||
/// achieve different artistic effects.
|
||||
///
|
||||
/// The texture is always sampled in its vertical center, so it should
|
||||
/// ordinarily have a height of 1 texel.
|
||||
pub color_lut: Handle<Image>,
|
||||
|
||||
/// The size of the streaks around the edges of objects, as a fraction of
|
||||
/// the window size.
|
||||
///
|
||||
/// The default value is 0.2.
|
||||
pub intensity: f32,
|
||||
|
||||
/// A cap on the number of texture samples that will be performed.
|
||||
///
|
||||
/// Higher values result in smoother-looking streaks but are slower.
|
||||
///
|
||||
/// The default value is 8.
|
||||
pub max_samples: u32,
|
||||
}
|
||||
|
||||
/// GPU pipeline data for the built-in postprocessing stack.
|
||||
///
|
||||
/// This is stored in the render world.
|
||||
#[derive(Resource)]
|
||||
pub struct PostProcessingPipeline {
|
||||
/// The layout of bind group 0, containing the source, LUT, and settings.
|
||||
bind_group_layout: BindGroupLayout,
|
||||
/// Specifies how to sample the source framebuffer texture.
|
||||
source_sampler: Sampler,
|
||||
/// Specifies how to sample the chromatic aberration gradient.
|
||||
chromatic_aberration_lut_sampler: Sampler,
|
||||
}
|
||||
|
||||
/// A key that uniquely identifies a built-in postprocessing pipeline.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct PostProcessingPipelineKey {
|
||||
/// The format of the source and destination textures.
|
||||
texture_format: TextureFormat,
|
||||
}
|
||||
|
||||
/// A component attached to cameras in the render world that stores the
|
||||
/// specialized pipeline ID for the built-in postprocessing stack.
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
pub struct PostProcessingPipelineId(CachedRenderPipelineId);
|
||||
|
||||
/// The on-GPU version of the [`ChromaticAberration`] settings.
|
||||
///
|
||||
/// See the documentation for [`ChromaticAberration`] for more information on
|
||||
/// each of these fields.
|
||||
#[derive(ShaderType)]
|
||||
pub struct ChromaticAberrationUniform {
|
||||
/// The intensity of the effect, in a fraction of the screen.
|
||||
intensity: f32,
|
||||
/// A cap on the number of samples of the source texture that the shader
|
||||
/// will perform.
|
||||
max_samples: u32,
|
||||
/// Padding data.
|
||||
unused_1: u32,
|
||||
/// Padding data.
|
||||
unused_2: u32,
|
||||
}
|
||||
|
||||
/// A resource, part of the render world, that stores the
|
||||
/// [`ChromaticAberrationUniform`]s for each view.
|
||||
#[derive(Resource, Deref, DerefMut, Default)]
|
||||
pub struct PostProcessingUniformBuffers {
|
||||
chromatic_aberration: DynamicUniformBuffer<ChromaticAberrationUniform>,
|
||||
}
|
||||
|
||||
/// A component, part of the render world, that stores the appropriate byte
|
||||
/// offset within the [`PostProcessingUniformBuffers`] for the camera it's
|
||||
/// attached to.
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
pub struct PostProcessingUniformBufferOffsets {
|
||||
chromatic_aberration: u32,
|
||||
}
|
||||
|
||||
/// The render node that runs the built-in postprocessing stack.
|
||||
#[derive(Default)]
|
||||
pub struct PostProcessingNode;
|
||||
|
||||
impl Plugin for PostProcessingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(
|
||||
app,
|
||||
POST_PROCESSING_SHADER_HANDLE,
|
||||
"post_process.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
CHROMATIC_ABERRATION_SHADER_HANDLE,
|
||||
"chromatic_aberration.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
|
||||
// Load the default chromatic aberration LUT.
|
||||
let mut assets = app.world_mut().resource_mut::<Assets<_>>();
|
||||
assets.insert(
|
||||
DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE.id(),
|
||||
Image::new(
|
||||
Extent3d {
|
||||
width: 3,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
TextureDimension::D2,
|
||||
DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),
|
||||
TextureFormat::Rgba8UnormSrgb,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
),
|
||||
);
|
||||
|
||||
app.register_type::<ChromaticAberration>();
|
||||
app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default());
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()
|
||||
.init_resource::<PostProcessingUniformBuffers>()
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_post_processing_pipelines,
|
||||
prepare_post_processing_uniforms,
|
||||
)
|
||||
.in_set(RenderSet::Prepare),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
|
||||
Core3d,
|
||||
Node3d::PostProcessing,
|
||||
)
|
||||
.add_render_graph_edges(
|
||||
Core3d,
|
||||
(
|
||||
Node3d::DepthOfField,
|
||||
Node3d::PostProcessing,
|
||||
Node3d::Tonemapping,
|
||||
),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
|
||||
Core2d,
|
||||
Node2d::PostProcessing,
|
||||
)
|
||||
.add_render_graph_edges(
|
||||
Core2d,
|
||||
(Node2d::Bloom, Node2d::PostProcessing, Node2d::Tonemapping),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
render_app.init_resource::<PostProcessingPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChromaticAberration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color_lut: DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE,
|
||||
intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY,
|
||||
max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for PostProcessingPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
|
||||
// Create our single bind group layout.
|
||||
let bind_group_layout = render_device.create_bind_group_layout(
|
||||
Some("postprocessing bind group layout"),
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
// Chromatic aberration source:
|
||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||
// Chromatic aberration source sampler:
|
||||
sampler(SamplerBindingType::Filtering),
|
||||
// Chromatic aberration LUT:
|
||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||
// Chromatic aberration LUT sampler:
|
||||
sampler(SamplerBindingType::Filtering),
|
||||
// Chromatic aberration settings:
|
||||
uniform_buffer::<ChromaticAberrationUniform>(true),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Both source and chromatic aberration LUTs should be sampled
|
||||
// bilinearly.
|
||||
|
||||
let source_sampler = render_device.create_sampler(&SamplerDescriptor {
|
||||
mipmap_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
mag_filter: FilterMode::Linear,
|
||||
..default()
|
||||
});
|
||||
|
||||
let chromatic_aberration_lut_sampler = render_device.create_sampler(&SamplerDescriptor {
|
||||
mipmap_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
mag_filter: FilterMode::Linear,
|
||||
..default()
|
||||
});
|
||||
|
||||
PostProcessingPipeline {
|
||||
bind_group_layout,
|
||||
source_sampler,
|
||||
chromatic_aberration_lut_sampler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for PostProcessingPipeline {
|
||||
type Key = PostProcessingPipelineKey;
|
||||
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("postprocessing".into()),
|
||||
layout: vec![self.bind_group_layout.clone()],
|
||||
vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: POST_PROCESSING_SHADER_HANDLE,
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment_main".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: key.texture_format,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: default(),
|
||||
depth_stencil: None,
|
||||
multisample: default(),
|
||||
push_constant_ranges: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewNode for PostProcessingNode {
|
||||
type ViewQuery = (
|
||||
Read<ViewTarget>,
|
||||
Read<PostProcessingPipelineId>,
|
||||
Read<ChromaticAberration>,
|
||||
Read<PostProcessingUniformBufferOffsets>,
|
||||
);
|
||||
|
||||
fn run<'w>(
|
||||
&self,
|
||||
_: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext<'w>,
|
||||
(view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let post_processing_pipeline = world.resource::<PostProcessingPipeline>();
|
||||
let post_processing_uniform_buffers = world.resource::<PostProcessingUniformBuffers>();
|
||||
let gpu_image_assets = world.resource::<RenderAssets<GpuImage>>();
|
||||
|
||||
// We need a render pipeline to be prepared.
|
||||
let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// We need the chromatic aberration LUT to be present.
|
||||
let Some(chromatic_aberration_lut) = gpu_image_assets.get(&chromatic_aberration.color_lut)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// We need the postprocessing settings to be uploaded to the GPU.
|
||||
let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers
|
||||
.chromatic_aberration
|
||||
.binding()
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Use the [`PostProcessWrite`] infrastructure, since this is a
|
||||
// full-screen pass.
|
||||
let post_process = view_target.post_process_write();
|
||||
|
||||
let pass_descriptor = RenderPassDescriptor {
|
||||
label: Some("postprocessing pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view: post_process.destination,
|
||||
resolve_target: None,
|
||||
ops: Operations::default(),
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
};
|
||||
|
||||
let bind_group = render_context.render_device().create_bind_group(
|
||||
Some("postprocessing bind group"),
|
||||
&post_processing_pipeline.bind_group_layout,
|
||||
&BindGroupEntries::sequential((
|
||||
post_process.source,
|
||||
&post_processing_pipeline.source_sampler,
|
||||
&chromatic_aberration_lut.texture_view,
|
||||
&post_processing_pipeline.chromatic_aberration_lut_sampler,
|
||||
chromatic_aberration_uniform_buffer_binding,
|
||||
)),
|
||||
);
|
||||
|
||||
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, &[**post_processing_uniform_buffer_offsets]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Specializes the built-in postprocessing pipeline for each applicable view.
|
||||
pub fn prepare_post_processing_pipelines(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessingPipeline>>,
|
||||
post_processing_pipeline: Res<PostProcessingPipeline>,
|
||||
views: Query<(Entity, &ExtractedView), With<ChromaticAberration>>,
|
||||
) {
|
||||
for (entity, view) in views.iter() {
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&post_processing_pipeline,
|
||||
PostProcessingPipelineKey {
|
||||
texture_format: if view.hdr {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(PostProcessingPipelineId(pipeline_id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Gathers the built-in postprocessing settings for every view and uploads them
|
||||
/// to the GPU.
|
||||
pub fn prepare_post_processing_uniforms(
|
||||
mut commands: Commands,
|
||||
mut post_processing_uniform_buffers: ResMut<PostProcessingUniformBuffers>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut views: Query<(Entity, &ChromaticAberration)>,
|
||||
) {
|
||||
post_processing_uniform_buffers.clear();
|
||||
|
||||
// Gather up all the postprocessing settings.
|
||||
for (view_entity, chromatic_aberration) in views.iter_mut() {
|
||||
let chromatic_aberration_uniform_buffer_offset =
|
||||
post_processing_uniform_buffers.push(&ChromaticAberrationUniform {
|
||||
intensity: chromatic_aberration.intensity,
|
||||
max_samples: chromatic_aberration.max_samples,
|
||||
unused_1: 0,
|
||||
unused_2: 0,
|
||||
});
|
||||
commands
|
||||
.entity(view_entity)
|
||||
.insert(PostProcessingUniformBufferOffsets {
|
||||
chromatic_aberration: chromatic_aberration_uniform_buffer_offset,
|
||||
});
|
||||
}
|
||||
|
||||
// Upload to the GPU.
|
||||
post_processing_uniform_buffers.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
|
||||
impl ExtractComponent for ChromaticAberration {
|
||||
type QueryData = Read<ChromaticAberration>;
|
||||
|
||||
type QueryFilter = With<Camera>;
|
||||
|
||||
type Out = ChromaticAberration;
|
||||
|
||||
fn extract_component(
|
||||
chromatic_aberration: QueryItem<'_, Self::QueryData>,
|
||||
) -> Option<Self::Out> {
|
||||
// Skip the postprocessing phase entirely if the intensity is zero.
|
||||
if chromatic_aberration.intensity > 0.0 {
|
||||
Some(chromatic_aberration.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Miscellaneous postprocessing effects, currently just chromatic aberration.
|
||||
|
||||
#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
|
||||
#import bevy_core_pipeline::post_processing::chromatic_aberration::chromatic_aberration
|
||||
|
||||
@fragment
|
||||
fn fragment_main(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
|
||||
return vec4(chromatic_aberration(in.uv), 1.0);
|
||||
}
|
216
examples/3d/post_processing.rs
Normal file
216
examples/3d/post_processing.rs
Normal file
|
@ -0,0 +1,216 @@
|
|||
//! Demonstrates Bevy's built-in postprocessing features.
|
||||
//!
|
||||
//! Currently, this simply consists of chromatic aberration.
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::post_process::ChromaticAberration, pbr::CascadeShadowConfigBuilder, prelude::*,
|
||||
};
|
||||
|
||||
/// The number of units per frame to add to or subtract from intensity when the
|
||||
/// arrow keys are held.
|
||||
const CHROMATIC_ABERRATION_INTENSITY_ADJUSTMENT_SPEED: f32 = 0.002;
|
||||
|
||||
/// The maximum supported chromatic aberration intensity level.
|
||||
const MAX_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.4;
|
||||
|
||||
/// The settings that the user can control.
|
||||
#[derive(Resource)]
|
||||
struct AppSettings {
|
||||
/// The intensity of the chromatic aberration effect.
|
||||
chromatic_aberration_intensity: f32,
|
||||
}
|
||||
|
||||
/// The entry point.
|
||||
fn main() {
|
||||
App::new()
|
||||
.init_resource::<AppSettings>()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Bevy Chromatic Aberration Example".into(),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, handle_keyboard_input)
|
||||
.add_systems(
|
||||
Update,
|
||||
(update_chromatic_aberration_settings, update_help_text)
|
||||
.run_if(resource_changed::<AppSettings>)
|
||||
.after(handle_keyboard_input),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// Creates the example scene and spawns the UI.
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: Res<AppSettings>) {
|
||||
// Spawn the camera.
|
||||
spawn_camera(&mut commands, &asset_server);
|
||||
|
||||
// Create the scene.
|
||||
spawn_scene(&mut commands, &asset_server);
|
||||
|
||||
// Spawn the help text.
|
||||
spawn_text(&mut commands, &app_settings);
|
||||
}
|
||||
|
||||
/// Spawns the camera, including the [`ChromaticAberration`] component.
|
||||
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
camera: Camera {
|
||||
hdr: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(0.7, 0.7, 1.0)
|
||||
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
FogSettings {
|
||||
color: Color::srgb_u8(43, 44, 47),
|
||||
falloff: FogFalloff::Linear {
|
||||
start: 1.0,
|
||||
end: 8.0,
|
||||
},
|
||||
..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"),
|
||||
intensity: 2000.0,
|
||||
},
|
||||
// Include the `ChromaticAberration` component.
|
||||
ChromaticAberration::default(),
|
||||
));
|
||||
}
|
||||
|
||||
/// Spawns the scene.
|
||||
///
|
||||
/// This is just the tonemapping test scene, chosen for the fact that it uses a
|
||||
/// variety of colors.
|
||||
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
|
||||
// Spawn the main scene.
|
||||
commands.spawn(SceneBundle {
|
||||
scene: asset_server.load(
|
||||
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
|
||||
),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Spawn the flight helmet.
|
||||
commands.spawn(SceneBundle {
|
||||
scene: asset_server
|
||||
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
|
||||
transform: Transform::from_xyz(0.5, 0.0, -0.5)
|
||||
.with_rotation(Quat::from_rotation_y(-0.15 * PI)),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Spawn the light.
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
illuminance: 15000.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_rotation(Quat::from_euler(
|
||||
EulerRot::ZYX,
|
||||
0.0,
|
||||
PI * -0.15,
|
||||
PI * -0.15,
|
||||
)),
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
maximum_distance: 3.0,
|
||||
first_cascade_far_bound: 0.9,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
/// Spawns the help text at the bottom of the screen.
|
||||
fn spawn_text(commands: &mut Commands, app_settings: &AppSettings) {
|
||||
commands.spawn(
|
||||
TextBundle {
|
||||
text: create_help_text(app_settings),
|
||||
..default()
|
||||
}
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
bottom: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
impl Default for AppSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chromatic_aberration_intensity: ChromaticAberration::default().intensity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates help text at the bottom of the screen.
|
||||
fn create_help_text(app_settings: &AppSettings) -> Text {
|
||||
Text::from_section(
|
||||
format!(
|
||||
"Chromatic aberration intensity: {} (Press Left or Right to change)",
|
||||
app_settings.chromatic_aberration_intensity
|
||||
),
|
||||
TextStyle::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Handles requests from the user to change the chromatic aberration intensity.
|
||||
fn handle_keyboard_input(mut app_settings: ResMut<AppSettings>, input: Res<ButtonInput<KeyCode>>) {
|
||||
let mut delta = 0.0;
|
||||
if input.pressed(KeyCode::ArrowLeft) {
|
||||
delta -= CHROMATIC_ABERRATION_INTENSITY_ADJUSTMENT_SPEED;
|
||||
} else if input.pressed(KeyCode::ArrowRight) {
|
||||
delta += CHROMATIC_ABERRATION_INTENSITY_ADJUSTMENT_SPEED;
|
||||
}
|
||||
|
||||
// If no arrow key was pressed, just bail out.
|
||||
if delta == 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
app_settings.chromatic_aberration_intensity = (app_settings.chromatic_aberration_intensity
|
||||
+ delta)
|
||||
.clamp(0.0, MAX_CHROMATIC_ABERRATION_INTENSITY);
|
||||
}
|
||||
|
||||
/// Updates the [`ChromaticAberration`] settings per the [`AppSettings`].
|
||||
fn update_chromatic_aberration_settings(
|
||||
mut chromatic_aberration_settings: Query<&mut ChromaticAberration>,
|
||||
app_settings: Res<AppSettings>,
|
||||
) {
|
||||
let intensity = app_settings.chromatic_aberration_intensity;
|
||||
|
||||
// Pick a reasonable maximum sample size for the intensity to avoid an
|
||||
// artifact whereby the individual samples appear instead of producing
|
||||
// smooth streaks of color.
|
||||
//
|
||||
// Don't take this formula too seriously; it hasn't been heavily tuned.
|
||||
let max_samples = ((intensity - 0.02) / (0.20 - 0.02) * 56.0 + 8.0)
|
||||
.clamp(8.0, 64.0)
|
||||
.round() as u32;
|
||||
|
||||
for mut chromatic_aberration_settings in &mut chromatic_aberration_settings {
|
||||
chromatic_aberration_settings.intensity = intensity;
|
||||
chromatic_aberration_settings.max_samples = max_samples;
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the help text at the bottom of the screen to reflect the current
|
||||
/// [`AppSettings`].
|
||||
fn update_help_text(mut text: Query<&mut Text>, app_settings: Res<AppSettings>) {
|
||||
for mut text in text.iter_mut() {
|
||||
*text = create_help_text(&app_settings);
|
||||
}
|
||||
}
|
|
@ -135,6 +135,7 @@ Example | Description
|
|||
[Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect
|
||||
[Auto Exposure](../examples/3d/auto_exposure.rs) | A scene showcasing auto exposure
|
||||
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
|
||||
[Built-in postprocessing](../examples/3d/post_processing.rs) | Demonstrates the built-in postprocessing features
|
||||
[Clearcoat](../examples/3d/clearcoat.rs) | Demonstrates the clearcoat PBR feature
|
||||
[Color grading](../examples/3d/color_grading.rs) | Demonstrates color grading
|
||||
[Deferred Rendering](../examples/3d/deferred_rendering.rs) | Renders meshes with both forward and deferred pipelines
|
||||
|
@ -386,7 +387,7 @@ Example | Description
|
|||
[Material - GLSL](../examples/shader/shader_material_glsl.rs) | A shader that uses the GLSL shading language
|
||||
[Material - Screenspace Texture](../examples/shader/shader_material_screenspace_texture.rs) | A shader that samples a texture with view-independent UV coordinates
|
||||
[Material Prepass](../examples/shader/shader_prepass.rs) | A shader that uses the various textures generated by the prepass
|
||||
[Post Processing - Custom Render Pass](../examples/shader/post_processing.rs) | A custom post processing effect, using a custom render pass that runs after the main pass
|
||||
[Post Processing - Custom Render Pass](../examples/shader/custom_post_processing.rs) | A custom post processing effect, using a custom render pass that runs after the main pass
|
||||
[Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader)
|
||||
[Texture Binding Array (Bindless Textures)](../examples/shader/texture_binding_array.rs) | A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures).
|
||||
|
||||
|
|
Loading…
Reference in a new issue