Skip to content

Commit dffb0d0

Browse files
committed
WIP post_processing plugin copy
1 parent 197cbcb commit dffb0d0

File tree

4 files changed

+482
-3
lines changed

4 files changed

+482
-3
lines changed

Cargo.toml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3828,6 +3828,19 @@ name = "fallback_image"
38283828
path = "examples/shader/fallback_image.rs"
38293829
doc-scrape-examples = true
38303830

3831+
[package.metadata.example.fallback_image]
3832+
hidden = true
3833+
3834+
[[example]]
3835+
name = "fullscreen_material"
3836+
path = "examples/shader/fullscreen_material.rs"
3837+
doc-scrape-examples = true
3838+
3839+
[package.metadata.example.fullscreen_material]
3840+
name = "Fullscreen Material"
3841+
description = "Demonstrates how to write a fullscreen material"
3842+
category = "3D Rendering"
3843+
38313844
[[example]]
38323845
name = "reflection_probes"
38333846
path = "examples/3d/reflection_probes.rs"
@@ -3839,9 +3852,6 @@ description = "Demonstrates reflection probes"
38393852
category = "3D Rendering"
38403853
wasm = false
38413854

3842-
[package.metadata.example.fallback_image]
3843-
hidden = true
3844-
38453855
[package.metadata.example.window_resizing]
38463856
name = "Window Resizing"
38473857
description = "Demonstrates resizing and responding to resizing a window"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// This shader computes the chromatic aberration effect
2+
3+
// Since post processing is a fullscreen effect, we use the fullscreen vertex shader provided by bevy.
4+
// This will import a vertex shader that renders a single fullscreen triangle.
5+
//
6+
// A fullscreen triangle is a single triangle that covers the entire screen.
7+
// The box in the top left in that diagram is the screen. The 4 x are the corner of the screen
8+
//
9+
// Y axis
10+
// 1 | x-----x......
11+
// 0 | | s | . ´
12+
// -1 | x_____x´
13+
// -2 | : .´
14+
// -3 | :´
15+
// +--------------- X axis
16+
// -1 0 1 2 3
17+
//
18+
// As you can see, the triangle ends up bigger than the screen.
19+
//
20+
// You don't need to worry about this too much since bevy will compute the correct UVs for you.
21+
#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
22+
23+
@group(0) @binding(0) var screen_texture: texture_2d<f32>;
24+
@group(0) @binding(1) var texture_sampler: sampler;
25+
struct PostProcessSettings {
26+
intensity: f32,
27+
#ifdef SIXTEEN_BYTE_ALIGNMENT
28+
// WebGL2 structs must be 16 byte aligned.
29+
_webgl2_padding: vec3<f32>
30+
#endif
31+
}
32+
@group(0) @binding(2) var<uniform> settings: PostProcessSettings;
33+
34+
@fragment
35+
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
36+
// Chromatic aberration strength
37+
let offset_strength = settings.intensity;
38+
39+
// Sample each color channel with an arbitrary shift
40+
return vec4<f32>(
41+
textureSample(screen_texture, texture_sampler, in.uv + vec2<f32>(offset_strength, -offset_strength)).r,
42+
textureSample(screen_texture, texture_sampler, in.uv + vec2<f32>(-offset_strength, 0.0)).g,
43+
textureSample(screen_texture, texture_sampler, in.uv + vec2<f32>(0.0, offset_strength)).b,
44+
1.0
45+
);
46+
}
47+
48+

examples/shader/custom_post_processing.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use bevy::{
3030
RenderApp, RenderStartup,
3131
},
3232
};
33+
use plugin::{FullscreenMaterial, FullscreenMaterialPlugin};
3334

3435
/// This example uses a shader source file from the assets subdirectory
3536
const SHADER_ASSET_PATH: &str = "shaders/post_processing.wgsl";
@@ -312,6 +313,7 @@ fn setup(
312313
intensity: 0.02,
313314
..default()
314315
},
316+
MyPostProcessing { data: 1.0 },
315317
));
316318

317319
// cube
@@ -355,3 +357,146 @@ fn update_settings(mut settings: Query<&mut PostProcessSettings>, time: Res<Time
355357
setting.intensity = intensity;
356358
}
357359
}
360+
361+
mod plugin {
362+
use std::marker::PhantomData;
363+
364+
use bevy::{core_pipeline::FullscreenShader, prelude::*, shader::ShaderRef};
365+
use bevy_render::{
366+
extract_component::{
367+
DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
368+
},
369+
render_graph::ViewNode,
370+
render_resource::{
371+
binding_types::{sampler, texture_2d, uniform_buffer},
372+
encase::internal::WriteInto,
373+
AsBindGroup, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
374+
ColorTargetState, ColorWrites, FragmentState, PipelineCache, RenderPipelineDescriptor,
375+
Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType,
376+
TextureFormat, TextureSampleType,
377+
},
378+
renderer::RenderDevice,
379+
view::ViewTarget,
380+
RenderApp, RenderStartup,
381+
};
382+
383+
use crate::PostProcessSettings;
384+
385+
#[derive(Default)]
386+
pub struct FullscreenMaterialPlugin<T: FullscreenMaterial> {
387+
_marker: PhantomData<T>,
388+
}
389+
impl<T: FullscreenMaterial> Plugin for FullscreenMaterialPlugin<T> {
390+
fn build(&self, app: &mut App) {
391+
app.add_plugins((
392+
ExtractComponentPlugin::<T>::default(),
393+
UniformComponentPlugin::<T>::default(),
394+
));
395+
396+
// We need to get the render app from the main app
397+
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
398+
return;
399+
};
400+
render_app.add_systems(RenderStartup, init_pipeline::<T>);
401+
}
402+
}
403+
404+
pub trait FullscreenMaterial:
405+
Component + ExtractComponent + AsBindGroup + Clone + Copy + ShaderType + WriteInto
406+
{
407+
fn fragment_shader() -> ShaderRef;
408+
}
409+
#[derive(Resource)]
410+
struct FullscreenMaterialPipeline {
411+
layout: BindGroupLayout,
412+
sampler: Sampler,
413+
pipeline_id: CachedRenderPipelineId,
414+
}
415+
fn init_pipeline<T: FullscreenMaterial>(
416+
mut commands: Commands,
417+
render_device: Res<RenderDevice>,
418+
asset_server: Res<AssetServer>,
419+
fullscreen_shader: Res<FullscreenShader>,
420+
pipeline_cache: Res<PipelineCache>,
421+
) {
422+
// We need to define the bind group layout used for our pipeline
423+
let layout = render_device.create_bind_group_layout(
424+
"post_process_bind_group_layout",
425+
&BindGroupLayoutEntries::sequential(
426+
// The layout entries will only be visible in the fragment stage
427+
ShaderStages::FRAGMENT,
428+
(
429+
// The screen texture
430+
texture_2d(TextureSampleType::Float { filterable: true }),
431+
// The sampler that will be used to sample the screen texture
432+
sampler(SamplerBindingType::Filtering),
433+
// The settings uniform that will control the effect
434+
uniform_buffer::<T>(true),
435+
),
436+
),
437+
);
438+
// We can create the sampler here since it won't change at runtime and doesn't depend on the view
439+
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
440+
let mayber_shader = match T::fragment_shader() {
441+
ShaderRef::Default => {
442+
unimplemented!("No default fallback for FullscreenMaterial shader")
443+
}
444+
ShaderRef::Handle(handle) => handle,
445+
ShaderRef::Path(path) => asset_server.load(path),
446+
};
447+
// This will setup a fullscreen triangle for the vertex state.
448+
let vertex_state = fullscreen_shader.to_vertex_state();
449+
let pipeline_id = pipeline_cache
450+
// This will add the pipeline to the cache and queue its creation
451+
.queue_render_pipeline(RenderPipelineDescriptor {
452+
label: Some("post_process_pipeline".into()),
453+
layout: vec![layout.clone()],
454+
vertex: vertex_state,
455+
fragment: Some(FragmentState {
456+
shader,
457+
// Make sure this matches the entry point of your shader.
458+
// It can be anything as long as it matches here and in the shader.
459+
targets: vec![Some(ColorTargetState {
460+
format: TextureFormat::bevy_default(),
461+
blend: None,
462+
write_mask: ColorWrites::ALL,
463+
})],
464+
..default()
465+
}),
466+
..default()
467+
});
468+
commands.insert_resource(FullscreenMaterialPipeline {
469+
layout,
470+
sampler,
471+
pipeline_id,
472+
});
473+
}
474+
struct FullscreenMaterialNode<T: FullscreenMaterial> {
475+
_marker: PhantomData<T>,
476+
}
477+
478+
impl<T: FullscreenMaterial> ViewNode for FullscreenMaterialNode<T> {
479+
// The node needs a query to gather data from the ECS in order to do its rendering,
480+
// but it's not a normal system so we need to define it manually.
481+
//
482+
// This query will only run on the view entity
483+
type ViewQuery = (
484+
&'static ViewTarget,
485+
// This makes sure the node only runs on cameras with the PostProcessSettings component
486+
&'static T,
487+
// As there could be multiple post processing components sent to the GPU (one per camera),
488+
// we need to get the index of the one that is associated with the current view.
489+
&'static DynamicUniformIndex<T>,
490+
);
491+
492+
fn run<'w>(
493+
&self,
494+
graph: &mut bevy_render::render_graph::RenderGraphContext,
495+
render_context: &mut bevy_render::renderer::RenderContext<'w>,
496+
view_query: bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>,
497+
world: &'w World,
498+
) -> std::result::Result<(), bevy_render::render_graph::NodeRunError> {
499+
todo!()
500+
}
501+
}
502+
}

0 commit comments

Comments
 (0)