From 765bd46c2eb853a6921c4de47c065e4850ede5fe Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 6 Jun 2022 00:06:49 +0000 Subject: [PATCH] add a post-processing example (#4797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Add an example showing a custom post processing effect, done after the first rendering pass. ## Solution - A simple post processing "chromatic aberration" effect. I mixed together examples `3d/render_to_texture`, and `shader/shader_material_screenspace_texture` - Reading a bit how https://github.com/bevyengine/bevy/pull/3430 was done gave me pointers to apply the main pass to the 2d render rather than using a 3d quad. This work might be or not be relevant to https://github.com/bevyengine/bevy/issues/2724
⚠️ Click for a video of the render ⚠️ I’ve been told it might hurt the eyes 👀 , maybe we should choose another effect just in case ? https://user-images.githubusercontent.com/2290685/169138830-a6dc8a9f-8798-44b9-8d9e-449e60614916.mp4
# Request for feedbacks - [ ] Is chromatic aberration effect ok ? (Correct term, not a danger for the eyes ?) I'm open to suggestion to make something different. - [ ] Is the code idiomatic ? I preferred a "main camera -> **new camera with post processing applied to a quad**" approach to emulate minimum modification to existing code wanting to add global post processing. --- ## Changelog - Add a full screen post processing shader example --- Cargo.toml | 4 + .../custom_material_chromatic_aberration.wgsl | 25 ++ examples/README.md | 1 + examples/shader/post_processing.rs | 256 ++++++++++++++++++ 4 files changed, 286 insertions(+) create mode 100644 assets/shaders/custom_material_chromatic_aberration.wgsl create mode 100644 examples/shader/post_processing.rs diff --git a/Cargo.toml b/Cargo.toml index 732c4b60044a8..5ba442b6c4f25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -512,6 +512,10 @@ path = "examples/scene/scene.rs" name = "custom_vertex_attribute" path = "examples/shader/custom_vertex_attribute.rs" +[[example]] +name = "post_processing" +path = "examples/shader/post_processing.rs" + [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" diff --git a/assets/shaders/custom_material_chromatic_aberration.wgsl b/assets/shaders/custom_material_chromatic_aberration.wgsl new file mode 100644 index 0000000000000..811cfb8810abc --- /dev/null +++ b/assets/shaders/custom_material_chromatic_aberration.wgsl @@ -0,0 +1,25 @@ +#import bevy_pbr::mesh_view_bindings + +[[group(1), binding(0)]] +var texture: texture_2d; + +[[group(1), binding(1)]] +var our_sampler: sampler; + + +[[stage(fragment)]] +fn fragment([[builtin(position)]] position: vec4) -> [[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, + 1.0 + ); + + return output_color; +} diff --git a/examples/README.md b/examples/README.md index eca0e384ff88a..6efb802391cf9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -253,6 +253,7 @@ Example | File | Description `animate_shader` | [`shader/animate_shader.rs`](./shader/animate_shader.rs) | A shader that uses dynamic data like the time since startup. `compute_shader_game_of_life` | [`shader/compute_shader_game_of_life.rs`](./shader/compute_shader_game_of_life.rs) | A compute shader that simulates Conway's Game of Life. `custom_vertex_attribute` | [`shader/custom_vertex_attribute.rs`](./shader/custom_vertex_attribute.rs) | A shader that reads a mesh's custom vertex attribute. +`post_processing` | [`shader/post_processing.rs`](./shader/post_processing.rs) | A custom post processing effect, using two cameras, with one reusing the render texture of the first one. `shader_defs` | [`shader/shader_defs.rs`](./shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader). `shader_instancing` | [`shader/shader_instancing.rs`](./shader/shader_instancing.rs) | A shader that renders a mesh multiple times in one draw call. `shader_material` | [`shader/shader_material.rs`](./shader/shader_material.rs) | A shader and a material that uses it. diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs new file mode 100644 index 0000000000000..14ec05ba77479 --- /dev/null +++ b/examples/shader/post_processing.rs @@ -0,0 +1,256 @@ +//! A custom post processing effect, using two cameras, with one reusing the render texture of the first one. +//! Here a chromatic aberration is applied to a 3d scene containting a rotating cube. +//! 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, + ecs::system::{lifetimeless::SRes, SystemParamItem}, + prelude::*, + reflect::TypeUuid, + render::{ + camera::{Camera, RenderTarget}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, + render_resource::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, + Extent3d, SamplerBindingType, ShaderStages, TextureDescriptor, TextureDimension, + TextureFormat, TextureSampleType, TextureUsages, TextureViewDimension, + }, + renderer::RenderDevice, + view::RenderLayers, + }, + sprite::{Material2d, Material2dPipeline, Material2dPlugin, MaterialMesh2dBundle}, +}; + +fn main() { + let mut app = App::new(); + app.add_plugins(DefaultPlugins) + .add_plugin(Material2dPlugin::::default()) + .add_startup_system(setup) + .add_system(main_camera_cube_rotator_system); + + app.run(); +} + +/// 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>, +) { + 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::Bgra8UnormSrgb, + 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), + reflectance: 0.02, + unlit: false, + ..default() + }); + + // The cube that will be rendered to the texture. + commands + .spawn_bundle(PbrBundle { + mesh: cube_handle, + material: cube_material_handle, + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), + ..default() + }) + .insert(MainCube); + + // Light + // NOTE: Currently lights are ignoring render layers - see https://github.com/bevyengine/bevy/issues/3462 + commands.spawn_bundle(PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + }); + + // Main camera, first to render + commands.spawn_bundle(Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + }, + 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() + }); + + // 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, + )))); + + // 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: quad_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); +} + +/// Rotates the cube rendered by the main camera +fn main_camera_cube_rotator_system( + time: Res