Path: blob/main/crates/bevy_post_process/src/effect_stack/mod.rs
6596 views
//! Miscellaneous built-in postprocessing effects.1//!2//! Currently, this consists only of chromatic aberration.34use bevy_app::{App, Plugin};5use bevy_asset::{6embedded_asset, load_embedded_asset, AssetServer, Assets, Handle, RenderAssetUsages,7};8use bevy_camera::Camera;9use bevy_derive::{Deref, DerefMut};10use bevy_ecs::{11component::Component,12entity::Entity,13query::{QueryItem, With},14reflect::ReflectComponent,15resource::Resource,16schedule::IntoScheduleConfigs as _,17system::{lifetimeless::Read, Commands, Query, Res, ResMut},18world::World,19};20use bevy_image::{BevyDefault, Image};21use bevy_reflect::{std_traits::ReflectDefault, Reflect};22use bevy_render::{23diagnostic::RecordDiagnostics,24extract_component::{ExtractComponent, ExtractComponentPlugin},25render_asset::RenderAssets,26render_graph::{27NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner,28},29render_resource::{30binding_types::{sampler, texture_2d, uniform_buffer},31BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,32ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d, FilterMode, FragmentState,33Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,34RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,35ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDimension,36TextureFormat, TextureSampleType,37},38renderer::{RenderContext, RenderDevice, RenderQueue},39texture::GpuImage,40view::{ExtractedView, ViewTarget},41Render, RenderApp, RenderStartup, RenderSystems,42};43use bevy_shader::{load_shader_library, Shader};44use bevy_utils::prelude::default;4546use bevy_core_pipeline::{47core_2d::graph::{Core2d, Node2d},48core_3d::graph::{Core3d, Node3d},49FullscreenShader,50};5152/// The default chromatic aberration intensity amount, in a fraction of the53/// window size.54const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02;5556/// The default maximum number of samples for chromatic aberration.57const DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES: u32 = 8;5859/// The raw RGBA data for the default chromatic aberration gradient.60///61/// This consists of one red pixel, one green pixel, and one blue pixel, in that62/// order.63static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] =64[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255];6566#[derive(Resource)]67struct DefaultChromaticAberrationLut(Handle<Image>);6869/// A plugin that implements a built-in postprocessing stack with some common70/// effects.71///72/// Currently, this only consists of chromatic aberration.73#[derive(Default)]74pub struct EffectStackPlugin;7576/// Adds colored fringes to the edges of objects in the scene.77///78/// [Chromatic aberration] simulates the effect when lenses fail to focus all79/// colors of light toward a single point. It causes rainbow-colored streaks to80/// appear, which are especially apparent on the edges of objects. Chromatic81/// aberration is commonly used for collision effects, especially in horror82/// games.83///84/// Bevy's implementation is based on that of *Inside* ([Gjøl & Svendsen 2016]).85/// It's based on a customizable lookup texture, which allows for changing the86/// color pattern. By default, the color pattern is simply a 3×1 pixel texture87/// consisting of red, green, and blue, in that order, but you can change it to88/// any image in order to achieve different effects.89///90/// [Chromatic aberration]: https://en.wikipedia.org/wiki/Chromatic_aberration91///92/// [Gjøl & Svendsen 2016]: https://github.com/playdeadgames/publications/blob/master/INSIDE/rendering_inside_gdc2016.pdf93#[derive(Reflect, Component, Clone)]94#[reflect(Component, Default, Clone)]95pub struct ChromaticAberration {96/// The lookup texture that determines the color gradient.97///98/// By default (if None), this is a 3×1 texel texture consisting of one red99/// pixel, one green pixel, and one blue texel, in that order. This100/// recreates the most typical chromatic aberration pattern. However, you101/// can change it to achieve different artistic effects.102///103/// The texture is always sampled in its vertical center, so it should104/// ordinarily have a height of 1 texel.105pub color_lut: Option<Handle<Image>>,106107/// The size of the streaks around the edges of objects, as a fraction of108/// the window size.109///110/// The default value is 0.02.111pub intensity: f32,112113/// A cap on the number of texture samples that will be performed.114///115/// Higher values result in smoother-looking streaks but are slower.116///117/// The default value is 8.118pub max_samples: u32,119}120121/// GPU pipeline data for the built-in postprocessing stack.122///123/// This is stored in the render world.124#[derive(Resource)]125pub struct PostProcessingPipeline {126/// The layout of bind group 0, containing the source, LUT, and settings.127bind_group_layout: BindGroupLayout,128/// Specifies how to sample the source framebuffer texture.129source_sampler: Sampler,130/// Specifies how to sample the chromatic aberration gradient.131chromatic_aberration_lut_sampler: Sampler,132/// The asset handle for the fullscreen vertex shader.133fullscreen_shader: FullscreenShader,134/// The fragment shader asset handle.135fragment_shader: Handle<Shader>,136}137138/// A key that uniquely identifies a built-in postprocessing pipeline.139#[derive(Clone, Copy, PartialEq, Eq, Hash)]140pub struct PostProcessingPipelineKey {141/// The format of the source and destination textures.142texture_format: TextureFormat,143}144145/// A component attached to cameras in the render world that stores the146/// specialized pipeline ID for the built-in postprocessing stack.147#[derive(Component, Deref, DerefMut)]148pub struct PostProcessingPipelineId(CachedRenderPipelineId);149150/// The on-GPU version of the [`ChromaticAberration`] settings.151///152/// See the documentation for [`ChromaticAberration`] for more information on153/// each of these fields.154#[derive(ShaderType)]155pub struct ChromaticAberrationUniform {156/// The intensity of the effect, in a fraction of the screen.157intensity: f32,158/// A cap on the number of samples of the source texture that the shader159/// will perform.160max_samples: u32,161/// Padding data.162unused_1: u32,163/// Padding data.164unused_2: u32,165}166167/// A resource, part of the render world, that stores the168/// [`ChromaticAberrationUniform`]s for each view.169#[derive(Resource, Deref, DerefMut, Default)]170pub struct PostProcessingUniformBuffers {171chromatic_aberration: DynamicUniformBuffer<ChromaticAberrationUniform>,172}173174/// A component, part of the render world, that stores the appropriate byte175/// offset within the [`PostProcessingUniformBuffers`] for the camera it's176/// attached to.177#[derive(Component, Deref, DerefMut)]178pub struct PostProcessingUniformBufferOffsets {179chromatic_aberration: u32,180}181182/// The render node that runs the built-in postprocessing stack.183#[derive(Default)]184pub struct PostProcessingNode;185186impl Plugin for EffectStackPlugin {187fn build(&self, app: &mut App) {188load_shader_library!(app, "chromatic_aberration.wgsl");189190embedded_asset!(app, "post_process.wgsl");191192// Load the default chromatic aberration LUT.193let mut assets = app.world_mut().resource_mut::<Assets<_>>();194let default_lut = assets.add(Image::new(195Extent3d {196width: 3,197height: 1,198depth_or_array_layers: 1,199},200TextureDimension::D2,201DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),202TextureFormat::Rgba8UnormSrgb,203RenderAssetUsages::RENDER_WORLD,204));205206app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default());207208let Some(render_app) = app.get_sub_app_mut(RenderApp) else {209return;210};211212render_app213.insert_resource(DefaultChromaticAberrationLut(default_lut))214.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()215.init_resource::<PostProcessingUniformBuffers>()216.add_systems(RenderStartup, init_post_processing_pipeline)217.add_systems(218Render,219(220prepare_post_processing_pipelines,221prepare_post_processing_uniforms,222)223.in_set(RenderSystems::Prepare),224)225.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(226Core3d,227Node3d::PostProcessing,228)229.add_render_graph_edges(230Core3d,231(232Node3d::DepthOfField,233Node3d::PostProcessing,234Node3d::Tonemapping,235),236)237.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(238Core2d,239Node2d::PostProcessing,240)241.add_render_graph_edges(242Core2d,243(Node2d::Bloom, Node2d::PostProcessing, Node2d::Tonemapping),244);245}246}247248impl Default for ChromaticAberration {249fn default() -> Self {250Self {251color_lut: None,252intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY,253max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES,254}255}256}257258pub fn init_post_processing_pipeline(259mut commands: Commands,260render_device: Res<RenderDevice>,261fullscreen_shader: Res<FullscreenShader>,262asset_server: Res<AssetServer>,263) {264// Create our single bind group layout.265let bind_group_layout = render_device.create_bind_group_layout(266Some("postprocessing bind group layout"),267&BindGroupLayoutEntries::sequential(268ShaderStages::FRAGMENT,269(270// Chromatic aberration source:271texture_2d(TextureSampleType::Float { filterable: true }),272// Chromatic aberration source sampler:273sampler(SamplerBindingType::Filtering),274// Chromatic aberration LUT:275texture_2d(TextureSampleType::Float { filterable: true }),276// Chromatic aberration LUT sampler:277sampler(SamplerBindingType::Filtering),278// Chromatic aberration settings:279uniform_buffer::<ChromaticAberrationUniform>(true),280),281),282);283284// Both source and chromatic aberration LUTs should be sampled285// bilinearly.286287let source_sampler = render_device.create_sampler(&SamplerDescriptor {288mipmap_filter: FilterMode::Linear,289min_filter: FilterMode::Linear,290mag_filter: FilterMode::Linear,291..default()292});293294let chromatic_aberration_lut_sampler = render_device.create_sampler(&SamplerDescriptor {295mipmap_filter: FilterMode::Linear,296min_filter: FilterMode::Linear,297mag_filter: FilterMode::Linear,298..default()299});300301commands.insert_resource(PostProcessingPipeline {302bind_group_layout,303source_sampler,304chromatic_aberration_lut_sampler,305fullscreen_shader: fullscreen_shader.clone(),306fragment_shader: load_embedded_asset!(asset_server.as_ref(), "post_process.wgsl"),307});308}309310impl SpecializedRenderPipeline for PostProcessingPipeline {311type Key = PostProcessingPipelineKey;312313fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {314RenderPipelineDescriptor {315label: Some("postprocessing".into()),316layout: vec![self.bind_group_layout.clone()],317vertex: self.fullscreen_shader.to_vertex_state(),318fragment: Some(FragmentState {319shader: self.fragment_shader.clone(),320targets: vec![Some(ColorTargetState {321format: key.texture_format,322blend: None,323write_mask: ColorWrites::ALL,324})],325..default()326}),327..default()328}329}330}331332impl ViewNode for PostProcessingNode {333type ViewQuery = (334Read<ViewTarget>,335Read<PostProcessingPipelineId>,336Read<ChromaticAberration>,337Read<PostProcessingUniformBufferOffsets>,338);339340fn run<'w>(341&self,342_: &mut RenderGraphContext,343render_context: &mut RenderContext<'w>,344(view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, '_, Self::ViewQuery>,345world: &'w World,346) -> Result<(), NodeRunError> {347let pipeline_cache = world.resource::<PipelineCache>();348let post_processing_pipeline = world.resource::<PostProcessingPipeline>();349let post_processing_uniform_buffers = world.resource::<PostProcessingUniformBuffers>();350let gpu_image_assets = world.resource::<RenderAssets<GpuImage>>();351let default_lut = world.resource::<DefaultChromaticAberrationLut>();352353// We need a render pipeline to be prepared.354let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {355return Ok(());356};357358// We need the chromatic aberration LUT to be present.359let Some(chromatic_aberration_lut) = gpu_image_assets.get(360chromatic_aberration361.color_lut362.as_ref()363.unwrap_or(&default_lut.0),364) else {365return Ok(());366};367368// We need the postprocessing settings to be uploaded to the GPU.369let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers370.chromatic_aberration371.binding()372else {373return Ok(());374};375376let diagnostics = render_context.diagnostic_recorder();377378// Use the [`PostProcessWrite`] infrastructure, since this is a379// full-screen pass.380let post_process = view_target.post_process_write();381382let pass_descriptor = RenderPassDescriptor {383label: Some("postprocessing"),384color_attachments: &[Some(RenderPassColorAttachment {385view: post_process.destination,386depth_slice: None,387resolve_target: None,388ops: Operations::default(),389})],390depth_stencil_attachment: None,391timestamp_writes: None,392occlusion_query_set: None,393};394395let bind_group = render_context.render_device().create_bind_group(396Some("postprocessing bind group"),397&post_processing_pipeline.bind_group_layout,398&BindGroupEntries::sequential((399post_process.source,400&post_processing_pipeline.source_sampler,401&chromatic_aberration_lut.texture_view,402&post_processing_pipeline.chromatic_aberration_lut_sampler,403chromatic_aberration_uniform_buffer_binding,404)),405);406407let mut render_pass = render_context408.command_encoder()409.begin_render_pass(&pass_descriptor);410let pass_span = diagnostics.pass_span(&mut render_pass, "postprocessing");411412render_pass.set_pipeline(pipeline);413render_pass.set_bind_group(0, &bind_group, &[**post_processing_uniform_buffer_offsets]);414render_pass.draw(0..3, 0..1);415416pass_span.end(&mut render_pass);417418Ok(())419}420}421422/// Specializes the built-in postprocessing pipeline for each applicable view.423pub fn prepare_post_processing_pipelines(424mut commands: Commands,425pipeline_cache: Res<PipelineCache>,426mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessingPipeline>>,427post_processing_pipeline: Res<PostProcessingPipeline>,428views: Query<(Entity, &ExtractedView), With<ChromaticAberration>>,429) {430for (entity, view) in views.iter() {431let pipeline_id = pipelines.specialize(432&pipeline_cache,433&post_processing_pipeline,434PostProcessingPipelineKey {435texture_format: if view.hdr {436ViewTarget::TEXTURE_FORMAT_HDR437} else {438TextureFormat::bevy_default()439},440},441);442443commands444.entity(entity)445.insert(PostProcessingPipelineId(pipeline_id));446}447}448449/// Gathers the built-in postprocessing settings for every view and uploads them450/// to the GPU.451pub fn prepare_post_processing_uniforms(452mut commands: Commands,453mut post_processing_uniform_buffers: ResMut<PostProcessingUniformBuffers>,454render_device: Res<RenderDevice>,455render_queue: Res<RenderQueue>,456mut views: Query<(Entity, &ChromaticAberration)>,457) {458post_processing_uniform_buffers.clear();459460// Gather up all the postprocessing settings.461for (view_entity, chromatic_aberration) in views.iter_mut() {462let chromatic_aberration_uniform_buffer_offset =463post_processing_uniform_buffers.push(&ChromaticAberrationUniform {464intensity: chromatic_aberration.intensity,465max_samples: chromatic_aberration.max_samples,466unused_1: 0,467unused_2: 0,468});469commands470.entity(view_entity)471.insert(PostProcessingUniformBufferOffsets {472chromatic_aberration: chromatic_aberration_uniform_buffer_offset,473});474}475476// Upload to the GPU.477post_processing_uniform_buffers.write_buffer(&render_device, &render_queue);478}479480impl ExtractComponent for ChromaticAberration {481type QueryData = Read<ChromaticAberration>;482483type QueryFilter = With<Camera>;484485type Out = ChromaticAberration;486487fn extract_component(488chromatic_aberration: QueryItem<'_, '_, Self::QueryData>,489) -> Option<Self::Out> {490// Skip the postprocessing phase entirely if the intensity is zero.491if chromatic_aberration.intensity > 0.0 {492Some(chromatic_aberration.clone())493} else {494None495}496}497}498499500