From d1d2a699a6cdde2fe478d0248f52ccf6743f7df7 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 7 Sep 2022 05:25:58 +0200 Subject: [PATCH 1/9] use vertex shader + attempt at resizing --- assets/shaders/screen_vertex.wgsl | 23 +++ .../color_blindness_simulation.rs | 172 ++++++++++++++++++ examples/shader/post_processing.rs | 108 ++++++++--- 3 files changed, 282 insertions(+), 21 deletions(-) create mode 100644 assets/shaders/screen_vertex.wgsl create mode 100644 examples/accessibility/color_blindness_simulation.rs diff --git a/assets/shaders/screen_vertex.wgsl b/assets/shaders/screen_vertex.wgsl new file mode 100644 index 0000000000000..deec73fb7f82a --- /dev/null +++ b/assets/shaders/screen_vertex.wgsl @@ -0,0 +1,23 @@ +#import bevy_sprite::mesh2d_view_bindings +#import bevy_sprite::mesh2d_bindings + +// NOTE: Bindings must come before functions that use them! +#import bevy_sprite::mesh2d_functions + +struct Vertex { + @location(0) position: vec3, + @location(2) uv: vec2, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + #import bevy_sprite::mesh2d_vertex_output +} + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex.uv; + out.clip_position = vec4((out.uv - vec2(0.5, 0.5)) * 2.0, 0.0, 1.0); + return out; +} \ No newline at end of file diff --git a/examples/accessibility/color_blindness_simulation.rs b/examples/accessibility/color_blindness_simulation.rs new file mode 100644 index 0000000000000..ec6502e2e86c6 --- /dev/null +++ b/examples/accessibility/color_blindness_simulation.rs @@ -0,0 +1,172 @@ +//! Small demo of how to use color blindness simulation +//! Shows a small scene, with four different cubes +//! +//! Holding the Space key enables the simulation +//! Pressing N cycles through the modes + +use bevy::{ + core_pipeline::clear_color::ClearColorConfig, + prelude::*, + render::camera::Viewport, + window::{close_on_esc, WindowId, WindowResized}, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // add the plugin + .add_plugin(ColorBlindnessPlugin) + .add_startup_system(setup) + .add_system(close_on_esc) + .add_system(change_mode) + .add_system(set_camera_viewports) + .run(); +} + +#[derive(Component)] +struct LeftCamera; + +#[derive(Component)] +struct RightCamera; + +/// set up a simple 3D scene +fn setup( + windows: Res, + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // create a small world + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..default() + }); + let cube = meshes.add(Mesh::from(shape::Cube { size: 0.5 })); + commands.spawn_bundle(PbrBundle { + mesh: cube.clone(), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: cube.clone(), + material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()), + transform: Transform::from_xyz(2.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: cube.clone(), + material: materials.add(Color::rgb(0.0, 1.0, 0.0).into()), + transform: Transform::from_xyz(3.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: cube, + material: materials.add(Color::rgb(0.0, 0.0, 1.0).into()), + transform: Transform::from_xyz(4.0, 0.5, 0.0), + ..default() + }); + commands.spawn_bundle(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + + let window = windows.primary(); + // create the cameras + commands + .spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + camera: Camera { + viewport: Some(Viewport { + physical_position: UVec2::new(0, 0), + physical_size: UVec2::new( + window.physical_width() / 2, + window.physical_height(), + ), + ..default() + }), + ..Default::default() + }, + ..default() + }) + .insert(ColorBlindnessCamera { + mode: ColorBlindnessMode::Deuteranopia, + enabled: false, + }) + .insert(LeftCamera); + // create the cameras + commands + .spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + camera: Camera { + viewport: Some(Viewport { + physical_position: UVec2::new(window.physical_width() / 2, 0), + physical_size: UVec2::new( + window.physical_width() / 2, + window.physical_height(), + ), + ..default() + }), + priority: 1, + ..Default::default() + }, + camera_3d: Camera3d { + // dont clear on the second camera because the first camera already cleared the window + clear_color: ClearColorConfig::None, + ..default() + }, + ..default() + }) + .insert(ColorBlindnessCamera { + mode: ColorBlindnessMode::Achromatomaly, + enabled: false, + }) + .insert(RightCamera); +} + +fn change_mode(input: Res>, mut cameras: Query<&mut ColorBlindnessCamera>) { + for mut camera in &mut cameras { + // cycle through the modes by pressing N + if input.just_pressed(KeyCode::N) { + camera.mode.cycle(); + println!("Changed to {:?}", camera.mode); + } + + camera.enabled = input.pressed(KeyCode::Space); + } +} + +fn set_camera_viewports( + windows: Res, + mut resize_events: EventReader, + mut left_camera: Query<&mut Camera, (With, Without)>, + mut right_camera: Query<&mut Camera, With>, +) { + // We need to dynamically resize the camera's viewports whenever the window size changes + // so then each camera always takes up half the screen. + // A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup. + for resize_event in resize_events.iter() { + if resize_event.id == WindowId::primary() { + let window = windows.primary(); + let mut left_camera = left_camera.single_mut(); + left_camera.viewport = Some(Viewport { + physical_position: UVec2::new(0, 0), + physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()), + ..default() + }); + + let mut right_camera = right_camera.single_mut(); + right_camera.viewport = Some(Viewport { + physical_position: UVec2::new(window.physical_width() / 2, 0), + physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()), + ..default() + }); + } + } +} diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index efe6d6fd7d814..b81b92f8708fc 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -9,14 +9,16 @@ use bevy::{ reflect::TypeUuid, render::{ camera::{Camera, RenderTarget}, + mesh::Indices, render_resource::{ - AsBindGroup, Extent3d, ShaderRef, TextureDescriptor, TextureDimension, TextureFormat, - TextureUsages, + AsBindGroup, Extent3d, PrimitiveTopology, ShaderRef, TextureDescriptor, + TextureDimension, TextureFormat, TextureUsages, }, texture::BevyDefault, view::RenderLayers, }, sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle}, + window::{WindowId, WindowResized}, }; fn main() { @@ -24,11 +26,18 @@ fn main() { app.add_plugins(DefaultPlugins) .add_plugin(Material2dPlugin::::default()) .add_startup_system(setup) - .add_system(main_camera_cube_rotator_system); + .add_system(main_camera_cube_rotator_system) + .add_system(update_image_to_window_size); app.run(); } +/// To support window resizing, this fits an image to a windows size. +#[derive(Component)] +struct FitToWindowSize { + image: Handle, + window_id: WindowId, +} /// Marks the first camera cube (rendered to a texture.) #[derive(Component)] struct MainCube; @@ -98,27 +107,50 @@ fn setup( }); // Main camera, first to render - commands.spawn_bundle(Camera3dBundle { - camera_3d: Camera3d { - clear_color: ClearColorConfig::Custom(Color::WHITE), - ..default() - }, - camera: Camera { - target: RenderTarget::Image(image_handle.clone()), + commands + .spawn_bundle(Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + camera: Camera { + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) + .looking_at(Vec3::default(), Vec3::Y), ..default() - }, - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) - .looking_at(Vec3::default(), Vec3::Y), - ..default() - }); + }) + .insert(FitToWindowSize { + image: image_handle.clone(), + window_id: window.id(), + }); // This specifies the layer used for the post processing camera, which will be attached to the post processing camera and 2d quad. let post_processing_pass_layer = RenderLayers::layer((RenderLayers::TOTAL_LAYERS - 1) as u8); - let quad_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::new( - size.width as f32, - size.height as f32, - )))); + let half_extents = Vec2::new(size.width as f32 / 2f32, size.height as f32 / 2f32); + let mut triangle_mesh = Mesh::new(PrimitiveTopology::TriangleList); + // NOTE: positions are actually not used because the vertex shader maps UV and clip space. + triangle_mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + [-half_extents.x, -half_extents.y, 0.0], + [half_extents.x * 3f32, -half_extents.y, 0.0], + [-half_extents.x, half_extents.y * 3f32, 0.0], + ], + ); + triangle_mesh.set_indices(Some(Indices::U32(vec![0, 1, 2]))); + triangle_mesh.insert_attribute( + Mesh::ATTRIBUTE_NORMAL, + vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], + ); + + triangle_mesh.insert_attribute( + Mesh::ATTRIBUTE_UV_0, + vec![[2.0, 0.0], [0.0, 2.0], [0.0, 0.0]], + ); + let triangle_handle = meshes.add(triangle_mesh); // This material has the texture that has been rendered. let material_handle = post_processing_materials.add(PostProcessingMaterial { @@ -128,7 +160,7 @@ fn setup( // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. commands .spawn_bundle(MaterialMesh2dBundle { - mesh: quad_handle.into(), + mesh: triangle_handle.into(), material: material_handle, transform: Transform { translation: Vec3::new(0.0, 0.0, 1.5), @@ -163,7 +195,38 @@ fn main_camera_cube_rotator_system( } // Region below declares of the custom material handling post processing effect - +/// Update image size to fit window +fn update_image_to_window_size( + windows: Res, + mut image_events: EventWriter>, + mut images: ResMut>, + mut resize_events: EventReader, + fit_to_window_size: Query<&FitToWindowSize>, +) { + for resize_event in resize_events.iter() { + for fit_to_window in fit_to_window_size.iter() { + if resize_event.id == fit_to_window.window_id { + let size = { + let window = windows.get(fit_to_window.window_id).expect("ColorBlindnessCamera is rendering to a window, but this window could not be found"); + Extent3d { + width: window.physical_width(), + height: window.physical_height(), + ..Default::default() + } + }; + let image = images.get_mut(&fit_to_window.image).expect( + "FitToScreenSize is referring to an Image, but this Image could not be found", + ); + dbg!(format!("resize to {:?}", size)); + image.resize(size); + // Hack because of https://github.com/bevyengine/bevy/issues/5595 + image_events.send(AssetEvent::Modified { + handle: fit_to_window.image.clone(), + }); + } + } + } +} /// Our custom post processing material #[derive(AsBindGroup, TypeUuid, Clone)] #[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"] @@ -178,4 +241,7 @@ impl Material2d for PostProcessingMaterial { fn fragment_shader() -> ShaderRef { "shaders/custom_material_chromatic_aberration.wgsl".into() } + fn vertex_shader() -> ShaderRef { + "shaders/screen_vertex.wgsl".into() + } } From a885e0390f9a21522080f9a21bb6cdbe1d69fcf6 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 7 Sep 2022 21:38:30 +0200 Subject: [PATCH 2/9] plugin for post processing --- examples/3d/render_to_texture.rs | 3 +- .../color_blindness_simulation.rs | 172 -------- examples/shader/post_processing.rs | 368 ++++++++++-------- 3 files changed, 212 insertions(+), 331 deletions(-) delete mode 100644 examples/accessibility/color_blindness_simulation.rs diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index 801f44f3caead..632942e442c6b 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -10,6 +10,7 @@ use bevy::{ render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, + texture::BevyDefault, view::RenderLayers, }, }; @@ -49,7 +50,7 @@ fn setup( label: None, size, dimension: TextureDimension::D2, - format: TextureFormat::Bgra8UnormSrgb, + format: TextureFormat::bevy_default(), mip_level_count: 1, sample_count: 1, usage: TextureUsages::TEXTURE_BINDING diff --git a/examples/accessibility/color_blindness_simulation.rs b/examples/accessibility/color_blindness_simulation.rs deleted file mode 100644 index ec6502e2e86c6..0000000000000 --- a/examples/accessibility/color_blindness_simulation.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! Small demo of how to use color blindness simulation -//! Shows a small scene, with four different cubes -//! -//! Holding the Space key enables the simulation -//! Pressing N cycles through the modes - -use bevy::{ - core_pipeline::clear_color::ClearColorConfig, - prelude::*, - render::camera::Viewport, - window::{close_on_esc, WindowId, WindowResized}, -}; - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - // add the plugin - .add_plugin(ColorBlindnessPlugin) - .add_startup_system(setup) - .add_system(close_on_esc) - .add_system(change_mode) - .add_system(set_camera_viewports) - .run(); -} - -#[derive(Component)] -struct LeftCamera; - -#[derive(Component)] -struct RightCamera; - -/// set up a simple 3D scene -fn setup( - windows: Res, - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - // create a small world - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), - material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), - ..default() - }); - let cube = meshes.add(Mesh::from(shape::Cube { size: 0.5 })); - commands.spawn_bundle(PbrBundle { - mesh: cube.clone(), - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), - transform: Transform::from_xyz(0.0, 0.5, 0.0), - ..default() - }); - commands.spawn_bundle(PbrBundle { - mesh: cube.clone(), - material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()), - transform: Transform::from_xyz(2.0, 0.5, 0.0), - ..default() - }); - commands.spawn_bundle(PbrBundle { - mesh: cube.clone(), - material: materials.add(Color::rgb(0.0, 1.0, 0.0).into()), - transform: Transform::from_xyz(3.0, 0.5, 0.0), - ..default() - }); - commands.spawn_bundle(PbrBundle { - mesh: cube, - material: materials.add(Color::rgb(0.0, 0.0, 1.0).into()), - transform: Transform::from_xyz(4.0, 0.5, 0.0), - ..default() - }); - commands.spawn_bundle(PointLightBundle { - point_light: PointLight { - intensity: 1500.0, - shadows_enabled: true, - ..default() - }, - transform: Transform::from_xyz(4.0, 8.0, 4.0), - ..default() - }); - - let window = windows.primary(); - // create the cameras - commands - .spawn_bundle(Camera3dBundle { - transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - camera: Camera { - viewport: Some(Viewport { - physical_position: UVec2::new(0, 0), - physical_size: UVec2::new( - window.physical_width() / 2, - window.physical_height(), - ), - ..default() - }), - ..Default::default() - }, - ..default() - }) - .insert(ColorBlindnessCamera { - mode: ColorBlindnessMode::Deuteranopia, - enabled: false, - }) - .insert(LeftCamera); - // create the cameras - commands - .spawn_bundle(Camera3dBundle { - transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - camera: Camera { - viewport: Some(Viewport { - physical_position: UVec2::new(window.physical_width() / 2, 0), - physical_size: UVec2::new( - window.physical_width() / 2, - window.physical_height(), - ), - ..default() - }), - priority: 1, - ..Default::default() - }, - camera_3d: Camera3d { - // dont clear on the second camera because the first camera already cleared the window - clear_color: ClearColorConfig::None, - ..default() - }, - ..default() - }) - .insert(ColorBlindnessCamera { - mode: ColorBlindnessMode::Achromatomaly, - enabled: false, - }) - .insert(RightCamera); -} - -fn change_mode(input: Res>, mut cameras: Query<&mut ColorBlindnessCamera>) { - for mut camera in &mut cameras { - // cycle through the modes by pressing N - if input.just_pressed(KeyCode::N) { - camera.mode.cycle(); - println!("Changed to {:?}", camera.mode); - } - - camera.enabled = input.pressed(KeyCode::Space); - } -} - -fn set_camera_viewports( - windows: Res, - mut resize_events: EventReader, - mut left_camera: Query<&mut Camera, (With, Without)>, - mut right_camera: Query<&mut Camera, With>, -) { - // We need to dynamically resize the camera's viewports whenever the window size changes - // so then each camera always takes up half the screen. - // A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup. - for resize_event in resize_events.iter() { - if resize_event.id == WindowId::primary() { - let window = windows.primary(); - let mut left_camera = left_camera.single_mut(); - left_camera.viewport = Some(Viewport { - physical_position: UVec2::new(0, 0), - physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()), - ..default() - }); - - let mut right_camera = right_camera.single_mut(); - right_camera.viewport = Some(Viewport { - physical_position: UVec2::new(window.physical_width() / 2, 0), - physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()), - ..default() - }); - } - } -} diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index b81b92f8708fc..92a1c93597666 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -3,84 +3,31 @@ //! This example is useful to implement your own post-processing effect such as //! edge detection, blur, pixelization, vignette... and countless others. -use bevy::{ - core_pipeline::clear_color::ClearColorConfig, - prelude::*, - reflect::TypeUuid, - render::{ - camera::{Camera, RenderTarget}, - mesh::Indices, - render_resource::{ - AsBindGroup, Extent3d, PrimitiveTopology, ShaderRef, TextureDescriptor, - TextureDimension, TextureFormat, TextureUsages, - }, - texture::BevyDefault, - view::RenderLayers, - }, - sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle}, - window::{WindowId, WindowResized}, -}; +use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*}; +use post_processing::PostProcessingCamera; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) - .add_plugin(Material2dPlugin::::default()) + .add_plugin(post_processing::PostProcessingPlugin) .add_startup_system(setup) - .add_system(main_camera_cube_rotator_system) - .add_system(update_image_to_window_size); + .add_system(main_camera_cube_rotator_system); app.run(); } -/// To support window resizing, this fits an image to a windows size. -#[derive(Component)] -struct FitToWindowSize { - image: Handle, - window_id: WindowId, -} /// Marks the first camera cube (rendered to a texture.) #[derive(Component)] struct MainCube; fn setup( mut commands: Commands, - mut windows: ResMut, mut meshes: ResMut>, - mut post_processing_materials: ResMut>, mut materials: ResMut>, - mut images: ResMut>, asset_server: Res, ) { asset_server.watch_for_changes().unwrap(); - let window = windows.get_primary_mut().unwrap(); - let size = Extent3d { - width: window.physical_width(), - height: window.physical_height(), - ..default() - }; - - // This is the texture that will be rendered to. - let mut image = Image { - texture_descriptor: TextureDescriptor { - label: None, - size, - dimension: TextureDimension::D2, - format: TextureFormat::bevy_default(), - mip_level_count: 1, - sample_count: 1, - usage: TextureUsages::TEXTURE_BINDING - | TextureUsages::COPY_DST - | TextureUsages::RENDER_ATTACHMENT, - }, - ..default() - }; - - // fill image.data with zeroes - image.resize(size); - - let image_handle = images.add(image); - let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 })); let cube_material_handle = materials.add(StandardMaterial { base_color: Color::rgb(0.8, 0.7, 0.6), @@ -113,74 +60,11 @@ fn setup( clear_color: ClearColorConfig::Custom(Color::WHITE), ..default() }, - camera: Camera { - target: RenderTarget::Image(image_handle.clone()), - ..default() - }, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), ..default() }) - .insert(FitToWindowSize { - image: image_handle.clone(), - window_id: window.id(), - }); - - // This specifies the layer used for the post processing camera, which will be attached to the post processing camera and 2d quad. - let post_processing_pass_layer = RenderLayers::layer((RenderLayers::TOTAL_LAYERS - 1) as u8); - - let half_extents = Vec2::new(size.width as f32 / 2f32, size.height as f32 / 2f32); - let mut triangle_mesh = Mesh::new(PrimitiveTopology::TriangleList); - // NOTE: positions are actually not used because the vertex shader maps UV and clip space. - triangle_mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - [-half_extents.x, -half_extents.y, 0.0], - [half_extents.x * 3f32, -half_extents.y, 0.0], - [-half_extents.x, half_extents.y * 3f32, 0.0], - ], - ); - triangle_mesh.set_indices(Some(Indices::U32(vec![0, 1, 2]))); - triangle_mesh.insert_attribute( - Mesh::ATTRIBUTE_NORMAL, - vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], - ); - - triangle_mesh.insert_attribute( - Mesh::ATTRIBUTE_UV_0, - vec![[2.0, 0.0], [0.0, 2.0], [0.0, 0.0]], - ); - let triangle_handle = meshes.add(triangle_mesh); - - // This material has the texture that has been rendered. - let material_handle = post_processing_materials.add(PostProcessingMaterial { - source_image: image_handle, - }); - - // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. - commands - .spawn_bundle(MaterialMesh2dBundle { - mesh: triangle_handle.into(), - material: material_handle, - transform: Transform { - translation: Vec3::new(0.0, 0.0, 1.5), - ..default() - }, - ..default() - }) - .insert(post_processing_pass_layer); - - // The post-processing pass camera. - commands - .spawn_bundle(Camera2dBundle { - camera: Camera { - // renders after the first main camera which has default value: 0. - priority: 1, - ..default() - }, - ..Camera2dBundle::default() - }) - .insert(post_processing_pass_layer); + .insert(PostProcessingCamera); } /// Rotates the cube rendered by the main camera @@ -194,54 +78,222 @@ fn main_camera_cube_rotator_system( } } -// Region below declares of the custom material handling post processing effect -/// Update image size to fit window -fn update_image_to_window_size( - windows: Res, - mut image_events: EventWriter>, - mut images: ResMut>, - mut resize_events: EventReader, - fit_to_window_size: Query<&FitToWindowSize>, -) { - for resize_event in resize_events.iter() { - for fit_to_window in fit_to_window_size.iter() { - if resize_event.id == fit_to_window.window_id { - let size = { - let window = windows.get(fit_to_window.window_id).expect("ColorBlindnessCamera is rendering to a window, but this window could not be found"); +mod post_processing { + use bevy::{ + prelude::*, + reflect::TypeUuid, + render::{ + camera::RenderTarget, + mesh::Indices, + render_resource::{ + AsBindGroup, Extent3d, PrimitiveTopology, ShaderRef, TextureDescriptor, + TextureDimension, TextureFormat, TextureUsages, + }, + texture::BevyDefault, + view::RenderLayers, + }, + sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle}, + window::{WindowId, WindowResized}, + }; + + pub struct PostProcessingPlugin; + + impl Plugin for PostProcessingPlugin { + fn build(&self, app: &mut App) { + app.add_plugin(Material2dPlugin::::default()) + .add_system(setup_new_color_blindness_cameras) + .add_system(update_image_to_window_size); + } + } + + /// To support window resizing, this fits an image to a windows size. + #[derive(Component)] + struct FitToWindowSize { + image: Handle, + window_id: WindowId, + } + #[derive(Component)] + pub struct PostProcessingCamera; + + /// Update image size to fit window + fn update_image_to_window_size( + windows: Res, + mut image_events: EventWriter>, + mut images: ResMut>, + mut resize_events: EventReader, + fit_to_window_size: Query<&FitToWindowSize>, + ) { + for resize_event in resize_events.iter() { + for fit_to_window in fit_to_window_size.iter() { + if resize_event.id == fit_to_window.window_id { + let size = { + let window = windows.get(fit_to_window.window_id).expect("ColorBlindnessCamera is rendering to a window, but this window could not be found"); + Extent3d { + width: window.physical_width(), + height: window.physical_height(), + ..Default::default() + } + }; + let image = images.get_mut(&fit_to_window.image).expect( + "FitToScreenSize is referring to an Image, but this Image could not be found", + ); + dbg!(format!("resize to {:?}", size)); + image.resize(size); + // Hack because of https://github.com/bevyengine/bevy/issues/5595 + image_events.send(AssetEvent::Modified { + handle: fit_to_window.image.clone(), + }); + } + } + } + } + + /// sets up post processing for cameras that have had `ColorBlindnessCamera` added + fn setup_new_color_blindness_cameras( + mut commands: Commands, + windows: Res, + mut meshes: ResMut>, + mut post_processing_materials: ResMut>, + mut images: ResMut>, + mut cameras: Query<(Entity, &mut Camera), Added>, + ) { + for (entity, mut camera) in &mut cameras { + let original_target = camera.target.clone(); + + let mut option_window_id: Option = None; + + // Get the size the camera is rendering to + let size = match &camera.target { + RenderTarget::Window(window_id) => { + let window = windows.get(*window_id).expect("ColorBlindnessCamera is rendering to a window, but this window could not be found"); + option_window_id = Some(*window_id); Extent3d { width: window.physical_width(), height: window.physical_height(), ..Default::default() } - }; - let image = images.get_mut(&fit_to_window.image).expect( - "FitToScreenSize is referring to an Image, but this Image could not be found", + } + RenderTarget::Image(handle) => { + let image = images.get(handle).expect( + "ColorBlindnessCamera is rendering to an Image, but this Image could not be found", ); - dbg!(format!("resize to {:?}", size)); - image.resize(size); - // Hack because of https://github.com/bevyengine/bevy/issues/5595 - image_events.send(AssetEvent::Modified { - handle: fit_to_window.image.clone(), + image.texture_descriptor.size + } + }; + + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::bevy_default(), + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + }, + ..Default::default() + }; + + // fill image.data with zeroes + image.resize(size); + + let image_handle = images.add(image); + + // This specifies the layer used for the post processing camera, which will be attached to the post processing camera and 2d quad. + let post_processing_pass_layer = + RenderLayers::layer((RenderLayers::TOTAL_LAYERS - 1) as u8); + let half_extents = Vec2::new(size.width as f32 / 2f32, size.height as f32 / 2f32); + let mut triangle_mesh = Mesh::new(PrimitiveTopology::TriangleList); + // NOTE: positions are actually not used because the vertex shader maps UV and clip space. + triangle_mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + [-half_extents.x, -half_extents.y, 0.0], + [half_extents.x * 3f32, -half_extents.y, 0.0], + [-half_extents.x, half_extents.y * 3f32, 0.0], + ], + ); + triangle_mesh.set_indices(Some(Indices::U32(vec![0, 1, 2]))); + triangle_mesh.insert_attribute( + Mesh::ATTRIBUTE_NORMAL, + vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], + ); + + triangle_mesh.insert_attribute( + Mesh::ATTRIBUTE_UV_0, + vec![[2.0, 0.0], [0.0, 2.0], [0.0, 0.0]], + ); + let triangle_handle = meshes.add(triangle_mesh); + + // This material has the texture that has been rendered. + let material_handle = post_processing_materials.add(PostProcessingMaterial { + source_image: image_handle.clone(), + }); + + commands + .entity(entity) + // add the handle to the camera so we can access it and change the percentages + .insert(material_handle.clone()) + // also disable show_ui so UI elements don't get rendered twice + .insert(UiCameraConfig { show_ui: false }); + if let Some(window_id) = option_window_id { + commands.entity(entity).insert(FitToWindowSize { + image: image_handle.clone(), + window_id, }); } + camera.target = RenderTarget::Image(image_handle); + + // Post processing 2d quad, with material using the render texture done by the main camera, with a custom shader. + commands + .spawn_bundle(MaterialMesh2dBundle { + mesh: triangle_handle.into(), + material: material_handle, + transform: Transform { + translation: Vec3::new(0.0, 0.0, 1.5), + ..Default::default() + }, + ..Default::default() + }) + .insert(post_processing_pass_layer); + + // The post-processing pass camera. + commands + .spawn_bundle(Camera2dBundle { + camera: Camera { + // renders after the first main camera which has default value: 0. + priority: camera.priority + 10, + // set this new camera to render to where the other camera was rendering + target: original_target, + ..Default::default() + }, + ..Camera2dBundle::default() + }) + .insert(post_processing_pass_layer); } } -} -/// Our custom post processing material -#[derive(AsBindGroup, TypeUuid, Clone)] -#[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"] -struct PostProcessingMaterial { - /// In this example, this image will be the result of the main camera. - #[texture(0)] - #[sampler(1)] - source_image: Handle, -} -impl Material2d for PostProcessingMaterial { - fn fragment_shader() -> ShaderRef { - "shaders/custom_material_chromatic_aberration.wgsl".into() + // Region below declares of the custom material handling post processing effect + + /// Our custom post processing material + #[derive(AsBindGroup, TypeUuid, Clone)] + #[uuid = "bc2f08eb-a0fb-43f1-a908-54871ea597d5"] + struct PostProcessingMaterial { + /// In this example, this image will be the result of the main camera. + #[texture(0)] + #[sampler(1)] + source_image: Handle, } - fn vertex_shader() -> ShaderRef { - "shaders/screen_vertex.wgsl".into() + + impl Material2d for PostProcessingMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/custom_material_chromatic_aberration.wgsl".into() + } + fn vertex_shader() -> ShaderRef { + "shaders/screen_vertex.wgsl".into() + } } } From ad51631b732f8d86ce92c17375a766df2b759e0b Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 7 Sep 2022 22:26:45 +0200 Subject: [PATCH 3/9] continuously update material post processing to allow resizing --- .../custom_material_chromatic_aberration.wgsl | 16 ++++++--- examples/shader/post_processing.rs | 33 +++++++++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/assets/shaders/custom_material_chromatic_aberration.wgsl b/assets/shaders/custom_material_chromatic_aberration.wgsl index e8ccdcfb62513..2c93409d635a4 100644 --- a/assets/shaders/custom_material_chromatic_aberration.wgsl +++ b/assets/shaders/custom_material_chromatic_aberration.wgsl @@ -6,6 +6,13 @@ var texture: texture_2d; @group(1) @binding(1) var our_sampler: sampler; +@group(1) @binding(2) +var offset_r: vec2; +@group(1) @binding(3) +var offset_g: vec2; +@group(1) @binding(4) +var offset_b: vec2; + @fragment fn fragment( @builtin(position) position: vec4, @@ -13,15 +20,14 @@ fn fragment( ) -> @location(0) vec4 { // Get screen position with coordinates from 0 to 1 let uv = position.xy / vec2(view.width, view.height); - let offset_strength = 0.02; // Sample each color channel with an arbitrary shift var output_color = vec4( - textureSample(texture, our_sampler, uv + vec2(offset_strength, -offset_strength)).r, - textureSample(texture, our_sampler, uv + vec2(-offset_strength, 0.0)).g, - textureSample(texture, our_sampler, uv + vec2(0.0, offset_strength)).b, + textureSample(texture, our_sampler, uv + offset_r).r, + textureSample(texture, our_sampler, uv + offset_g).g, + textureSample(texture, our_sampler, uv + offset_b).b, 1.0 - ); + ); return output_color; } diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index 92a1c93597666..ba0543fc80d9a 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -102,7 +102,8 @@ mod post_processing { fn build(&self, app: &mut App) { app.add_plugin(Material2dPlugin::::default()) .add_system(setup_new_color_blindness_cameras) - .add_system(update_image_to_window_size); + .add_system(update_image_to_window_size) + .add_system(update_enable); } } @@ -147,6 +148,22 @@ mod post_processing { } } } + fn update_enable( + time: Res