use bevy::{
core_pipeline::{
core_2d::graph::{Core2d, Node2d},
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
},
ecs::query::QueryItem,
prelude::*,
render::{
extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
},
render_graph::{
NodeRunError, RenderGraphApp, RenderGraphContext, RenderLabel, ViewNode, ViewNodeRunner,
},
render_resource::{
binding_types::{sampler, texture_2d, uniform_buffer},
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
ColorTargetState, ColorWrites, FragmentState, MultisampleState, Operations,
PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
ShaderType, TextureFormat, TextureSampleType,
},
renderer::{RenderContext, RenderDevice},
texture::BevyDefault,
view::ViewTarget,
RenderApp,
},
};
pub struct ChromaticAberrationPlugin;
impl Plugin for ChromaticAberrationPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
ExtractComponentPlugin::<ChromaticAberattionSettings>::default(),
UniformComponentPlugin::<ChromaticAberattionSettings>::default(),
));
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_render_graph_node::<ViewNodeRunner<ChromaticAberrationNode>>(
Core2d,
ChromaticAberrationLabel,
)
.add_render_graph_edges(
Core2d,
(
Node2d::Tonemapping,
ChromaticAberrationLabel,
Node2d::EndMainPassPostProcessing,
),
);
}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<ChromaticAberrationPipeline>();
}
}
#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)]
pub struct ChromaticAberattionSettings {
pub intensity: f32,
pub red_offset: Vec2,
pub green_offset: Vec2,
pub blue_offset: Vec2,
#[cfg(feature = "webgl2")]
pub _webgl_padding: f32,
}
impl Default for ChromaticAberattionSettings {
fn default() -> Self {
Self {
intensity: default(),
red_offset: Vec2::NEG_Y + Vec2::X,
green_offset: Vec2::NEG_X,
blue_offset: Vec2::Y,
#[cfg(feature = "webgl2")]
_webgl_padding: default(),
}
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
struct ChromaticAberrationLabel;
#[derive(Default)]
struct ChromaticAberrationNode;
impl ViewNode for ChromaticAberrationNode {
type ViewQuery = (&'static ViewTarget, &'static ChromaticAberattionSettings);
fn run<'w>(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(view_target, _settings): QueryItem<'w, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let chromatic_aberration_pipeline = world.resource::<ChromaticAberrationPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();
let Some(pipeline) =
pipeline_cache.get_render_pipeline(chromatic_aberration_pipeline.pipeline_id)
else {
return Ok(());
};
let settings_uniform = world.resource::<ComponentUniforms<ChromaticAberattionSettings>>();
let Some(settings_binding) = settings_uniform.uniforms().binding() else {
return Ok(());
};
let post_process = view_target.post_process_write();
let bind_group = render_context.render_device().create_bind_group(
"chromatic_aberration_bind_group",
&chromatic_aberration_pipeline.layout,
&BindGroupEntries::sequential((
post_process.source,
&chromatic_aberration_pipeline.sampler,
settings_binding.clone(),
)),
);
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("post_process_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,
});
render_pass.set_render_pipeline(pipeline);
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.draw(0..3, 0..1);
Ok(())
}
}
#[derive(Resource)]
struct ChromaticAberrationPipeline {
layout: BindGroupLayout,
sampler: Sampler,
pipeline_id: CachedRenderPipelineId,
}
impl FromWorld for ChromaticAberrationPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let layout = render_device.create_bind_group_layout(
"chromatic_aberration_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
uniform_buffer::<ChromaticAberattionSettings>(false),
),
),
);
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
let shader = world
.resource::<AssetServer>()
.load("shaders/chromatic_aberration.wgsl");
let pipeline_id = world
.resource_mut::<PipelineCache>()
.queue_render_pipeline(RenderPipelineDescriptor {
label: Some("chromatic_aberration_pipeline".into()),
layout: vec![layout.clone()],
vertex: fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader,
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: TextureFormat::bevy_default(),
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
});
Self {
layout,
sampler,
pipeline_id,
}
}
}