Path: blob/main/crates/bevy_post_process/src/effect_stack/mod.rs
9327 views
//! Miscellaneous built-in postprocessing effects.1//!2//! Includes:3//!4//! - Chromatic Aberration5//! - Vignette67mod chromatic_aberration;8mod vignette;910use bevy_color::ColorToComponents;11pub use chromatic_aberration::{ChromaticAberration, ChromaticAberrationUniform};12pub use vignette::{Vignette, VignetteUniform};1314use crate::effect_stack::chromatic_aberration::{15DefaultChromaticAberrationLut, DEFAULT_CHROMATIC_ABERRATION_LUT_DATA,16};1718use bevy_app::{App, Plugin};19use bevy_asset::{20embedded_asset, load_embedded_asset, AssetServer, Assets, Handle, RenderAssetUsages,21};22use bevy_derive::{Deref, DerefMut};23use bevy_ecs::{24component::Component,25entity::Entity,26query::{AnyOf, Or, With},27resource::Resource,28schedule::IntoScheduleConfigs as _,29system::{Commands, Query, Res, ResMut},30};31use bevy_image::{BevyDefault, Image};32use bevy_render::{33diagnostic::RecordDiagnostics,34extract_component::ExtractComponentPlugin,35render_asset::RenderAssets,36render_resource::{37binding_types::{sampler, texture_2d, uniform_buffer},38BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,39CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d,40FilterMode, FragmentState, MipmapFilterMode, Operations, PipelineCache,41RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,42SamplerBindingType, SamplerDescriptor, ShaderStages, SpecializedRenderPipeline,43SpecializedRenderPipelines, TextureDimension, TextureFormat, TextureSampleType,44},45renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},46texture::GpuImage,47view::{ExtractedView, ViewTarget},48Render, RenderApp, RenderStartup, RenderSystems,49};50use bevy_shader::{load_shader_library, Shader};51use bevy_utils::prelude::default;5253use crate::{bloom::bloom, dof::depth_of_field};54use bevy_core_pipeline::{55schedule::{Core2d, Core3d},56tonemapping::tonemapping,57FullscreenShader,58};5960/// A plugin that implements a built-in postprocessing stack with some common61/// effects.62///63/// Includes:64///65/// - Chromatic Aberration66/// - Vignette67#[derive(Default)]68pub struct EffectStackPlugin;6970/// GPU pipeline data for the built-in postprocessing stack.71///72/// This is stored in the render world.73#[derive(Resource)]74pub struct PostProcessingPipeline {75/// The layout of bind group 0, containing the source, LUT, and settings.76bind_group_layout: BindGroupLayoutDescriptor,77/// Specifies how to sample the source framebuffer texture.78source_sampler: Sampler,79/// Specifies how to sample the chromatic aberration gradient.80chromatic_aberration_lut_sampler: Sampler,81/// The asset handle for the fullscreen vertex shader.82fullscreen_shader: FullscreenShader,83/// The fragment shader asset handle.84fragment_shader: Handle<Shader>,85}8687/// A key that uniquely identifies a built-in postprocessing pipeline.88#[derive(Clone, Copy, PartialEq, Eq, Hash)]89pub struct PostProcessingPipelineKey {90/// The format of the source and destination textures.91texture_format: TextureFormat,92}9394/// A component attached to cameras in the render world that stores the95/// specialized pipeline ID for the built-in postprocessing stack.96#[derive(Component, Deref, DerefMut)]97pub struct PostProcessingPipelineId(CachedRenderPipelineId);9899/// A resource, part of the render world, that stores the uniform buffers for100/// post-processing effects.101///102/// This currently holds buffers for [`ChromaticAberrationUniform`] and103/// [`VignetteUniform`], allowing them to be uploaded to the GPU efficiently.104#[derive(Resource, Default)]105pub struct PostProcessingUniformBuffers {106chromatic_aberration: DynamicUniformBuffer<ChromaticAberrationUniform>,107vignette: DynamicUniformBuffer<VignetteUniform>,108}109110/// A component, part of the render world, that stores the appropriate byte111/// offset within the [`PostProcessingUniformBuffers`] for the camera it's112/// attached to.113#[derive(Component)]114pub struct PostProcessingUniformBufferOffsets {115chromatic_aberration: u32,116vignette: u32,117}118119impl Plugin for EffectStackPlugin {120fn build(&self, app: &mut App) {121load_shader_library!(app, "chromatic_aberration.wgsl");122load_shader_library!(app, "vignette.wgsl");123124embedded_asset!(app, "post_process.wgsl");125126// Load the default chromatic aberration LUT.127let mut assets = app.world_mut().resource_mut::<Assets<_>>();128let default_lut = assets.add(Image::new(129Extent3d {130width: 3,131height: 1,132depth_or_array_layers: 1,133},134TextureDimension::D2,135DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),136TextureFormat::Rgba8UnormSrgb,137RenderAssetUsages::RENDER_WORLD,138));139140app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default())141.add_plugins(ExtractComponentPlugin::<Vignette>::default());142143let Some(render_app) = app.get_sub_app_mut(RenderApp) else {144return;145};146147render_app148.insert_resource(DefaultChromaticAberrationLut(default_lut))149.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()150.init_resource::<PostProcessingUniformBuffers>()151.add_systems(RenderStartup, init_post_processing_pipeline)152.add_systems(153Render,154(155prepare_post_processing_pipelines,156prepare_post_processing_uniforms,157)158.in_set(RenderSystems::Prepare),159)160.add_systems(161Core3d,162post_processing.after(depth_of_field).before(tonemapping),163)164.add_systems(Core2d, post_processing.after(bloom).before(tonemapping));165}166}167168pub fn init_post_processing_pipeline(169mut commands: Commands,170render_device: Res<RenderDevice>,171fullscreen_shader: Res<FullscreenShader>,172asset_server: Res<AssetServer>,173) {174// Create our single bind group layout.175let bind_group_layout = BindGroupLayoutDescriptor::new(176"postprocessing bind group layout",177&BindGroupLayoutEntries::sequential(178ShaderStages::FRAGMENT,179(180// Common source:181texture_2d(TextureSampleType::Float { filterable: true }),182// Common source sampler:183sampler(SamplerBindingType::Filtering),184// Chromatic aberration LUT:185texture_2d(TextureSampleType::Float { filterable: true }),186// Chromatic aberration LUT sampler:187sampler(SamplerBindingType::Filtering),188// Chromatic aberration settings:189uniform_buffer::<ChromaticAberrationUniform>(true),190// Vignette settings:191uniform_buffer::<VignetteUniform>(true),192),193),194);195196// Both source and chromatic aberration LUTs should be sampled197// bilinearly.198199let source_sampler = render_device.create_sampler(&SamplerDescriptor {200mipmap_filter: MipmapFilterMode::Linear,201min_filter: FilterMode::Linear,202mag_filter: FilterMode::Linear,203..default()204});205206let chromatic_aberration_lut_sampler = render_device.create_sampler(&SamplerDescriptor {207mipmap_filter: MipmapFilterMode::Linear,208min_filter: FilterMode::Linear,209mag_filter: FilterMode::Linear,210..default()211});212213commands.insert_resource(PostProcessingPipeline {214bind_group_layout,215source_sampler,216chromatic_aberration_lut_sampler,217fullscreen_shader: fullscreen_shader.clone(),218fragment_shader: load_embedded_asset!(asset_server.as_ref(), "post_process.wgsl"),219});220}221222impl SpecializedRenderPipeline for PostProcessingPipeline {223type Key = PostProcessingPipelineKey;224225fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {226RenderPipelineDescriptor {227label: Some("postprocessing".into()),228layout: vec![self.bind_group_layout.clone()],229vertex: self.fullscreen_shader.to_vertex_state(),230fragment: Some(FragmentState {231shader: self.fragment_shader.clone(),232targets: vec![Some(ColorTargetState {233format: key.texture_format,234blend: None,235write_mask: ColorWrites::ALL,236})],237..default()238}),239..default()240}241}242}243244pub(crate) fn post_processing(245view: ViewQuery<(246&ViewTarget,247&PostProcessingPipelineId,248AnyOf<(&ChromaticAberration, &Vignette)>,249&PostProcessingUniformBufferOffsets,250)>,251pipeline_cache: Res<PipelineCache>,252post_processing_pipeline: Res<PostProcessingPipeline>,253post_processing_uniform_buffers: Res<PostProcessingUniformBuffers>,254gpu_image_assets: Res<RenderAssets<GpuImage>>,255default_lut: Res<DefaultChromaticAberrationLut>,256mut ctx: RenderContext,257) {258let (view_target, pipeline_id, post_effects, post_processing_uniform_buffer_offsets) =259view.into_inner();260261let (maybe_chromatic_aberration, maybe_vignette) = post_effects;262263if maybe_chromatic_aberration.is_none() && maybe_vignette.is_none() {264return;265}266267// We need a render pipeline to be prepared.268let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {269return;270};271272// We need the chromatic aberration LUT to be present.273let Some(chromatic_aberration_lut) = gpu_image_assets.get(274maybe_chromatic_aberration275.and_then(|ca| ca.color_lut.as_ref())276.unwrap_or(&default_lut.0),277) else {278return;279};280281// We need the postprocessing settings to be uploaded to the GPU.282let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers283.chromatic_aberration284.binding()285else {286return;287};288289let Some(vignette_uniform_buffer_binding) = post_processing_uniform_buffers.vignette.binding()290else {291return;292};293294// Use the [`PostProcessWrite`] infrastructure, since this is a full-screen pass.295let post_process = view_target.post_process_write();296297let pass_descriptor = RenderPassDescriptor {298label: Some("postprocessing"),299color_attachments: &[Some(RenderPassColorAttachment {300view: post_process.destination,301depth_slice: None,302resolve_target: None,303ops: Operations::default(),304})],305depth_stencil_attachment: None,306timestamp_writes: None,307occlusion_query_set: None,308multiview_mask: None,309};310311let bind_group = ctx.render_device().create_bind_group(312Some("postprocessing bind group"),313&pipeline_cache.get_bind_group_layout(&post_processing_pipeline.bind_group_layout),314&BindGroupEntries::sequential((315post_process.source,316&post_processing_pipeline.source_sampler,317&chromatic_aberration_lut.texture_view,318&post_processing_pipeline.chromatic_aberration_lut_sampler,319chromatic_aberration_uniform_buffer_binding,320vignette_uniform_buffer_binding,321)),322);323324let diagnostics = ctx.diagnostic_recorder();325let diagnostics = diagnostics.as_deref();326327let mut render_pass = ctx.begin_tracked_render_pass(pass_descriptor);328let pass_span = diagnostics.pass_span(&mut render_pass, "postprocessing");329330render_pass.set_render_pipeline(pipeline);331render_pass.set_bind_group(3320,333&bind_group,334&[335post_processing_uniform_buffer_offsets.chromatic_aberration,336post_processing_uniform_buffer_offsets.vignette,337],338);339render_pass.draw(0..3, 0..1);340341pass_span.end(&mut render_pass);342}343344/// Specializes the built-in postprocessing pipeline for each applicable view.345pub fn prepare_post_processing_pipelines(346mut commands: Commands,347pipeline_cache: Res<PipelineCache>,348mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessingPipeline>>,349post_processing_pipeline: Res<PostProcessingPipeline>,350views: Query<(Entity, &ExtractedView), Or<(With<ChromaticAberration>, With<Vignette>)>>,351) {352for (entity, view) in views.iter() {353let pipeline_id = pipelines.specialize(354&pipeline_cache,355&post_processing_pipeline,356PostProcessingPipelineKey {357texture_format: if view.hdr {358ViewTarget::TEXTURE_FORMAT_HDR359} else {360TextureFormat::bevy_default()361},362},363);364365commands366.entity(entity)367.insert(PostProcessingPipelineId(pipeline_id));368}369}370371/// Gathers the built-in postprocessing settings for every view and uploads them372/// to the GPU.373pub fn prepare_post_processing_uniforms(374mut commands: Commands,375mut post_processing_uniform_buffers: ResMut<PostProcessingUniformBuffers>,376render_device: Res<RenderDevice>,377render_queue: Res<RenderQueue>,378mut views: Query<379(Entity, Option<&ChromaticAberration>, Option<&Vignette>),380Or<(With<ChromaticAberration>, With<Vignette>)>,381>,382) {383post_processing_uniform_buffers.chromatic_aberration.clear();384post_processing_uniform_buffers.vignette.clear();385386// Gather up all the postprocessing settings.387for (view_entity, maybe_chromatic_aberration, maybe_vignette) in views.iter_mut() {388let chromatic_aberration_uniform_buffer_offset =389if let Some(chromatic_aberration) = maybe_chromatic_aberration {390post_processing_uniform_buffers.chromatic_aberration.push(391&ChromaticAberrationUniform {392intensity: chromatic_aberration.intensity,393max_samples: chromatic_aberration.max_samples,394unused_1: 0,395unused_2: 0,396},397)398} else {399post_processing_uniform_buffers400.chromatic_aberration401.push(&ChromaticAberrationUniform::default())402};403404let vignette_uniform_buffer_offset = if let Some(vignette) = maybe_vignette {405post_processing_uniform_buffers406.vignette407.push(&VignetteUniform {408intensity: vignette.intensity,409radius: vignette.radius,410smoothness: vignette.smoothness,411roundness: vignette.roundness,412center: vignette.center,413edge_compensation: vignette.edge_compensation,414unused: 0,415color: vignette.color.to_srgba().to_vec4(),416})417} else {418post_processing_uniform_buffers419.vignette420.push(&VignetteUniform::default())421};422423commands424.entity(view_entity)425.insert(PostProcessingUniformBufferOffsets {426chromatic_aberration: chromatic_aberration_uniform_buffer_offset,427vignette: vignette_uniform_buffer_offset,428});429}430431// Upload to the GPU.432post_processing_uniform_buffers433.chromatic_aberration434.write_buffer(&render_device, &render_queue);435post_processing_uniform_buffers436.vignette437.write_buffer(&render_device, &render_queue);438}439440441